|
| 1 | +// ============================================================================= |
| 2 | +// npu_periph.sv — the systolic NPU wrapped as a memory-mapped accelerator |
| 3 | +// ----------------------------------------------------------------------------- |
| 4 | +// Register map (byte offsets within the peripheral): |
| 5 | +// 0x00..0x0C A[0][0] A[0][1] A[1][0] A[1][1] (write operands) |
| 6 | +// 0x10..0x1C B[0][0] B[0][1] B[1][0] B[1][1] (write operands) |
| 7 | +// 0x20 CONTROL : write bit0=1 -> start a multiply |
| 8 | +// 0x24 STATUS : read bit0 -> done |
| 9 | +// 0x30..0x3C C[0][0] C[0][1] C[1][0] C[1][1] (read results) |
| 10 | +// |
| 11 | +// On start it clears the array, streams the stored operands in with the correct |
| 12 | +// systolic skew, then raises `done`. The CPU polls STATUS and reads the result. |
| 13 | +// Fixed for a 2x2 array (the register map is size-specific). |
| 14 | +// ============================================================================= |
| 15 | +`default_nettype none |
| 16 | + |
| 17 | +module npu_periph #(parameter int N = 2, parameter int DATA_W = 8, parameter int ACC_W = 32) ( |
| 18 | + input wire clk, rst_n, |
| 19 | + input wire [31:0] addr, |
| 20 | + input wire [31:0] wdata, |
| 21 | + input wire we, |
| 22 | + output reg [31:0] rdata |
| 23 | +); |
| 24 | + logic [DATA_W-1:0] A_reg [0:1][0:1]; |
| 25 | + logic [DATA_W-1:0] B_reg [0:1][0:1]; |
| 26 | + |
| 27 | + typedef enum logic [1:0] {IDLE, CLR, RUN, DONE} st_t; |
| 28 | + st_t state; |
| 29 | + logic [5:0] t; |
| 30 | + logic done, arr_clr; |
| 31 | + |
| 32 | + // operands fed to the array this cycle (skewed) |
| 33 | + logic [DATA_W-1:0] a0, a1, b0, b1; |
| 34 | + wire [N*DATA_W-1:0] a_left = {a1, a0}; |
| 35 | + wire [N*DATA_W-1:0] b_top = {b1, b0}; |
| 36 | + wire [N*N*ACC_W-1:0] c_flat; |
| 37 | + |
| 38 | + systolic_mm #(.N(N), .DATA_W(DATA_W), .ACC_W(ACC_W)) u_arr ( |
| 39 | + .clk, .rst_n, .clr(arr_clr), .a_left, .b_top, .c_flat); |
| 40 | + |
| 41 | + // helper nets (avoid procedural part-selects of wdata) |
| 42 | + wire [DATA_W-1:0] wbyte = wdata[DATA_W-1:0]; |
| 43 | + wire wd0 = wdata[0]; |
| 44 | + wire [7:0] off = addr[7:0]; |
| 45 | + |
| 46 | + // result slices as nets |
| 47 | + wire [ACC_W-1:0] c00 = c_flat[0*ACC_W +: ACC_W]; |
| 48 | + wire [ACC_W-1:0] c01 = c_flat[1*ACC_W +: ACC_W]; |
| 49 | + wire [ACC_W-1:0] c10 = c_flat[2*ACC_W +: ACC_W]; |
| 50 | + wire [ACC_W-1:0] c11 = c_flat[3*ACC_W +: ACC_W]; |
| 51 | + |
| 52 | + // ---- skewed operand feed (combinational, active during RUN) ---- |
| 53 | + always_comb begin |
| 54 | + a0 = '0; a1 = '0; b0 = '0; b1 = '0; |
| 55 | + if (state == RUN) begin |
| 56 | + if (t == 0) a0 = A_reg[0][0]; else if (t == 1) a0 = A_reg[0][1]; |
| 57 | + if (t == 1) a1 = A_reg[1][0]; else if (t == 2) a1 = A_reg[1][1]; |
| 58 | + if (t == 0) b0 = B_reg[0][0]; else if (t == 1) b0 = B_reg[1][0]; |
| 59 | + if (t == 1) b1 = B_reg[0][1]; else if (t == 2) b1 = B_reg[1][1]; |
| 60 | + end |
| 61 | + end |
| 62 | + |
| 63 | + // ---- control FSM + operand register writes ---- |
| 64 | + always_ff @(posedge clk or negedge rst_n) begin |
| 65 | + if (!rst_n) begin |
| 66 | + state <= IDLE; t <= 0; done <= 0; arr_clr <= 0; |
| 67 | + end else begin |
| 68 | + arr_clr <= 1'b0; |
| 69 | + case (state) |
| 70 | + IDLE: begin |
| 71 | + if (we) begin |
| 72 | + case (off) |
| 73 | + 8'h00: A_reg[0][0] <= wbyte; |
| 74 | + 8'h04: A_reg[0][1] <= wbyte; |
| 75 | + 8'h08: A_reg[1][0] <= wbyte; |
| 76 | + 8'h0C: A_reg[1][1] <= wbyte; |
| 77 | + 8'h10: B_reg[0][0] <= wbyte; |
| 78 | + 8'h14: B_reg[0][1] <= wbyte; |
| 79 | + 8'h18: B_reg[1][0] <= wbyte; |
| 80 | + 8'h1C: B_reg[1][1] <= wbyte; |
| 81 | + 8'h20: if (wd0) begin state <= CLR; arr_clr <= 1'b1; done <= 1'b0; t <= 0; end |
| 82 | + default: ; |
| 83 | + endcase |
| 84 | + end |
| 85 | + end |
| 86 | + CLR: begin state <= RUN; t <= 0; end |
| 87 | + RUN: begin |
| 88 | + t <= t + 1'b1; |
| 89 | + if (t >= 6) begin state <= DONE; done <= 1'b1; end |
| 90 | + end |
| 91 | + DONE: begin |
| 92 | + done <= 1'b1; |
| 93 | + if (we && off == 8'h20 && wd0) begin state <= CLR; arr_clr <= 1'b1; done <= 1'b0; t <= 0; end |
| 94 | + end |
| 95 | + endcase |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + // ---- read mux (combinational) ---- |
| 100 | + always_comb begin |
| 101 | + case (off) |
| 102 | + 8'h24: rdata = {31'b0, done}; |
| 103 | + 8'h30: rdata = c00; |
| 104 | + 8'h34: rdata = c01; |
| 105 | + 8'h38: rdata = c10; |
| 106 | + 8'h3C: rdata = c11; |
| 107 | + default: rdata = 32'h0; |
| 108 | + endcase |
| 109 | + end |
| 110 | +endmodule |
| 111 | + |
| 112 | +`default_nettype wire |
0 commit comments