// 8x8 Weight-Stationary Systolic Array
module systolic_array #(parameter DW=16, N=8, AW=40)(
    input clk, rst_n, en, clear_acc, weight_load,
    input signed [DW-1:0] wt_in[0:N-1],
    input signed [DW-1:0] act_in[0:N-1], input act_valid,
    input signed [AW-1:0] psum_in[0:N-1],
    output signed [AW-1:0] psum_out[0:N-1], output psum_valid
);
    wire signed [DW-1:0] w_store[0:N-1][0:N-1];
    wire signed [DW-1:0] ah[0:N][0:N-1];
    wire signed [AW-1:0] pv[0:N-1][0:N];
    genvar i,j; generate
        for(i=0;i<N;i++) begin:fd
            sa_delay #(.DW(DW),.DLY(i)) ud(.clk(clk),.rst_n(rst_n),.d(act_in[i]),.v(act_valid),.q(ah[0][i]),.qv());
        end
        for(i=0;i<N;i++) for(j=0;j<N;j++) begin:pe
            sa_pe #(.DW(DW),.AW(AW)) u(.clk(clk),.rst_n(rst_n),.en(en),.clr(clear_acc),
                .wl(weight_load),.wi(ah[i][j]),.wo(w_store[i][j]),
                .ai(ah[i][j]),.ao(ah[i+1][j]),
                .pi(pv[i][j]),.po(pv[i][j+1]));
        end
        for(j=0;j<N;j++) begin:pvf assign pv[j][0]=psum_in[j]; assign psum_out[j]=pv[j][N]; end
    endgenerate
    reg [7:0] vc; always_ff @(posedge clk or negedge rst_n)
        if(!rst_n) vc<=0; else if(clear_acc) vc<=0; else if(en&&act_valid&&vc<255) vc<=vc+1;
    assign psum_valid=(vc>=N);
endmodule

module sa_pe #(parameter DW=16, AW=40)(
    input clk,rst_n,en,clr,wl, input signed [DW-1:0] wi,ai, input signed [AW-1:0] pi,
    output signed [DW-1:0] wo, output reg signed [DW-1:0] ao, output reg signed [AW-1:0] po
);
    reg signed [DW-1:0] sw;
    always_ff @(posedge clk or negedge rst_n)
        if(!rst_n) begin sw<='0; ao<='0; po<='0; end
        else if(en) begin if(wl) sw<=wi; ao<=ai; po<=clr?'0:(pi+ai*sw); end
    assign wo=sw;
endmodule

module sa_delay #(parameter DW=16, DLY=0)(
    input clk,rst_n, input signed [DW-1:0] d, input v, output signed [DW-1:0] q, output qv
);
    reg signed [DW-1:0] dr[0:DLY-1]; reg [0:DLY-1] vr; integer k;
    always_ff @(posedge clk or negedge rst_n)
        if(!rst_n) begin for(k=0;k<DLY;k++) begin dr[k]<='0; vr[k]<=0; end end
        else if(DLY>0) begin dr[0]<=d; vr[0]<=v; for(k=1;k<DLY;k++) begin dr[k]<=dr[k-1]; vr[k]<=vr[k-1]; end end
    assign q=(DLY>0)?dr[DLY-1]:d; assign qv=(DLY>0)?vr[DLY-1]:v;
endmodule