Skip to main content

logicaffeine_compile/spatial/decode_engine/rtl/
matvec.rs

1//! The array's matrix-vector step as Verilog: one weight block of two rows against one token-row's activations, from
2//! the GGUF bytes to each row's next f32 ([`crate::spatial::decode_engine::numerics::dot`] under
3//! [`ActScaling::PerBlock`](crate::spatial::decode_engine::numerics::ActScaling::PerBlock), a block at a time).
4//!
5//! The two rows' blocks pass their decoders; their codes meet the token-row's activation words in the processing
6//! elements a 4-bit digit at a time — one digit for a code of four bits or fewer, two for a wider one, the high digit's
7//! partials weighted by 16 — each 16-element unit's partials summed from its two elements; the units' `Σ q·a′`, the
8//! words' `Σ a′`, the decoders' `Σ q` and multipliers feed each row's lane, which adds the block into the row.
9//!
10//! An activation code is 16 bits: a one-word code in `−127..127` crosses as `a + 128`; a two-word code as its signed
11//! high byte plus 128 and its unsigned low byte. `logos_matvec_block #(UNITS, DIGITS)` is the format-free step;
12//! `logos_matvec_<format>` puts the format's decoders in front of it; `logos_matvec_engine_<format>` is one column of
13//! the array: the weight stream's two rows a beat, every token-row's codes held, a token-row a cycle, every
14//! token-row's two rows emitted once their last block is in.
15
16use std::fmt::Write as _;
17
18use super::weights::{decoder_name, DECODERS, UNIT};
19
20/// The matrix-vector step for every format, with the lane, the processing element, the decoders, the f32 units and the
21/// block RAM they are built from.
22pub fn matvec_verilog() -> String {
23    let mut v = format!(
24        "{}\n{}\n{}\n{}\n{}",
25        super::ram::ram_verilog(&act_ram_widths()),
26        super::lane::lane_verilog(),
27        super::array::array_verilog(),
28        super::weights::weights_verilog(),
29        MATVEC_VERILOG
30    );
31    for d in DECODERS {
32        let elems = d.ty.block_len();
33        let units = elems / UNIT;
34        let digits = if d.code_bits > 4 { 2 } else { 1 };
35        let name = d.ty.name().to_lowercase();
36        let bits = 8 * d.ty.block_bytes();
37        writeln!(
38            v,
39            r#"
40// Two rows' {fmt} blocks against one token-row.
41module logos_matvec_{name}(input [{top}:0] blk1, input [{top}:0] blk2, input [{act_top}:0] act, input two, input [7:0] m,
42                    input signed [11:0] e, input [31:0] acc1, input [31:0] acc2, output [31:0] out1, output [31:0] out2);
43  wire [{code_top}:0] code1, code2;
44  wire [{unit_top}:0] c1, n1, c2, n2;
45  wire [15:0] a1, b1, a2, b2;
46  {dec} d1(.blk(blk1), .code(code1), .c(c1), .n(n1), .a(a1), .b(b1));
47  {dec} d2(.blk(blk2), .code(code2), .c(c2), .n(n2), .a(a2), .b(b2));
48  logos_matvec_block #(.UNITS({units}), .DIGITS({digits})) u(.code1(code1), .code2(code2), .c1(c1), .n1(n1), .c2(c2), .n2(n2),
49    .a1(a1), .b1(b1), .a2(a2), .b2(b2), .z(8'd{offset}), .act(act), .two(two), .m(m), .e(e), .acc1(acc1), .acc2(acc2),
50    .out1(out1), .out2(out2));
51endmodule"#,
52            fmt = d.ty.name(),
53            top = bits - 1,
54            act_top = 16 * elems - 1,
55            code_top = 8 * elems - 1,
56            unit_top = 16 * units - 1,
57            dec = decoder_name(d.ty),
58            offset = d.offset,
59        )
60        .unwrap();
61        writeln!(
62            v,
63            r#"
64// A {fmt} matrix against up to T_MAX token-rows: two rows' blocks a beat from the weight stream, each applied to every
65// token-row a cycle, every token-row's two rows emitted after their last block, then the next two rows.
66module logos_matvec_engine_{name} #(parameter T_MAX = 4, NB_MAX = 8)
67  (input clk, input rst,
68   input start, input [15:0] pairs, input [15:0] blocks, input [7:0] tokens, input two, output busy,
69   input act_wr, input [7:0] act_t, input [15:0] act_b, input [{act_top}:0] act_codes, input [7:0] act_m, input [11:0] act_e,
70   input w_valid, output w_ready, input [{top}:0] w_blk1, input [{top}:0] w_blk2,
71   output reg o_valid, output reg [15:0] o_pair, output reg [7:0] o_t, output reg [31:0] o_val1, output reg [31:0] o_val2);
72  reg [15:0] pair, b;
73  reg [7:0] t;
74  // Each token-row's blocks of activations in block RAM: the codes, the scale's m and its exponent.
75  localparam AN = T_MAX * NB_MAX, AA = (AN > 1) ? $clog2(AN) : 1;
76  wire [{act_top} + 24:0] act_word;
77  wire [31:0] act_ra = t * NB_MAX + b, act_wa = act_t * NB_MAX + act_b;
78  {act_ram} #(.D(AN), .A(AA)) act_ram(.clk(clk), .ra(act_ra[AA-1:0]), .rd(act_word), .we(act_wr),
79    .wa(act_wa[AA-1:0]), .wd({{4'd0, act_e, act_m, act_codes}}), .wm({{(({act_top} + 25) / 8){{1'b1}}}}));
80  (* ramstyle = "logic" *) reg [31:0] acc1 [0:T_MAX-1];
81  (* ramstyle = "logic" *) reg [31:0] acc2 [0:T_MAX-1];
82  reg [{top}:0] blk1, blk2;
83  localparam IDLE = 2'd0, WAIT = 2'd1, APPLY = 2'd2, EMIT = 2'd3;
84  reg [1:0] state;
85  assign busy = (state != IDLE);
86  assign w_ready = (state == WAIT);
87  wire [31:0] out1, out2;
88  logos_matvec_{name} u(.blk1(blk1), .blk2(blk2), .act(act_word[{act_top}:0]), .two(two), .m(act_word[{act_top} + 8:{act_top} + 1]),
89                    .e($signed(act_word[{act_top} + 20:{act_top} + 9])), .acc1(acc1[t]), .acc2(acc2[t]), .out1(out1), .out2(out2));
90  integer k;
91  always @(posedge clk) begin
92    o_valid <= 1'b0;
93    if (rst) state <= IDLE;
94    else case (state)
95      IDLE: if (start) begin
96        pair <= 16'd0; b <= 16'd0; t <= 8'd0; state <= WAIT;
97        for (k = 0; k < T_MAX; k = k + 1) begin acc1[k] <= 32'd0; acc2[k] <= 32'd0; end
98      end
99      WAIT: if (w_valid) begin blk1 <= w_blk1; blk2 <= w_blk2; t <= 8'd0; state <= APPLY; end
100      APPLY: begin
101        acc1[t] <= out1; acc2[t] <= out2;
102        if (t + 8'd1 < tokens) t <= t + 8'd1;
103        else if (b + 16'd1 < blocks) begin b <= b + 16'd1; state <= WAIT; end
104        else begin t <= 8'd0; state <= EMIT; end
105      end
106      EMIT: begin
107        o_valid <= 1'b1; o_pair <= pair; o_t <= t; o_val1 <= acc1[t]; o_val2 <= acc2[t];
108        if (t + 8'd1 < tokens) t <= t + 8'd1;
109        else begin
110          for (k = 0; k < T_MAX; k = k + 1) begin acc1[k] <= 32'd0; acc2[k] <= 32'd0; end
111          b <= 16'd0;
112          if (pair + 16'd1 < pairs) begin pair <= pair + 16'd1; state <= WAIT; end
113          else state <= IDLE;
114        end
115      end
116    endcase
117  end
118endmodule"#,
119            fmt = d.ty.name(),
120            top = bits - 1,
121            act_top = 16 * elems - 1,
122            act_ram = super::ram::ram_name(16 * elems + 24),
123        )
124        .unwrap();
125    }
126    v
127}
128
129/// The widths of the columns' activation memories: a block's codes, its scale's m and its exponent.
130pub(super) fn act_ram_widths() -> Vec<usize> {
131    let mut w: Vec<usize> = DECODERS.iter().map(|d| 16 * d.ty.block_len() + 24).collect();
132    w.sort();
133    w.dedup();
134    w
135}
136
137const MATVEC_VERILOG: &str = r#"
138// One weight block of two rows against one token-row: the rows' unit sums from the processing elements, a 4-bit digit
139// at a time, and each row's lane.
140module logos_matvec_block #(parameter UNITS = 16, DIGITS = 2)
141  (input [128*UNITS-1:0] code1, input [128*UNITS-1:0] code2,
142   input [16*UNITS-1:0] c1, input [16*UNITS-1:0] n1, input [16*UNITS-1:0] c2, input [16*UNITS-1:0] n2,
143   input [15:0] a1, input [15:0] b1, input [15:0] a2, input [15:0] b2, input [7:0] z,
144   input [256*UNITS-1:0] act, input two, input [7:0] m, input signed [11:0] e,
145   input [31:0] acc1, input [31:0] acc2, output [31:0] out1, output [31:0] out2);
146  wire [24*UNITS-1:0] bp1_hi, bp2_hi, bp1_lo, bp2_lo;
147  wire [12*UNITS-1:0] bs_hi, bs_lo, bq1, bq2;
148  logos_matvec_partials #(.UNITS(UNITS), .DIGITS(DIGITS)) parts(.code1(code1), .code2(code2), .act(act), .two(two),
149    .p1_hi(bp1_hi), .p2_hi(bp2_hi), .p1_lo(bp1_lo), .p2_lo(bp2_lo), .s_hi(bs_hi), .s_lo(bs_lo), .q1(bq1), .q2(bq2));
150  logos_lane #(.UNITS(UNITS)) l1(.p_hi(bp1_hi), .s_hi(bs_hi), .p_lo(bp1_lo), .s_lo(bs_lo), .q(bq1), .c(c1), .n(n1), .z(z),
151    .a(a1), .b(b1), .m(m), .e(e), .two(two), .acc(acc1), .out(out1));
152  logos_lane #(.UNITS(UNITS)) l2(.p_hi(bp2_hi), .s_hi(bs_hi), .p_lo(bp2_lo), .s_lo(bs_lo), .q(bq2), .c(c2), .n(n2), .z(z),
153    .a(a2), .b(b2), .m(m), .e(e), .two(two), .acc(acc2), .out(out2));
154endmodule
155
156// A block of two rows' codes against one token-row's activation words: each 16-element unit's partial sums for both
157// rows from the processing elements, a 4-bit digit at a time, and the unit's sums of excess codes and of each row's
158// codes, the buses the lanes take.
159module logos_matvec_partials #(parameter UNITS = 16, DIGITS = 2)
160  (input [128*UNITS-1:0] code1, input [128*UNITS-1:0] code2, input [256*UNITS-1:0] act, input two,
161   output [24*UNITS-1:0] p1_hi, output [24*UNITS-1:0] p2_hi, output [24*UNITS-1:0] p1_lo, output [24*UNITS-1:0] p2_lo,
162   output [12*UNITS-1:0] s_hi, output [12*UNITS-1:0] s_lo, output [12*UNITS-1:0] q1, output [12*UNITS-1:0] q2);
163  // Each element's words: the excess high (or only) word, and the low byte of a two-word code.
164  wire [128*UNITS-1:0] w_hi, w_lo;
165  genvar i, u, d;
166  generate
167    for (i = 0; i < 16*UNITS; i = i + 1) begin : word
168      wire signed [15:0] code = act[16*i +: 16];
169      assign w_hi[8*i +: 8] = two ? (code[15:8] + 8'd128) : (code[7:0] + 8'd128);
170      assign w_lo[8*i +: 8] = two ? code[7:0] : 8'd0;
171    end
172    for (u = 0; u < UNITS; u = u + 1) begin : unit
173      // The unit's sums: the excess codes', each row's codes'.
174      reg [11:0] s_hi, s_lo, q1s, q2s;
175      integer k;
176      always @* begin
177        s_hi = 12'd0; s_lo = 12'd0; q1s = 12'd0; q2s = 12'd0;
178        for (k = 0; k < 16; k = k + 1) begin
179          s_hi = s_hi + w_hi[8*(16*u + k) +: 8];
180          s_lo = s_lo + w_lo[8*(16*u + k) +: 8];
181          q1s = q1s + code1[8*(16*u + k) +: 8];
182          q2s = q2s + code2[8*(16*u + k) +: 8];
183        end
184      end
185      // Per word and digit, the unit's two elements of eight: row one's partial in the low lanes, row two's in the
186      // high lanes with its offset restored.
187      wire [23:0] p1_hi, p2_hi, p1_lo, p2_lo;
188      wire [23:0] dig1_hi [0:1], dig2_hi [0:1], dig1_lo [0:1], dig2_lo [0:1];
189      for (d = 0; d < 2; d = d + 1) begin : digit
190        wire [31:0] q1a, q2a, q1b, q2b;
191        for (i = 0; i < 8; i = i + 1) begin : nib
192          assign q1a[4*i +: 4] = (d < DIGITS) ? code1[8*(16*u + i) + 4*d +: 4] : 4'd0;
193          assign q2a[4*i +: 4] = (d < DIGITS) ? code2[8*(16*u + i) + 4*d +: 4] : 4'd0;
194          assign q1b[4*i +: 4] = (d < DIGITS) ? code1[8*(16*u + 8 + i) + 4*d +: 4] : 4'd0;
195          assign q2b[4*i +: 4] = (d < DIGITS) ? code2[8*(16*u + 8 + i) + 4*d +: 4] : 4'd0;
196        end
197        wire [14:0] lo_ha, lo_hb, lo_la, lo_lb;
198        wire signed [16:0] hi_ha, hi_hb, hi_la, hi_lb;
199        logos_pe8 ha(.a(w_hi[8*16*u +: 64]), .q1(q1a), .q2(q2a), .lo(lo_ha), .hi(hi_ha));
200        logos_pe8 hb(.a(w_hi[8*(16*u + 8) +: 64]), .q1(q1b), .q2(q2b), .lo(lo_hb), .hi(hi_hb));
201        logos_pe8 la(.a(w_lo[8*16*u +: 64]), .q1(q1a), .q2(q2a), .lo(lo_la), .hi(hi_la));
202        logos_pe8 lb(.a(w_lo[8*(16*u + 8) +: 64]), .q1(q1b), .q2(q2b), .lo(lo_lb), .hi(hi_lb));
203        assign dig1_hi[d] = {9'd0, lo_ha} + {9'd0, lo_hb};
204        assign dig2_hi[d] = $signed(hi_ha) + $signed(hi_hb) + $signed({9'd0, s_hi, 3'd0});
205        assign dig1_lo[d] = {9'd0, lo_la} + {9'd0, lo_lb};
206        assign dig2_lo[d] = $signed(hi_la) + $signed(hi_lb) + $signed({9'd0, s_lo, 3'd0});
207      end
208      assign p1_hi = dig1_hi[0] + (dig1_hi[1] << 4);
209      assign p2_hi = dig2_hi[0] + (dig2_hi[1] << 4);
210      assign p1_lo = dig1_lo[0] + (dig1_lo[1] << 4);
211      assign p2_lo = dig2_lo[0] + (dig2_lo[1] << 4);
212    end
213  endgenerate
214  // The lanes' unit buses.
215  generate
216    for (u = 0; u < UNITS; u = u + 1) begin : bus
217      assign p1_hi[24*u +: 24] = unit[u].p1_hi;
218      assign p2_hi[24*u +: 24] = unit[u].p2_hi;
219      assign p1_lo[24*u +: 24] = unit[u].p1_lo;
220      assign p2_lo[24*u +: 24] = unit[u].p2_lo;
221      assign s_hi[12*u +: 12] = unit[u].s_hi;
222      assign s_lo[12*u +: 12] = unit[u].s_lo;
223      assign q1[12*u +: 12] = unit[u].q1s;
224      assign q2[12*u +: 12] = unit[u].q2s;
225    end
226  endgenerate
227endmodule
228"#;