File size: 12,729 Bytes
2166d7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// FA_ENGINE: tiled FlashAttention with online softmax, driven by an in-memory descriptor.
//
//   FA_BEGIN descriptor (6 consecutive words at desc_addr):
//     [0] Q_ptr  [1] K_ptr  [2] V_ptr  [3] O_ptr  [4] sequence_length N  [5] head_dimension d
//   Q, K, V, O are N x d row-major word arrays. Q/K/V elements are Q4.4 int8 (low 8 bits of the
//   word); O elements are signed integers with 8 fractional bits.
//
//   FA_ENGINE
//   |-- Q_TILE_SRAM / K_TILE_SRAM / V_TILE_SRAM
//   |-- QK_DOT_PRODUCT (fa_qk_mac)  -> ROW_MAX (fa_row_max)
//   |-- ONLINE_SOFTMAX (m, l) with EXP_APPROX
//   |-- PV_ACCUMULATOR
//   |-- OUTPUT_NORMALIZER
//   `-- DMA / MEMORY_INTERFACE
//
// start/desc_addr are sampled while idle. done pulses when finished; err is set (until the next
// start) if the descriptor is invalid, in which case memory is not written.

module fa_engine
    import fa_pkg::*;
(
    input  logic        clk,
    input  logic        rst_n,

    input  logic        start,
    input  logic [31:0] desc_addr,
    output logic        busy,
    output logic        done,
    output logic        err,

    output logic        mem_req,
    output logic        mem_we,
    output logic [31:0] mem_addr,
    output logic [31:0] mem_wdata,
    input  logic [31:0] mem_rdata,
    input  logic        mem_ready
);

    localparam int RW = $clog2(BR);
    localparam int CW = $clog2(D_MAX);
    localparam logic [31:0] MAX_N = 32'd1_048_576;

    localparam logic [1:0] OP_LOAD = 2'd0, OP_RD = 2'd1, OP_WR = 2'd2;
    localparam logic [1:0] TGT_Q = 2'd0, TGT_K = 2'd1, TGT_V = 2'd2;

    typedef enum logic [5:0] {
        S_IDLE, S_DESC_REQ, S_DESC_WAIT, S_CHECK,
        S_Q_REQ, S_Q_WAIT, S_INIT,
        S_K_REQ, S_K_WAIT, S_V_REQ, S_V_WAIT,
        S_SC_CLR, S_SC_RD, S_SC_FEED, S_SC_WAIT,
        S_TILE_ST, S_ALPHA_WAIT, S_RESC, S_RESC_WAIT,
        S_PR_RD, S_PR_WAIT, S_PR_ACC, S_PR_ACCW,
        S_NRM_START, S_NRM_WAIT, S_NRM_STORE, S_NRM_STWAIT,
        S_FIN, S_ERR
    } state_t;
    state_t state;

    // descriptor
    logic [31:0] desc_base;
    logic [2:0]  dcnt;
    logic [31:0] q_ptr, k_ptr, v_ptr, o_ptr, seq_n, hd;

    // loop state
    logic [31:0] i0, j0, rows_q, keys_k, r, jj, nr, nc;
    logic [1:0]  tgt;
    logic signed [31:0] score_buf [BC];
    logic signed [31:0] nrm_out_q;
    logic err_q;

    assign busy = (state != S_IDLE);
    assign err  = err_q;

    function automatic logic [31:0] min32(input logic [31:0] a, input logic [31:0] b);
        return (a < b) ? a : b;
    endfunction

    // ------------------------------------------------------------------ DMA
    logic        dma_cmd_valid;
    logic [1:0]  dma_op;
    logic [31:0] dma_addr, dma_wdata;
    logic [3:0]  dma_rows;
    logic [4:0]  dma_cols;
    logic        dma_done;
    logic [31:0] dma_rd_data;
    logic        sram_we;
    logic [1:0]  sram_row;
    logic [3:0]  sram_col;
    logic [7:0]  sram_data;

    fa_dma u_dma (
        .clk, .rst_n,
        .cmd_valid(dma_cmd_valid), .cmd_op(dma_op), .cmd_addr(dma_addr),
        .cmd_rows(dma_rows), .cmd_cols(dma_cols), .cmd_wdata(dma_wdata),
        .cmd_done(dma_done), .rd_data(dma_rd_data),
        .sram_we, .sram_row, .sram_col, .sram_data,
        .mem_req, .mem_we, .mem_addr, .mem_wdata, .mem_rdata, .mem_ready
    );

    // ------------------------------------------------------------------ tile SRAMs
    logic [D_MAX-1:0][7:0] q_rd, k_rd, v_rd;

    fa_tile_sram #(.ROWS(BR), .COLS(D_MAX), .W(8)) u_q_sram (
        .clk, .wr_en(sram_we && tgt == TGT_Q), .wr_row(sram_row[RW-1:0]), .wr_col(sram_col[CW-1:0]),
        .wr_data(sram_data), .rd_row(r[RW-1:0]), .rd_data(q_rd));
    fa_tile_sram #(.ROWS(BC), .COLS(D_MAX), .W(8)) u_k_sram (
        .clk, .wr_en(sram_we && tgt == TGT_K), .wr_row(sram_row[$clog2(BC)-1:0]), .wr_col(sram_col[CW-1:0]),
        .wr_data(sram_data), .rd_row(jj[$clog2(BC)-1:0]), .rd_data(k_rd));
    fa_tile_sram #(.ROWS(BC), .COLS(D_MAX), .W(8)) u_v_sram (
        .clk, .wr_en(sram_we && tgt == TGT_V), .wr_row(sram_row[$clog2(BC)-1:0]), .wr_col(sram_col[CW-1:0]),
        .wr_data(sram_data), .rd_row(jj[$clog2(BC)-1:0]), .rd_data(v_rd));

    // ------------------------------------------------------------------ QK MAC + ROW_MAX
    logic               mac_in_valid, mac_out_valid;
    logic signed [23:0] mac_dot;
    fa_qk_mac u_mac (
        .clk, .rst_n, .in_valid(mac_in_valid), .q_vec(q_rd), .k_vec(k_rd), .d(hd[4:0]),
        .out_valid(mac_out_valid), .dot(mac_dot));

    logic signed [47:0] logit_wide;
    logic signed [31:0] logit_val;
    always_comb begin
        logit_wide = 48'(mac_dot) * $signed({39'b0, scale_q(hd[4:0])});
        logit_val  = 32'(logit_wide >>> 8);
    end

    logic               rm_clear, rm_valid;
    logic signed [31:0] rm_max;
    fa_row_max u_rowmax (
        .clk, .rst_n, .clear(rm_clear), .in_valid(rm_valid), .in_val(logit_val), .max_val(rm_max));

    // ------------------------------------------------------------------ online softmax + PV
    logic        sm_init, sm_tile_start, sm_alpha_valid, sm_p_start, sm_p_valid;
    logic [16:0] sm_alpha, sm_p;
    logic signed [63:0] sm_l;
    fa_online_softmax u_softmax (
        .clk, .rst_n, .init(sm_init),
        .tile_start(sm_tile_start), .row(r[RW-1:0]), .tile_max(rm_max),
        .alpha_valid(sm_alpha_valid), .alpha(sm_alpha),
        .p_start(sm_p_start), .logit(score_buf[jj[$clog2(BC)-1:0]]),
        .p_valid(sm_p_valid), .p(sm_p),
        .l_rd_row(nr[RW-1:0]), .l_out(sm_l));

    logic        pv_rescale, pv_acc, pv_done;
    logic signed [63:0] pv_o;
    fa_pv_accum u_pv (
        .clk, .rst_n, .init(sm_init),
        .rescale_en(pv_rescale), .row(r[RW-1:0]), .alpha(sm_alpha),
        .acc_en(pv_acc), .p(sm_p), .v_vec(v_rd), .d(hd[4:0]),
        .op_done(pv_done),
        .rd_row(nr[RW-1:0]), .rd_col(nc[CW-1:0]), .o_out(pv_o));

    // ------------------------------------------------------------------ normalizer
    logic               nrm_start, nrm_done;
    logic signed [31:0] nrm_out;
    fa_output_normalizer u_norm (
        .clk, .rst_n, .start(nrm_start), .o(pv_o), .l(sm_l), .done(nrm_done), .out(nrm_out));

    // ------------------------------------------------------------------ control outputs
    always_comb begin
        dma_cmd_valid = 1'b0; dma_op = OP_RD; dma_addr = '0; dma_wdata = '0;
        dma_rows = '0; dma_cols = hd[4:0];
        mac_in_valid = 1'b0; rm_clear = 1'b0;
        rm_valid = 1'b0;
        sm_init = 1'b0; sm_tile_start = 1'b0; sm_p_start = 1'b0;
        pv_rescale = 1'b0; pv_acc = 1'b0; nrm_start = 1'b0;

        case (state)
            S_DESC_REQ: begin
                dma_cmd_valid = 1'b1; dma_op = OP_RD; dma_addr = desc_base + 32'(dcnt);
            end
            S_Q_REQ: begin
                dma_cmd_valid = 1'b1; dma_op = OP_LOAD; dma_addr = q_ptr + i0 * hd;
                dma_rows = rows_q[3:0];
            end
            S_K_REQ: begin
                dma_cmd_valid = 1'b1; dma_op = OP_LOAD; dma_addr = k_ptr + j0 * hd;
                dma_rows = keys_k[3:0];
            end
            S_V_REQ: begin
                dma_cmd_valid = 1'b1; dma_op = OP_LOAD; dma_addr = v_ptr + j0 * hd;
                dma_rows = keys_k[3:0];
            end
            S_NRM_STORE: begin
                dma_cmd_valid = 1'b1; dma_op = OP_WR;
                dma_addr  = o_ptr + (i0 + nr) * hd + nc;
                dma_wdata = nrm_out_q;
            end
            S_INIT:      sm_init = 1'b1;
            S_SC_CLR:    rm_clear = 1'b1;
            S_SC_FEED:   mac_in_valid = 1'b1;
            S_SC_WAIT:   rm_valid = mac_out_valid;
            S_TILE_ST:   sm_tile_start = 1'b1;
            S_RESC:      pv_rescale = 1'b1;
            S_PR_RD:     sm_p_start = 1'b1;
            S_PR_ACC:    pv_acc = 1'b1;
            S_NRM_START: nrm_start = 1'b1;
            default: ;
        endcase
    end

    // ------------------------------------------------------------------ FSM
    logic [31:0] i0_next, j0_next;
    always_comb begin
        i0_next = i0 + 32'(BR);
        j0_next = j0 + 32'(BC);
    end

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= S_IDLE; done <= 1'b0; err_q <= 1'b0; tgt <= TGT_Q;
            desc_base <= '0; dcnt <= '0;
            q_ptr <= '0; k_ptr <= '0; v_ptr <= '0; o_ptr <= '0; seq_n <= '0; hd <= '0;
            i0 <= '0; j0 <= '0; rows_q <= '0; keys_k <= '0; r <= '0; jj <= '0; nr <= '0; nc <= '0;
            nrm_out_q <= '0;
            for (int i = 0; i < BC; i++) score_buf[i] <= '0;
        end else begin
            done <= 1'b0;
            case (state)
                S_IDLE: if (start) begin
                    desc_base <= desc_addr; dcnt <= '0; err_q <= 1'b0; state <= S_DESC_REQ;
                end

                S_DESC_REQ: state <= S_DESC_WAIT;
                S_DESC_WAIT: if (dma_done) begin
                    case (dcnt)
                        3'd0: q_ptr <= dma_rd_data;
                        3'd1: k_ptr <= dma_rd_data;
                        3'd2: v_ptr <= dma_rd_data;
                        3'd3: o_ptr <= dma_rd_data;
                        3'd4: seq_n <= dma_rd_data;
                        default: hd <= dma_rd_data;
                    endcase
                    if (dcnt == 3'd5) state <= S_CHECK;
                    else begin dcnt <= dcnt + 3'd1; state <= S_DESC_REQ; end
                end

                S_CHECK: begin
                    if (seq_n == 0 || seq_n > MAX_N || hd == 0 || hd > 32'(D_MAX)) state <= S_ERR;
                    else begin
                        i0 <= '0; rows_q <= min32(32'(BR), seq_n); tgt <= TGT_Q; state <= S_Q_REQ;
                    end
                end

                S_Q_REQ:  state <= S_Q_WAIT;
                S_Q_WAIT: if (dma_done) state <= S_INIT;
                S_INIT: begin
                    j0 <= '0; keys_k <= min32(32'(BC), seq_n); tgt <= TGT_K; state <= S_K_REQ;
                end

                S_K_REQ:  state <= S_K_WAIT;
                S_K_WAIT: if (dma_done) begin tgt <= TGT_V; state <= S_V_REQ; end
                S_V_REQ:  state <= S_V_WAIT;
                S_V_WAIT: if (dma_done) begin r <= '0; state <= S_SC_CLR; end

                S_SC_CLR:  begin jj <= '0; state <= S_SC_RD; end
                S_SC_RD:   state <= S_SC_FEED;
                S_SC_FEED: state <= S_SC_WAIT;
                S_SC_WAIT: if (mac_out_valid) begin
                    score_buf[jj[$clog2(BC)-1:0]] <= logit_val;
                    if (jj + 32'd1 == keys_k) state <= S_TILE_ST;
                    else begin jj <= jj + 32'd1; state <= S_SC_RD; end
                end

                S_TILE_ST:    state <= S_ALPHA_WAIT;
                S_ALPHA_WAIT: if (sm_alpha_valid) state <= S_RESC;
                S_RESC:       state <= S_RESC_WAIT;
                S_RESC_WAIT:  if (pv_done) begin jj <= '0; state <= S_PR_RD; end

                S_PR_RD:   state <= S_PR_WAIT;
                S_PR_WAIT: if (sm_p_valid) state <= S_PR_ACC;
                S_PR_ACC:  state <= S_PR_ACCW;
                S_PR_ACCW: if (pv_done) begin
                    if (jj + 32'd1 != keys_k) begin
                        jj <= jj + 32'd1; state <= S_PR_RD;
                    end else if (r + 32'd1 != rows_q) begin
                        r <= r + 32'd1; state <= S_SC_CLR;
                    end else if (j0_next < seq_n) begin
                        j0 <= j0_next; keys_k <= min32(32'(BC), seq_n - j0_next);
                        tgt <= TGT_K; state <= S_K_REQ;
                    end else begin
                        nr <= '0; nc <= '0; state <= S_NRM_START;
                    end
                end

                S_NRM_START: state <= S_NRM_WAIT;
                S_NRM_WAIT:  if (nrm_done) begin nrm_out_q <= nrm_out; state <= S_NRM_STORE; end
                S_NRM_STORE: state <= S_NRM_STWAIT;
                S_NRM_STWAIT: if (dma_done) begin
                    if (nc + 32'd1 != hd) begin
                        nc <= nc + 32'd1; state <= S_NRM_START;
                    end else if (nr + 32'd1 != rows_q) begin
                        nc <= '0; nr <= nr + 32'd1; state <= S_NRM_START;
                    end else if (i0_next < seq_n) begin
                        i0 <= i0_next; rows_q <= min32(32'(BR), seq_n - i0_next);
                        tgt <= TGT_Q; state <= S_Q_REQ;
                    end else begin
                        state <= S_FIN;
                    end
                end

                S_FIN: begin done <= 1'b1; state <= S_IDLE; end
                S_ERR: begin err_q <= 1'b1; done <= 1'b1; state <= S_IDLE; end
                default: state <= S_IDLE;
            endcase
        end
    end

endmodule : fa_engine