Skip to main content

logicaffeine_compile/spatial/decode_engine/rtl/
column.rs

1//! The streaming engine's matrix columns as Verilog: a matrix streamed block-major within groups of row pairs, each
2//! (row, token-row) still the row's blocks added in order, so every result is
3//! [`crate::spatial::decode_engine::numerics::dot`]'s.
4//!
5//! A column takes its channel's weight stream a group of rows at a time and every row pair's block `b` before any
6//! pair's block `b + 1`. A block's activations then serve every pair of the group in turn, and each (pair, token-row)
7//! sum comes round once a group's pairs and token-rows have passed, which is what lets the block step be pipelined
8//! without reordering a row's sum.
9//!
10//! What every core of a column array would otherwise compute alike is computed once beside them. A beat (a pair's two
11//! blocks) is decoded once, by `logos_column_decode_<set>`, into its blocks' codes, their units' multipliers and offset
12//! terms as bytes, their scales, and their units' sums of codes; a token-row's activation block is written to block RAM
13//! as each activation word's excess codes and their units' sums (`logos_column_act_word`), so a read serves every core.
14//!
15//! `logos_column_core_<set>` is a column's datapath: it rings decoded beats — the one its issues run at the ring's head
16//! and those queued behind it, each put in as its feed decodes it — and takes the next by moving the head, so a step
17//! moves a pointer and never a beat. It runs each issue of the beat at the head against a token-row as passes, a clock
18//! each: one per 4-bit digit of the format's codes and, with two activation words, per
19//! word, each pass the beat's codes and the token-row's word through one digit and one word of systolic processing
20//! elements (`logos_matvec_pass_p`, [`PASS_LATENCY`] clocks), its partials summed in their places as they leave them,
21//! the pass's flags and the fields its lanes take delayed beside them. The issue's last pass hands the sums to two pipelined
22//! lanes (`logos_lane_value_p`), and the value is added into its sum through the pipelined `logos_fadd_p`; a sum's last
23//! block leaves the add as the sum's emission while its place goes back to +0 for the next group. An issue whose sum is
24//! still in the add waits at its last pass.
25//!
26//! `logos_column_<set>` is one core with its own sequencer over groups of row pairs: it takes the next beat the clock
27//! it issues the last token-row of the one it holds, so a block step of fewer passes than the add has stages takes
28//! [`FADD_LATENCY`] + 1 clocks and a larger one a clock a pass ([`column_clocks`]).
29//!
30//! `logos_column_array_<set>` is the streaming engine's matvec: `CH · N` cores, `N` on each memory channel, one row
31//! pair each of their channel's tile of a stripe ([`super::super::image::ImageLayout::stripe`]), every core running the
32//! same pass of the same token-row of the same block step each clock against one activation read. A step issues when
33//! every core whose channel's tile holds its pair has its beat; each channel's `logos_column_feed_<set>` puts its cores'
34//! decoded beats into their rings as it deals them, and the cores take the next step's the clock the last token-row
35//! issues ([`column_array_clocks`]).
36//!
37//! A set with octets ([`ColumnSet::octets`]) takes a Q8_0 matrix's blocks eight at a time: a beat holds a row's eight
38//! blocks (`logos_dec_q8_0x8`, 256 elements in 272 bytes), the processing elements run every unit of them in one issue,
39//! and the array keeps each issue's sums in a slot of one of two buffers instead of handing them to the lanes. A full
40//! buffer's blocks then leave for the lanes a block a clock — every token-row's block 0, then every token-row's block 1,
41//! and so on — each as the lone Q8_0 block an issue carries otherwise (its two units the lane's first two, its scale and
42//! its activation block's scale), and each the clock no add still holds its sum, so a sum's blocks are added in order.
43//! While one buffer's blocks leave, the next step's issues fill the other: a Q8_0 block costs the lanes a clock instead
44//! of an issue of two passes.
45//!
46//! `logos_matvec_pass_x2` ([`matvec_pass_x2_verilog`]) is two cores' passes on one set of processing elements in a
47//! clock of twice the cores' (`clk2`, its rising edges on `clk`'s and between them): the cores of a channel run in
48//! lockstep against one activation word, so the elements take core a's codes with the word at `clk2`'s edge between
49//! `clk`'s and core b's at `clk`'s next, and each core's partials leave as `logos_matvec_pass_p` gives them.
50
51use std::fmt::Write as _;
52
53use super::array::{CHAIN, PE8_P_LATENCY};
54use super::fp32::FADD_LATENCY;
55use super::lane::LANE_VALUE_LATENCY;
56use crate::model::quant::GgmlType;
57
58use super::super::numerics::Words;
59use super::weights::{decoder_name, Decoder, DECODERS, UNIT};
60
61/// Clocks from a pass's operands to its partial sums in `logos_matvec_pass_p`: the systolic elements'.
62pub const PASS_LATENCY: usize = PE8_P_LATENCY;
63
64/// Clocks from a block's issue to its value at the column's add: the activations' read (the codes' register beside it),
65/// the processing elements, the lanes' input registers, and the lane.
66pub const COLUMN_VALUE_LATENCY: usize = LANE_VALUE_LATENCY + 2 + PASS_LATENCY;
67
68/// The passes an issue of a block of `ty` takes: one per 4-bit digit of its codes, for each activation word.
69pub fn column_passes(ty: GgmlType, two: bool) -> usize {
70    let d = DECODERS.iter().find(|d| d.ty == ty).unwrap_or_else(|| panic!("no decoder reads {}", ty.name()));
71    (if d.code_bits > 4 { 2 } else { 1 }) * (if two { 2 } else { 1 })
72}
73
74/// The job's format a clock behind it, a module's own copy (`fmt_q`, never merged with another module's): a job holds its
75/// format from its start to its end and no module reads it in the job's first clock, so the job's word drives a
76/// register in each module and none of their datapaths' selects.
77pub(crate) const FMT_COPY: &str = "  (* dont_merge, maxfan = 256 *) reg [3:0] fmt_q;\n  always @(posedge clk) fmt_q <= fmt;\n";
78
79/// The clocks from the edge that takes `start` to the edge that raises the column's last sum, for a stream that offers
80/// a beat whenever the column is ready and issues of `passes` passes: a clock to take the first beat; a block step
81/// (every pair's beat of one block against every token-row) of `pairs · tokens` issues, a pass a clock, its first issue's
82/// last pass at least [`FADD_LATENCY`] + 1 clocks after the step before's, waiting for the sum that one left in the add;
83/// then the last issue's value through the lane and the add.
84pub fn column_clocks(groups: usize, pairs: usize, blocks: usize, tokens: usize, passes: usize) -> usize {
85    let (steps, issues) = (groups * blocks, pairs * tokens);
86    (steps - 1) * (passes * issues).max(FADD_LATENCY + 1) + passes * issues + 1 + COLUMN_VALUE_LATENCY + FADD_LATENCY
87}
88
89/// The clocks from the edge that takes `start` to the edge that raises the array's last sums, for streams that offer
90/// every core its beat whenever it is ready: a stripe's block step is one pair a core against every token-row, so the
91/// array runs as one column of one pair a group over the stripes.
92pub fn column_array_clocks(stripes: usize, blocks: usize, tokens: usize, passes: usize) -> usize {
93    column_clocks(stripes, 1, blocks, tokens, passes)
94}
95
96/// [`column_array_clocks`] for an octet job ([`ColumnSet::octets`]): `octets` of a row's eight blocks each a stripe's
97/// block step. A step's issues run a pass a clock once the step before has issued its last, each into its buffer's slot,
98/// the first waiting for its buffer to be free; a buffer's blocks are asked for from four clocks after its last issue,
99/// block-major, a clock each and no sooner than [`FADD_LATENCY`] + 1 clocks after its sum's block before, and the
100/// buffer is free the clock after its last; the last block's sum leaves as an issue's would.
101pub fn column_array_octet_clocks(stripes: usize, octets: usize, tokens: usize, passes: usize) -> usize {
102    column_array_octet_lane_clocks(stripes, octets, tokens, passes, 1)
103}
104
105/// [`column_array_octet_clocks`] for a set of `lanes` octet lanes a row ([`ColumnSet::with_octet_lanes`]): a buffer's
106/// blocks asked for `lanes` at once — a token-row's next `lanes` — a clock each group, no sooner than `lanes` adds and a
107/// clock after its sum's group before, the last group's sum leaving through the chain's `lanes` adds.
108pub fn column_array_octet_lane_clocks(stripes: usize, octets: usize, tokens: usize, passes: usize, lanes: usize) -> usize {
109    let gap = lanes * FADD_LATENCY + 1;
110    let mut free = [0usize; 2];
111    let mut fin = 0usize;
112    let mut emitted = 0usize;
113    let mut last: Vec<Option<usize>> = vec![None; tokens];
114    for k in 0..stripes * octets {
115        let first = (fin + passes).max(free[k % 2]);
116        fin = first + passes * (tokens - 1);
117        let mut at = (fin + 4).max(emitted + 1);
118        for _group in 0..8 / lanes {
119            for sum in last.iter_mut() {
120                if let Some(before) = *sum {
121                    at = at.max(before + gap);
122                }
123                *sum = Some(at);
124                emitted = at;
125                at += 1;
126            }
127        }
128        free[k % 2] = emitted + 1;
129    }
130    emitted + 1 + COLUMN_VALUE_LATENCY + lanes * FADD_LATENCY
131}
132
133/// A set of formats one column is built for, under one name, and the activation words its jobs run at: built for one
134/// word, every module of the set has no `two` and its lanes are one word wide. With `octets`, a Q8_0 matrix's blocks
135/// come eight to one of the column's (its octets, [`ColumnSet::block_len`]): an issue's units hold a row's eight
136/// blocks, and the lane takes them one a clock, each still a block of its own. With a `slot` past 1 every block comes
137/// in the image padded with zeros to a multiple of `slot` bytes (`ImageLayout::slot`), so the feed gathers its beats
138/// `slot` bytes at a time. With `lanes` past 1 an octet's blocks leave for `lanes` lanes a row a clock, a token-row's
139/// next `lanes` blocks at once: the row's full lane and `lanes − 1` lanes of one scale (`logos_lane_slim_p`), their
140/// values added into the row's sum in block order through a chain of `lanes` adds.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct ColumnSet {
143    pub name: String,
144    pub formats: Vec<GgmlType>,
145    pub words: Words,
146    pub octets: bool,
147    pub slot: usize,
148    pub lanes: usize,
149    pub pe_pairs: bool,
150    pub token_pairs: bool,
151}
152
153impl ColumnSet {
154    /// The set `formats` under `name`, for jobs at `words`.
155    pub fn new(name: &str, formats: &[GgmlType], words: Words) -> ColumnSet {
156        ColumnSet { name: name.to_string(), formats: formats.to_vec(), words, octets: false, slot: 1, lanes: 1, pe_pairs: false, token_pairs: false }
157    }
158
159    /// The set with its Q8_0 matrices' blocks taken eight at a time.
160    pub fn with_octets(self) -> ColumnSet {
161        assert!(self.formats.contains(&GgmlType::Q8_0), "a set without Q8_0 has no octets to take");
162        ColumnSet { octets: true, ..self }
163    }
164
165    /// The set with an octet's blocks leaving for `lanes` lanes a row a clock: 1, 2, 4 or 8, a divisor of an octet's
166    /// eight blocks.
167    pub fn with_octet_lanes(self, lanes: usize) -> ColumnSet {
168        assert!(self.octets, "octet lanes take an octet's blocks: a set with octets");
169        assert!(matches!(lanes, 1 | 2 | 4 | 8), "{lanes} lanes a row: a divisor of an octet's eight blocks");
170        ColumnSet { lanes, ..self }
171    }
172
173    /// The set with its processing elements in a clock of twice its cores' (the array's `clk2`), each set of them
174    /// taking two issues' passes a clock. An array's cores run in lockstep against one activation word, so a pair of
175    /// cores' elements take one core's codes and then the other's (`logos_matvec_pass_x2`); with token pairs
176    /// ([`ColumnSet::with_token_pairs`]) each core's own elements take its beat's codes against the pair's first word
177    /// and then its second (`logos_matvec_pass_x2t`). Half the elements for the same passes, each core's clocks as they
178    /// were.
179    pub fn with_pe_pairs(self) -> ColumnSet {
180        ColumnSet { pe_pairs: true, ..self }
181    }
182
183    /// The set with each issue of its array's held beat run against two token-rows a clock, token-rows `t` and `t + 1`
184    /// for even `t` (`token_pairs`): each core takes two activation words a clock, one from each half of its channel's
185    /// copy of the activations (a word a token pair, written a half at a time under the mask), through processing
186    /// elements and lanes of each, the beat and the issue's flags and fields shared; a sum's place is a token pair's, and
187    /// a step issues ⌈tokens / 2⌉ times ([`ColumnSet::issues`]). The ring, the core's control and its channel's feed
188    /// then serve twice the multiplies. An odd count's last pair runs its second token-row on whatever its half holds,
189    /// and the array's consumer takes only the token-rows the job has. With [`ColumnSet::pe_pairs`] the pair's two words
190    /// run on one set of the core's elements in clk2. With [`ColumnSet::lanes`] past one each token-row of the pair has
191    /// its octet lanes.
192    pub fn with_token_pairs(self) -> ColumnSet {
193        ColumnSet { token_pairs: true, ..self }
194    }
195
196    /// The token-rows one issue of the set's array runs against: two with token pairs, one otherwise.
197    pub fn issue_rows(&self) -> usize {
198        if self.token_pairs { 2 } else { 1 }
199    }
200
201    /// The issues a block step of `tokens` token-rows takes: ⌈tokens / [`ColumnSet::issue_rows`]⌉.
202    pub fn issues(&self, tokens: usize) -> usize {
203        tokens.div_ceil(self.issue_rows())
204    }
205
206    /// The set with its blocks in slots of `slot` bytes: a power of two a line's 64 bytes are a whole number of.
207    pub fn with_slots(self, slot: usize) -> ColumnSet {
208        assert!(slot.is_power_of_two() && slot <= 64, "a slot of {slot} bytes: a power of two no wider than a line");
209        ColumnSet { slot, ..self }
210    }
211
212    /// Whether `ty`'s blocks come in octets.
213    pub fn octet(&self, ty: GgmlType) -> bool {
214        self.octets && ty == GgmlType::Q8_0
215    }
216
217    /// The elements of one of the column's blocks of `ty`: its format's block, or an octet's 256.
218    pub fn block_len(&self, ty: GgmlType) -> usize {
219        if self.octet(ty) { 8 * ty.block_len() } else { ty.block_len() }
220    }
221
222    /// The bytes of one of the column's blocks of `ty`: its format's block, or an octet's 272.
223    pub fn block_bytes(&self, ty: GgmlType) -> usize {
224        if self.octet(ty) { 8 * ty.block_bytes() } else { ty.block_bytes() }
225    }
226
227    /// The bytes one of the column's blocks of `ty` takes in the image: its block's, then zeros to the set's slot.
228    pub fn slot_bytes(&self, ty: GgmlType) -> usize {
229        self.block_bytes(ty).next_multiple_of(self.slot)
230    }
231
232    /// A hybrid engine's set: `formats` in their ggml ids' order under the name `step`, so one build's set and the
233    /// programs written for it place every format alike.
234    pub fn hybrid(formats: &[GgmlType], words: Words) -> ColumnSet {
235        let mut f = formats.to_vec();
236        f.sort_by_key(|t| t.id());
237        f.dedup();
238        ColumnSet::new("step", &f, words)
239    }
240
241    /// The set as a program and a build name it: its formats in order and its word count, `Q8_0+Q4_K:1`, a format
242    /// taken in octets `Q8_0x8`, blocks in slots of 16 bytes `@16`.
243    pub fn signature(&self) -> String {
244        let names: Vec<String> = self.formats.iter().map(|&t| if self.octet(t) { format!("{}x8", t.name()) } else { t.name().to_string() }).collect();
245        let slot = if self.slot > 1 { format!("@{}", self.slot) } else { String::new() };
246        format!("{}:{}{slot}", names.join("+"), self.word_count())
247    }
248
249    /// The elements of the set's widest block.
250    pub fn elems(&self) -> usize {
251        self.formats.iter().map(|&t| self.block_len(t)).max().expect("a set of formats")
252    }
253
254    /// The activation words a job of the set may run at.
255    pub fn word_count(&self) -> usize {
256        if self.words == Words::Two {
257            2
258        } else {
259            1
260        }
261    }
262
263    /// Bits of one decoded block: its codes, its units' multipliers and offset terms as bytes, its two scales and its
264    /// units' sums of codes ([`ColumnSet::decoded_fields`]), and with octets an octet's seven scales past its first; a
265    /// decoded beat is two, block one's below block two's.
266    pub fn decoded_bits(&self) -> usize {
267        let e = self.elems();
268        8 * e + 28 * (e / UNIT) + 32 + if self.octets { 7 * 16 } else { 0 }
269    }
270
271    /// Where each field of a decoded block starts: the codes, the multipliers, the offset terms, the two scales, the
272    /// units' sums of codes, and an octet's seven scales past its first.
273    pub fn decoded_fields(&self) -> DecodedFields {
274        let (e, u) = (self.elems(), self.elems() / UNIT);
275        let q = 8 * e + 16 * u + 32;
276        DecodedFields { code: 0, c: 8 * e, n: 8 * e + 8 * u, a: 8 * e + 16 * u, b: 8 * e + 16 * u + 16, q, s7: q + 12 * u }
277    }
278
279    /// The activation scales a word holds: one, or with octets an octet's eight blocks'.
280    pub fn act_slots(&self) -> usize {
281        if self.octets { 8 } else { 1 }
282    }
283
284    /// Bits of an activation word in block RAM, whole bytes: each word's excess codes, then each word's units' sums of
285    /// them, then each scale's significand and exponent, 24 bits a scale ([`ColumnSet::act_fields`]).
286    pub fn act_bits(&self) -> usize {
287        let (e, w) = (self.elems(), self.word_count());
288        (w * (8 * e + 12 * (e / UNIT)) + 20 + 24 * (self.act_slots() - 1)).div_ceil(8) * 8
289    }
290
291    /// Where each field of an activation word starts: word `k`'s excess codes at `excess + k·8E`, its units' sums at
292    /// `sums + k·12U`, scale `j`'s significand at `m + 24j` and its exponent at `e + 24j`.
293    pub fn act_fields(&self) -> ActFields {
294        let (e, w) = (self.elems(), self.word_count());
295        let sums = w * 8 * e;
296        let m = sums + w * 12 * (e / UNIT);
297        ActFields { excess: 0, sums, m, e: m + 8 }
298    }
299}
300
301/// The bit offsets of a decoded block's fields.
302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
303pub struct DecodedFields {
304    pub code: usize,
305    pub c: usize,
306    pub n: usize,
307    pub a: usize,
308    pub b: usize,
309    pub q: usize,
310    pub s7: usize,
311}
312
313/// The bit offsets of an activation word's fields.
314#[derive(Clone, Copy, Debug, PartialEq, Eq)]
315pub struct ActFields {
316    pub excess: usize,
317    pub sums: usize,
318    pub m: usize,
319    pub e: usize,
320}
321
322/// The columns for every format, each its own set for both word counts, with the block steps, decoders, lanes, f32
323/// units and block RAM they are built from.
324pub fn column_verilog() -> String {
325    let sets: Vec<ColumnSet> = DECODERS.iter().map(|d| ColumnSet::new(&d.ty.name().to_lowercase(), &[d.ty], Words::Two)).collect();
326    columns_verilog(&sets)
327}
328
329/// The columns of each set, with the modules they are built from: `logos_column_decode_<name>`,
330/// `logos_column_core_<name>`, `logos_column_<name>`, `logos_column_array_<name>` and `logos_column_feed_<name>`. A
331/// set's core holds a block of any of its formats, its widths the set's widest; with more than one format each module
332/// takes the job's format as `fmt`, its place in the set, a block of fewer elements than the widest running with the
333/// units past it zero.
334pub fn columns_verilog(sets: &[ColumnSet]) -> String {
335    let mut v = String::from(super::ram::RAMSTYLE);
336    v.push_str(&super::matvec::matvec_verilog());
337    let mut widths: Vec<usize> = sets.iter().map(ColumnSet::act_bits).collect();
338    widths.extend(sets.iter().filter(|s| s.token_pairs).map(|s| 2 * s.act_bits()));
339    widths.sort();
340    widths.dedup();
341    v.push_str(&super::ram::bram_verilog(&widths));
342    v.push_str(super::ram::delay_verilog());
343    v.push_str(&MATVEC_PASS.replace("@PE@", &PASS_LATENCY.to_string()));
344    v.push_str(ACT_STAGGER);
345    v.push_str(ACT_WORD);
346    for set in sets {
347        let ColumnSet { name, formats, words, .. } = set;
348        let decs: Vec<Coded> = formats
349            .iter()
350            .map(|&ty| Coded {
351                dec: DECODERS.iter().find(|d| d.ty == ty).unwrap_or_else(|| panic!("no decoder reads {}", ty.name())),
352                octet: set.octet(ty),
353                slot: set.slot,
354            })
355            .collect();
356        let elems = set.elems();
357        let bits = 8 * decs.iter().map(Coded::bytes).max().unwrap();
358        let units = elems / UNIT;
359        let db = set.decoded_bits();
360        let f = set.decoded_fields();
361        let a = set.act_fields();
362        let multi = decs.len() > 1;
363        let wide = *words == Words::Two;
364        let fmt_in = if multi { ", input [3:0] fmt" } else { "" };
365        let fmt_pass = if multi { ".fmt(fmt), " } else { "" };
366        let fmt_copy = if multi { FMT_COPY } else { "" };
367        let fmt_dec = if multi { ".fmt(fmt_q), " } else { "" };
368        let two_in = if wide { ", input two" } else { "" };
369        let two_pass = if wide { ".two(two), " } else { "" };
370        let two_decl = if wide { "" } else { "  wire two = 1'b0;\n" };
371        let what = decs.iter().map(Coded::name).collect::<Vec<_>>().join("/");
372        let (code_top, bus12, bus24) = (8 * elems - 1, 12 * units - 1, 24 * units - 1);
373        let oct = core_octets(set);
374        let lane = lane_fields(set);
375        // Cores share elements in pairs only without token pairs: with them, each core's own elements run its pair's two
376        // words in clk2.
377        let paired = set.pe_pairs && !set.token_pairs;
378        let arr: std::collections::HashMap<&str, String> = array_octets(set).into_iter().collect();
379        // The pass's word of the activations: its excess codes (chosen in the column by the word registered beside the
380        // activation read, for the stagger) and their unit sums.
381        let (st_sel, s_sel) = if wide {
382            (
383                format!("word_a ? act_word[{}:{}] : act_word[{}:{}]", a.excess + 16 * elems - 1, a.excess + 8 * elems, a.excess + code_top, a.excess),
384                format!("s1_word ? act_word[{}:{}] : act_word[{}:{}]", a.sums + 24 * units - 1, a.sums + 12 * units, a.sums + bus12, a.sums),
385            )
386        } else {
387            (format!("act_word[{}:{}]", a.excess + code_top, a.excess), format!("act_word[{}:{}]", a.sums + bus12, a.sums))
388        };
389        writeln!(
390            v,
391            r#"
392// A {what} beat decoded: each block's codes, its units' multipliers and offset terms as bytes, its two scales and its
393// units' sums of codes (sixteen codes in a balanced tree of four adds), block one's fields below block two's.
394module logos_column_decode_{name}(input [{top}:0] blk1, input [{top}:0] blk2{fmt_in}, output [{beat_top}:0] beat);
395  function [11:0] sum16(input [127:0] c);
396    reg [8:0] a0, a1, a2, a3, a4, a5, a6, a7;
397    reg [9:0] b0, b1, b2, b3;
398    reg [10:0] d0, d1;
399    begin
400      a0 = c[7:0] + c[15:8]; a1 = c[23:16] + c[31:24]; a2 = c[39:32] + c[47:40]; a3 = c[55:48] + c[63:56];
401      a4 = c[71:64] + c[79:72]; a5 = c[87:80] + c[95:88]; a6 = c[103:96] + c[111:104]; a7 = c[119:112] + c[127:120];
402      b0 = a0 + a1; b1 = a2 + a3; b2 = a4 + a5; b3 = a6 + a7;
403      d0 = b0 + b1; d1 = b2 + b3;
404      sum16 = d0 + d1;
405    end
406  endfunction
407{decode}  genvar u;
408  generate
409    for (u = 0; u < {units}; u = u + 1) begin : unit
410      wire [11:0] q1 = sum16(code1_d[128*u +: 128]);
411      wire [11:0] q2 = sum16(code2_d[128*u +: 128]);
412      assign beat[{fc} + 8*u +: 8] = c1_d[16*u +: 8];
413      assign beat[{fn} + 8*u +: 8] = n1_d[16*u +: 8];
414      assign beat[{fq} + 12*u +: 12] = q1;
415      assign beat[{db} + {fc} + 8*u +: 8] = c2_d[16*u +: 8];
416      assign beat[{db} + {fn} + 8*u +: 8] = n2_d[16*u +: 8];
417      assign beat[{db} + {fq} + 12*u +: 12] = q2;
418    end
419  endgenerate
420  assign beat[{code_top}:0] = code1_d;
421  assign beat[{db} + {code_top}:{db}] = code2_d;
422  assign beat[{fa} +: 16] = a1_d;
423  assign beat[{fb} +: 16] = b1_d;
424  assign beat[{db} + {fa} +: 16] = a2_d;
425  assign beat[{db} + {fb} +: 16] = b2_d;
426{beat_s7}endmodule
427
428// A {what} column's datapath: the decoded beats it rings (each put in on `put`, the beat at the ring's head the one it
429// holds, the next taken on `load` by moving the head), and each pass of an issue of the beat held, the beat and
430// the pass's word of the activations (`act_word`, the clock after `pass`: the word's excess codes and their unit sums;
431// its excess codes staggered for the elements in `act_st`) through the systolic processing elements of the pass's code
432// digit, its partials summed into its word's in their place
433// from the issue's `first` pass as they leave the elements PL clocks on, with the pass's flags and its lanes' fields
434// delayed to meet them. The issue's last pass (`issue`) hands the sums to the two rows' lanes and the pipelined add. The
435// value of the issue LAT clocks before lands on the sum `land` names; the sum leaves the add ADD clocks later as the sum
436// `done` names and is written back, or, its last block (`last`), emitted and its place reset to +0. `clear` resets every
437// sum to +0.
438module logos_column_core_{name} #(parameter SUMS = 4, SW = 2, RING = 2)
439  (input clk, input rst, input clear{two_in}{fmt_in},
440   input put, input load, input [{beat_top}:0] w_beat,
441   input pass, input digit, input word, input first, input issue, input [{act_top}:0] act_word, input [{code_top}:0] act_st,
442   input [SW-1:0] land, input [SW-1:0] done, input last{core_octet_ports},
443   output reg o_valid, output reg [31:0] o_val1, output reg [31:0] o_val2);
444  localparam LAT = {lat}, ADD = {add}, TAGS = LAT + ADD;
445{two_decl}{fmt_copy}{zsel}  // The ring: RING places, the beat held at its head and the beats queued behind it, a beat put in at the place after the
446  // last; a step's next beat is the head's next place, so taking it moves the head and no beat.
447  localparam RA = (RING > 2) ? 2 : 1;
448  localparam [RA-1:0] R0 = 0, R1 = 1, RLAST = RING - 1;
449  `LOGOS_RAMSTYLE_ASYNC(RING) reg [{beat_top}:0] ring [0:RING-1];
450  // The ring's places address every MLAB of the beat's width: registers Quartus copies beside each group of them.
451  (* maxfan = 64 *) reg [RA-1:0] rp, wp;
452  always @(posedge clk) begin
453    if (put) begin ring[wp] <= w_beat; wp <= (wp == RLAST) ? R0 : wp + R1; end
454    if (load) rp <= (rp == RLAST) ? R0 : rp + R1;
455    if (rst) begin rp <= R0; wp <= R1; end
456  end
457  wire [{beat_top}:0] held = ring[rp];
458  // The pass's flags, registered beside its word of the activations; its codes go to the elements from the beat held,
459  // a clock before the word.
460  reg s1_pass, s1_digit, s1_word, s1_first;
461  always @(posedge clk) begin s1_pass <= pass; s1_digit <= digit; s1_word <= word; s1_first <= first; end
462  wire [{code_top}:0] code1 = held[{code_top}:0], code2 = held[{db} + {code_top}:{db}];
463  wire [{bus12}:0] s_act = {s_sel};
464  wire [{bus24}:0] w_p1, w_p2;
465  logos_matvec_pass_p #(.UNITS({units})) parts(.clk(clk), .code1(code1), .code2(code2), .w(act_st), .s(s_act), .digit(digit),
466    .p1(w_p1), .p2(w_p2));
467  // The pass's flags and the fields of its beat and activations the lanes and the slots take, PL clocks behind with its
468  // partials (the beat's taken from the beat held at the pass, a clock before the activations'); the pass flag in
469  // registers the reset clears.
470  localparam PL = {pl}, LB = {lb_bits}, LA = {la_bits};
471  reg [PL-1:0] d_pass, d_digit;
472  reg [PL-2:0] d_word, d_first;
473  // The word and first flags' last places drive every unit's sums: registers Quartus copies a copy each 256 loads.
474  (* maxfan = 256 *) reg p_word, p_first;
475  wire [LB-1:0] lb_in = {{{lb_in}}};
476  wire [LA-1:0] la_in = {{{la_in}}};
477  wire [LB-1:0] lfb;
478  wire [LA-1:0] lfa;
479  logos_delay #(.W(LB), .D(PL + 1)) lbd(.clk(clk), .d(lb_in), .q(lfb));
480  logos_delay #(.W(LA), .D(PL)) lad(.clk(clk), .d(la_in), .q(lfa));
481  always @(posedge clk) begin
482    d_pass <= rst ? {{PL{{1'b0}}}} : {{d_pass[PL-2:0], s1_pass}};
483    d_digit <= {{d_digit[PL-2:0], s1_digit}};
484    d_word <= (d_word << 1) | {{{{(PL-2){{1'b0}}}}, s1_word}}; p_word <= d_word[PL-2];
485    d_first <= (d_first << 1) | {{{{(PL-2){{1'b0}}}}, s1_first}}; p_first <= d_first[PL-2];
486  end
487  wire p_pass = d_pass[PL-1], p_digit = d_digit[PL-1];
488{lf_out}  // The pass's partials, its digit's in their place, added to its word's sums so far (none at an issue's first pass).
489  reg [{bus24}:0] a1_hi, a2_hi, a1_lo, a2_lo, t1_hi, t2_hi, t1_lo, t2_lo;
490  reg [{bus12}:0] as_hi, as_lo, ts_hi, ts_lo;
491  reg [23:0] sh1, sh2, b1_hi, b2_hi, b1_lo, b2_lo;
492  integer u;
493  always @* begin
494    for (u = 0; u < {units}; u = u + 1) begin
495      sh1 = p_digit ? {{w_p1[24*u +: 20], 4'd0}} : w_p1[24*u +: 24];
496      sh2 = p_digit ? {{w_p2[24*u +: 20], 4'd0}} : w_p2[24*u +: 24];
497      b1_hi = p_first ? 24'd0 : a1_hi[24*u +: 24];
498      b2_hi = p_first ? 24'd0 : a2_hi[24*u +: 24];
499      b1_lo = p_first ? 24'd0 : a1_lo[24*u +: 24];
500      b2_lo = p_first ? 24'd0 : a2_lo[24*u +: 24];
501      t1_hi[24*u +: 24] = p_word ? b1_hi : b1_hi + sh1;
502      t2_hi[24*u +: 24] = p_word ? b2_hi : b2_hi + sh2;
503      t1_lo[24*u +: 24] = p_word ? b1_lo + sh1 : b1_lo;
504      t2_lo[24*u +: 24] = p_word ? b2_lo + sh2 : b2_lo;
505    end
506    ts_hi = p_word ? (p_first ? {{({units}*12){{1'b0}}}} : as_hi) : f_s;
507    ts_lo = p_word ? f_s : (p_first ? {{({units}*12){{1'b0}}}} : as_lo);
508  end
509  always @(posedge clk) if (p_pass) begin
510    a1_hi <= t1_hi; a2_hi <= t2_hi; a1_lo <= t1_lo; a2_lo <= t2_lo; as_hi <= ts_hi; as_lo <= ts_lo;
511  end
512{core_octets}  // The lanes' inputs: the sums over the issue's passes, registered the clock after its last{lanes_from}.
513  reg [{bus24}:0] p1_hi, p2_hi, p1_lo, p2_lo;
514  reg [{bus12}:0] s_hi, s_lo, q1, q2;
515  reg [{unit_top}:0] lc1, ln1, lc2, ln2;
516  reg [15:0] la1, lb1, la2, lb2;
517  reg [7:0] lm;
518  reg signed [11:0] le;
519  always @(posedge clk) begin
520{octet_lanes}    p1_hi <= t1_hi; p2_hi <= t2_hi; p1_lo <= t1_lo; p2_lo <= t2_lo;
521    s_hi <= ts_hi; s_lo <= ts_lo; q1 <= f_q1; q2 <= f_q2;
522    lc1 <= f_c1; ln1 <= f_n1; lc2 <= f_c2; ln2 <= f_n2;
523    la1 <= f_a1; lb1 <= f_b1; la2 <= f_a2; lb2 <= f_b2;
524    lm <= f_m;
525    le <= f_e;
526{octet_lanes_end}  end
527  wire [31:0] value1, value2;
528  logos_lane_value_p #(.UNITS({units}), .WORDS({words_n})) l1(.clk(clk), .p_hi(p1_hi), .s_hi(s_hi), .p_lo(p1_lo), .s_lo(s_lo),
529    .q(q1), .c(lc1), .n(ln1), .z(z), .a(la1), .b(lb1), .m(lm), .e(le), .two(two), .value(value1));
530  logos_lane_value_p #(.UNITS({units}), .WORDS({words_n})) l2(.clk(clk), .p_hi(p2_hi), .s_hi(s_hi), .p_lo(p2_lo), .s_lo(s_lo),
531    .q(q2), .c(lc2), .n(ln2), .z(z), .a(la2), .b(lb2), .m(lm), .e(le), .two(two), .value(value2));
532  // The two rows' sums: a value added as it lands, the sum written as it leaves the add.
533  (* ramstyle = "logic" *) reg [31:0] acc1 [0:SUMS-1];
534  (* ramstyle = "logic" *) reg [31:0] acc2 [0:SUMS-1];
535  wire [31:0] sum1, sum2;
536  logos_fadd_p add1(.clk(clk), .a(acc1[land]), .b(value1), .y(sum1));
537  logos_fadd_p add2(.clk(clk), .a(acc2[land]), .b(value2), .y(sum2));
538  // This core's issues in flight.
539  reg alive [0:TAGS-1];
540  integer k;
541  always @(posedge clk) begin
542    o_valid <= 1'b0;
543    alive[0] <= issue;
544    for (k = 1; k < TAGS; k = k + 1) alive[k] <= alive[k-1];
545    if (alive[TAGS-1]) begin
546      acc1[done] <= last ? 32'd0 : sum1;
547      acc2[done] <= last ? 32'd0 : sum2;
548      if (last) begin o_valid <= 1'b1; o_val1 <= sum1; o_val2 <= sum2; end
549    end
550    if (rst) begin
551      for (k = 0; k < TAGS; k = k + 1) alive[k] <= 1'b0;
552    end else if (clear) begin
553      for (k = 0; k < SUMS; k = k + 1) begin acc1[k] <= 32'd0; acc2[k] <= 32'd0; end
554    end
555  end
556endmodule
557
558// A {what} matrix streamed block-major within groups of row pairs against up to T_MAX token-rows: for each group, every
559// pair's block 0, then every pair's block 1, ..., a beat a pair's two blocks, decoded as it is taken. The beat held is
560// issued against every token-row, a token-row a clock, an issue's coordinates riding beside it to its sum's emission, and
561// the next beat is taken the clock the held one issues its last token-row.
562module logos_column_{name} #(parameter T_MAX = 4, NB_MAX = 8, P_MAX = 4)
563  (input clk, input rst,
564   input start, input [15:0] groups, input [7:0] pairs, input [15:0] blocks, input [7:0] tokens{two_in}{fmt_in}, output busy,
565   input act_wr, input [7:0] act_t, input [15:0] act_b, input [{codes_top}:0] act_codes, input [7:0] act_m, input [11:0] act_e,
566   input w_valid, output w_ready, input [{top}:0] w_blk1, input [{top}:0] w_blk2,
567   output o_valid, output reg [15:0] o_group, output reg [7:0] o_pair, output reg [7:0] o_t, output [31:0] o_val1,
568   output [31:0] o_val2);
569{two_decl}{fmt_copy}  localparam LAT = {lat}, ADD = {add}, TAGS = LAT + ADD;
570  localparam AC = P_MAX * T_MAX, AW = (AC > 1) ? $clog2(AC) : 1;
571  // The beat held, its group, block and pair, the token-row it issues next; the coordinates of the beat taken next.
572  reg have, more;
573  reg [15:0] hg, hb, ng, nb;
574  reg [7:0] hp, np, t;
575  wire [31:0] at_wide = hp * T_MAX + t;
576  wire [AW-1:0] at = at_wide[AW-1:0];
577  // Each issue in flight: whether it is one, its sum's place, its coordinates, and whether it is its sum's last block.
578  reg live [0:TAGS-1];
579  reg [AW-1:0] tag [0:TAGS-1];
580  reg [15:0] tg [0:TAGS-1];
581  reg [7:0] tp [0:TAGS-1];
582  reg [7:0] tt [0:TAGS-1];
583  reg last [0:TAGS-1];
584  // An issue waits while an earlier issue of its sum is among the last ADD: that one's sum is written as it leaves the
585  // add, ADD clocks after it lands, and this one, landing LAT clocks after it issues as that one did, must read it.
586  reg stall, flight;
587  integer h;
588  always @* begin
589    stall = 1'b0;
590    flight = 1'b0;
591    for (h = 0; h < ADD; h = h + 1) if (live[h] && tag[h] == at) stall = 1'b1;
592    for (h = 0; h < TAGS; h = h + 1) if (live[h]) flight = 1'b1;
593  end
594  // An issue's passes, a clock each: its format's code digits, for each activation word; the last waits on a stall.
595{digits}  wire [2:0] passes = two ? {{digits, 1'b0}} : {{1'b0, digits}};
596  reg [2:0] pk;
597  wire last_pass = pk + 3'd1 == passes;
598  wire issue = have && last_pass && !stall;
599  wire pass = have && (!last_pass || !stall);
600  wire digit = digits[1] && pk[0];
601  wire word = digits[1] ? pk[1] : pk[0];
602  wire fin = issue && (t + 8'd1 == tokens);
603  assign w_ready = more && (!have || fin);
604  wire take = w_valid && w_ready;
605  assign busy = have || more || flight;
606  // The beat decoded as it is taken; each token-row's blocks of activations, as their words' excess codes and unit
607  // sums, in block RAM.
608  wire [{beat_top}:0] w_beat;
609  logos_column_decode_{name} dec(.blk1(w_blk1), .blk2(w_blk2), {fmt_dec}.beat(w_beat));
610  localparam AN = T_MAX * NB_MAX, AA = (AN > 1) ? $clog2(AN) : 1;
611  wire [{act_top}:0] act_wd, act_word;
612  logos_column_act_word #(.ELEMS({elems}), .WORDS({words_n}), .SLOTS({slots})) word_of(.codes(act_codes), .two(two), .m(act_m), .e(act_e),
613    .slot({act_slot}),
614    .word(act_wd));
615  wire [31:0] act_ra = t * NB_MAX + hb, act_wa = act_t * NB_MAX + act_b;
616  {act_ram} #(.D(AN), .A(AA)) act_ram(.clk(clk), .ra(act_ra[AA-1:0]), .rd(act_word), .we(act_wr),
617    .wa(act_wa[AA-1:0]), .wd(act_wd), .wm({{{act_bytes}{{1'b1}}}}));
618{stagger}  logos_column_core_{name} #(.SUMS(AC), .SW(AW)) core(.clk(clk), .rst(rst), .clear(start && !busy), {two_pass}{fmt_pass}.put(take), .load(take),
619    .w_beat(w_beat), .pass(pass), .digit(digit), .word(word), .first(pk == 3'd0), .issue(issue), .act_word(act_word), .act_st(act_st),
620    .land(tag[LAT-1]), .done(tag[TAGS-1]), .last(last[TAGS-1]), .o_valid(o_valid), .o_val1(o_val1), .o_val2(o_val2));
621  integer k;
622  always @(posedge clk) begin
623    live[0] <= issue; tag[0] <= at; tg[0] <= hg; tp[0] <= hp; tt[0] <= t; last[0] <= (hb + 16'd1 == blocks);
624    for (k = 1; k < TAGS; k = k + 1) begin
625      live[k] <= live[k-1]; tag[k] <= tag[k-1]; tg[k] <= tg[k-1]; tp[k] <= tp[k-1]; tt[k] <= tt[k-1]; last[k] <= last[k-1];
626    end
627    if (live[TAGS-1] && last[TAGS-1]) begin o_group <= tg[TAGS-1]; o_pair <= tp[TAGS-1]; o_t <= tt[TAGS-1]; end
628    if (pass) pk <= last_pass ? 3'd0 : pk + 3'd1;
629    if (issue) t <= (t + 8'd1 == tokens) ? 8'd0 : t + 8'd1;
630    if (take) begin
631      hg <= ng; hb <= nb; hp <= np; have <= 1'b1;
632      if (np + 8'd1 < pairs) np <= np + 8'd1;
633      else begin
634        np <= 8'd0;
635        if (nb + 16'd1 < blocks) nb <= nb + 16'd1;
636        else begin
637          nb <= 16'd0;
638          if (ng + 16'd1 < groups) ng <= ng + 16'd1; else more <= 1'b0;
639        end
640      end
641    end else if (fin) have <= 1'b0;
642    if (rst) begin
643      have <= 1'b0; more <= 1'b0; t <= 8'd0; pk <= 3'd0;
644      for (k = 0; k < TAGS; k = k + 1) live[k] <= 1'b0;
645    end else if (start && !busy) begin
646      ng <= 16'd0; nb <= 16'd0; np <= 8'd0; t <= 8'd0; pk <= 3'd0; more <= 1'b1;
647    end
648  end
649endmodule
650
651// A {what} matrix laid in stripes, each channel's tile of a stripe block-major, through CH * N cores, N on each channel:
652// core c = ch * N + p takes row pair p of channel ch's tile of each stripe, its decoded beats in order. Stripe s's tile on
653// channel ch holds the tensor's rows from (CH * s + turns[ch]) * 2N, fewer than 2N only at the tensor's end; a core whose
654// pair's first row is past the tensor takes no beat that stripe. Every core runs the same pass of the same token-row of
655// the same block step each clock against one read of the activations; a step issues once every core the stripe holds a
656// pair for has its beat, and the cores take the next step's beats the clock the last token-row issues: core c's feed
657// puts each of its beats into the core's ring as it decodes it (w_put[c], the beat on its w_beat), w_valid[c] says the
658// ring holds one queued, and taking it moves the ring's head (its feed queues one beat a core for two cores or more, two
659// for a lone core, so the ring's places are one more). Each core's sums
660// emerge at the stripe's last block, a token-row a clock, all cores at once, with the stripe and the token-row beside
661// them. No pass runs while `hold`.
662module logos_column_array_{name} #(parameter CH = 4, N = 2, T_MAX = 4, NB_MAX = 8)
663  (input clk,{clk2_in} input rst,
664   input start, input [15:0] stripes, input [15:0] blocks, input [7:0] tokens{two_in}{fmt_in}, input [31:0] rows,
665   input [2*CH-1:0] turns, input hold, output busy,
666   input act_wr, input [7:0] act_t, input [15:0] act_b{arr_act_s}, input [{codes_top}:0] act_codes, input [7:0] act_m, input [11:0] act_e,
667   input [CH*N-1:0] w_valid, output [CH*N-1:0] w_ready, input [CH*N-1:0] w_put, input [CH*N*{beat}-1:0] w_beat,
668   output [CH*N-1:0] o_valid, output [CH*N*32-1:0] o_val1, output [CH*N*32-1:0] o_val2, output reg [15:0] o_stripe,
669   output reg [7:0] o_t);
670  localparam LAT = {lat}, ADD = {add}, TAGS = LAT + ADD, C = CH * N;
671{two_decl}{fmt_copy}  localparam TW = (T_MAX > 1) ? $clog2(T_MAX) : 1;
672  // The step held: its stripe, its block, the token-row it issues next; whether a step is held. Beside them, each the value its wire would have from them, so an issue is a few levels
673  // from registers: whether the block is its stripe's last (step_end), the stripe the last (last_stripe), the token-row
674  // the step's last (t_last), and whether every core the step holds a pair for has its beat (ready).
675  reg held, step_end, last_stripe, t_last, ready;
676  reg [15:0] hs, hb;
677  reg [7:0] t;
678  reg [C-1:0] have;
679  wire next = !(step_end && last_stripe);
680  localparam [31:0] STRIPE_ROWS = CH * 2 * N;
681  // The matrix's rows from the next stripe's first on (left_after), and whether each core's pair's first row is among
682  // the rows from the held stripe's first on (need_cur) and from the next one's (need_adv), registers updated a stripe
683  // at a time; so the cores the held step and the next one hold a pair for are a select of registers. The first
684  // step's from the rows.
685  reg [31:0] left_after;
686  function [31:0] after_stripe(input [31:0] l);
687    after_stripe = (l > STRIPE_ROWS) ? l - STRIPE_ROWS : 32'd0;
688  endfunction
689  reg [C-1:0] need, need_next, need_start, need_cur, need_adv, adv_start, adv_next;
690  reg [31:0] first;
691  integer c;
692  always @* begin
693    for (c = 0; c < C; c = c + 1) begin
694      first = turns[2*(c/N) +: 2] * 2 * N + 2 * (c % N);
695      need[c] = held && need_cur[c];
696      need_next[c] = held && next && (step_end ? need_adv[c] : need_cur[c]);
697      need_start[c] = first < rows;
698      adv_start[c] = first < after_stripe(rows);
699      adv_next[c] = first < after_stripe(left_after);
700    end
701  end
702  // Each issue in flight: whether it is one, its token-row, its stripe, and whether it is its sums' last block.
703  reg live [0:TAGS-1];
704  reg [TW-1:0] tag [0:TAGS-1];
705  reg [15:0] ts [0:TAGS-1];
706  reg [7:0] tt [0:TAGS-1];
707  reg last [0:TAGS-1];
708  wire [TW-1:0] at = t[TW-1:0];
709  reg stall, flight;
710  integer h;
711  always @* begin
712    stall = 1'b0;
713    flight = 1'b0;
714    for (h = 0; h < ADD; h = h + 1) if (live[h] && tag[h] == at) stall = 1'b1;
715    for (h = 0; h < TAGS; h = h + 1) if (live[h]) flight = 1'b1;
716  end
717{arr_octets}  // An issue's passes, a clock each: its format's code digits, for each activation word; the last waits on a stall.
718{digits}  wire [2:0] passes = two ? {{digits, 1'b0}} : {{1'b0, digits}};
719  reg [2:0] pk;
720  // The pass counter one on (pk_up), a register beside it from its next value, so the last pass is a compare of registers.
721  reg [2:0] pk_up;
722  wire last_pass = pk_up == passes;
723  wire issue = held && ready && !hold && last_pass && {pe_ok};
724  wire pass = held && ready && !hold && (!last_pass || {pe_ok});
725  wire digit = digits[1] && pk[0];
726  wire word = digits[1] ? pk[1] : pk[0];
727  wire fin = issue && t_last;
728  wire [2:0] pk_n = rst || (start && !busy) ? 3'd0 : pass ? (last_pass ? 3'd0 : pk_up) : pk;
729  always @(posedge clk) pk_up <= pk_n + 3'd1;
730  assign w_ready = (need & ~have) | ({{C{{fin}}}} & need_next);
731  wire [C-1:0] take = w_valid & w_ready;
732  assign busy = held || flight{arr_busy};
733  // Each token-row's blocks of activations, as their words' excess codes and unit sums, in block RAM beside each
734  // channel: every copy written alike a clock after the word is made, each read a clock for its own channel's cores.
735  // Each token-row's blocks at a pitch of a power of two (NB_P, 2^NB_L), so an address is a token-row's place and a
736  // block's in bits of their own.
737  localparam NB_L = (NB_MAX > 1) ? $clog2(NB_MAX) : 0, NB_P = 1 << NB_L;
738  localparam AN = T_MAX * NB_P, AA = (AN > 1) ? $clog2(AN) : 1;
739  wire [{act_top}:0] act_wd;
740  logos_column_act_word #(.ELEMS({elems}), .WORDS({words_n}), .SLOTS({slots})) word_of(.codes(act_codes), .two(two), .m(act_m), .e(act_e),
741    .slot({act_slot}),
742    .word(act_wd));
743  wire [31:0] act_wa = ({{24'd0, act_t}} << NB_L) | (act_b & (NB_P - 1));
744  // The step's next token-row and block, as the clock's update leaves them, and the read address they make: each copy
745  // takes it into a register of its own beside its blocks, so a read sees the address of the step's token-row and block
746  // as before, from a register.
747  // The step's token-row and block one on (t_up, hb_up) are registers beside them, the next values plus their steps,
748  // so each next value is a choice of registers and the read address holds no carry chain.
749  reg [7:0] t_up;
750  reg [15:0] hb_up;
751  wire [7:0] t_n = rst || (start && !busy) ? 8'd0 : issue ? (t_last ? 8'd0 : t_up) : t;
752  wire [15:0] hb_n = !rst && start && !busy ? 16'd0 : fin && next ? (step_end ? 16'd0 : hb_up) : hb;
753  always @(posedge clk) begin t_up <= t_n + 8'd1; hb_up <= hb_n + 16'd1; end
754  wire [31:0] ra_n = ({{24'd0, t_n}} << NB_L) | ({{16'd0, hb_n}} & (NB_P - 1));
755  wire [CH*{act_w}-1:0] act_words;
756{ach_decls}  genvar ch;
757  generate
758    for (ch = 0; ch < CH; ch = ch + 1) begin : ach
759      reg we;
760      reg [AA-1:0] wa;
761      reg [{act_top}:0] wd;
762      reg [{wm_top}:0] wm;
763      (* maxfan = 64 *) reg [AA-1:0] ra;
764      always @(posedge clk) begin we <= act_wr; wa <= act_wa[AA-1:0]; wd <= act_wd; wm <= {act_wm}; ra <= ra_n[AA-1:0]; end
765      wire [{act_top}:0] act_word;
766      {act_ram} #(.D(AN), .A(AA)) act_ram(.clk(clk), .ra(ra), .rd(act_word), .we(we), .wa(wa), .wd(wd), .wm(wm));
767      assign act_words[ch*{act_w} +: {act_w}] = act_word;
768{ach_body}    end
769  endgenerate
770  genvar g;
771  generate
772    for (g = 0; g < C; g = g + 1) begin : core
773      logos_column_core_{core_mod} #(.SUMS(T_MAX), .SW(TW), .RING((N == 1) ? 3 : 2)) u(.clk(clk), .rst(rst), .clear(start && !busy),
774        {two_pass}{fmt_pass}.put(w_put[g]), .load(take[g]), .w_beat(w_beat[g*{beat} +: {beat}]), .pass(pass), .digit(digit), .word(word), .first(pk == 3'd0),
775        .issue({core_issue}){core_octet_conn}, .act_word(act_words[(g/N)*{act_w} +: {act_w}]), {core_pe}.land(tag[LAT-1]), .done(tag[TAGS-1]), .last(last[TAGS-1]),
776        .o_valid(o_valid[g]), .o_val1(o_val1[g*32 +: 32]), .o_val2(o_val2[g*32 +: 32]));
777    end
778{pairs}  endgenerate
779  integer k;
780  always @(posedge clk) begin
781{tags_in}
782    for (k = 1; k < TAGS; k = k + 1) begin
783      live[k] <= live[k-1]; tag[k] <= tag[k-1]; ts[k] <= ts[k-1]; tt[k] <= tt[k-1]; last[k] <= last[k-1];
784    end
785    if (live[TAGS-1] && last[TAGS-1]) begin o_stripe <= ts[TAGS-1]; o_t <= tt[TAGS-1]; end
786    if (pass) pk <= last_pass ? 3'd0 : pk + 3'd1;
787    if (issue) begin t <= t_last ? 8'd0 : t + 8'd1; t_last <= t_last ? tokens == 8'd1 : t + 8'd2 == tokens; end
788    have <= (have & ~{{C{{fin}}}}) | take;
789    // Next clock's ready from this one's registers: past a step's last issue every core the next step needs is offered
790    // its beat, otherwise every core this step needs holds its beat or is offered it.
791    ready <= fin ? &(~need_next | w_valid) : &(have | w_valid | ~need);
792    if (fin) begin
793      if (!next) held <= 1'b0;
794      else if (step_end) begin
795        hb <= 16'd0; hs <= hs + 16'd1; left_after <= after_stripe(left_after); need_cur <= need_adv;
796        need_adv <= adv_next; step_end <= blocks == 16'd1; last_stripe <= hs + 16'd2 == stripes;
797      end else begin
798        hb <= hb + 16'd1; step_end <= hb + 16'd2 == blocks;
799      end
800    end
801{arr_octet_step}    if (rst) begin
802      held <= 1'b0; have <= {{C{{1'b0}}}}; t <= 8'd0; pk <= 3'd0;
803      for (k = 0; k < TAGS; k = k + 1) live[k] <= 1'b0;{arr_octet_reset}
804    end else if (start && !busy) begin
805      held <= 1'b1; hs <= 16'd0; hb <= 16'd0; left_after <= after_stripe(rows); need_cur <= need_start;
806      need_adv <= adv_start; t <= 8'd0; pk <= 3'd0; have <= {{C{{1'b0}}}};
807      step_end <= blocks == 16'd1; last_stripe <= stripes == 16'd1; t_last <= tokens == 8'd1; ready <= &(~need_start);{arr_octet_reset}
808    end
809  end
810endmodule
811
812// One channel's feed of a {what} matrix laid in stripes to its N cores: the image stream's beats of the channel's tiles
813// (a beat at offset 0 starting a tile, whose first row it carries; zeros past its kept bytes) gathered into blocks, and
814// each block step's blocks dealt in pairs, decoded, the tile's rows 2p and 2p + 1 to core p, the second block zeros when
815// the tile's last row has no partner. A tile holds 2N rows, fewer at the tensor's `rows`. Each block comes in its slot
816// (its bytes, then its pad's zeros), so a beat lands in the gather register a whole number of slots in. The gather
817// register keeps less than a block past the last block it holds, so it never holds more than two tiles' bytes, and its
818// bytes past those it holds are zeros, a beat's bytes ORed in at its end (cleared with the reset, so whatever it powers
819// up holding never reaches a block); the beats queued for each core, in its ring behind the beat it holds
820// (logos_column_core_{name}), let the stream run a step ahead of the cores: two for a lone core, one a core for two or
821// more, whose beats come no sooner than 2N blocks apart. A pair's beat is decoded into a register and put into its
822// core's ring the clock after (w_put, the register on every core's w_beat), a core's room counting the beat on its way;
823// whether the row a block goes to is its tile's last is held in a register beside the row.
824module logos_column_feed_{name} #(parameter N = 2)
825  (input clk, input rst{fmt_in}, input [15:0] blocks, input [31:0] rows,
826   input in_valid, output in_ready, input [511:0] in_data, input [6:0] in_keep, input [31:0] in_row, input [23:0] in_offset,
827   output [N-1:0] w_valid, input [N-1:0] w_ready, output [N-1:0] w_put, output [N*{beat}-1:0] w_beat);
828{fmt_copy}{feed_bb}  reg [8*GB-1:0] gather;
829  // Each core's put, a register copied a copy each 64 of the ring's write enables it reaches.
830  (* maxfan = 64 *) reg [N-1:0] put_q;
831  assign w_put = put_q;
832  reg [15:0] fill;
833  // The rows of the tile whose blocks leave next, and of the tile after it once its first beat is in.
834  reg [7:0] held, held_next;
835  reg has_next;
836  (* maxfan = 256 *) reg [7:0] j;
837  reg jl;
838  reg [15:0] b;
839  reg [{top}:0] stage;
840  // The beats queued in each core's ring, QD at most, and the beat decoded on its way there.
841  localparam QD = (N == 1) ? 2 : 1;
842  reg [1:0] count [0:N-1];
843  reg [{beat_top}:0] dq;
844  reg dq_v;
845  reg [7:0] dq_p;
846  wire [7:0] p = j >> 1;
847  wire odd = j[0];
848  wire alone = !odd && jl;
849  // A block leaves the gather register when its tile is known and, completing a beat, its core's queue has room for it
850  // beside any beat already on its way there.
851  wire pair = odd || alone;
852  wire on_way = dq_v && dq_p == p;
853  wire queue_room = (QD == 2) ? (count[p] == 2'd0 || (count[p] == 2'd1 && !on_way)) : (count[p] == 2'd0 && !on_way);
854  wire emit = fill >= BB && held != 8'd0 && (!pair || queue_room);
855  wire push = emit && pair;
856  wire [15:0] kept = emit ? fill - BB : fill;
857  assign in_ready = kept <= BB && !(in_offset == 24'd0 && has_next);
858  wire take = in_valid && in_ready;
859  localparam [31:0] TILE = 2 * N;
860  wire [31:0] room = rows - in_row;
861  wire [7:0] tile_rows = (room < TILE) ? room[7:0] : TILE[7:0];
862  wire tile_end = emit && jl && (b + 16'd1 == blocks);
863  wire [8*GB-1:0] shifted = emit ? {drop} : gather;
864  // The next row and the next tile's rows, and so whether the next row is its tile's last.
865  wire [7:0] j_next = emit ? (jl ? 8'd0 : j + 8'd1) : j;
866  wire tile_in = take && in_offset == 24'd0;
867  wire [7:0] held_now = tile_in ? ((held == 8'd0 || tile_end) ? tile_rows : held) : tile_end ? (has_next ? held_next : 8'd0) : held;
868  // The beat a push queues, decoded.
869  wire [{beat_top}:0] decoded;
870  logos_column_decode_{name} dec(.blk1(odd ? stage : gather[{top}:0]), .blk2(odd ? gather[{top}:0] : {{{bits}{{1'b0}}}}),
871    {fmt_dec}.beat(decoded));
872  genvar g;
873  generate
874    for (g = 0; g < N; g = g + 1) begin : core
875      assign w_valid[g] = count[g] != 2'd0;
876      assign w_beat[g*{beat} +: {beat}] = dq;
877    end
878  endgenerate
879  integer k;
880  always @(posedge clk) begin
881    if (take) gather <= shifted | ({{{{(8*GB-512){{1'b0}}}}, in_data}} << ({gather_shift}));
882    else gather <= shifted;
883    fill <= kept + (take ? in_keep : 7'd0);
884    if (emit) begin
885      if (!odd && !alone) stage <= gather[{top}:0];
886      if (jl) b <= (b + 16'd1 == blocks) ? 16'd0 : b + 16'd1;
887    end
888    j <= j_next;
889    held <= held_now;
890    jl <= j_next + 8'd1 == held_now;
891    dq_v <= push;
892    if (push) begin dq <= decoded; dq_p <= p; end
893    for (k = 0; k < N; k = k + 1) begin
894      put_q[k] <= push && p == k[7:0];
895      if (w_valid[k] && w_ready[k]) begin
896        if (!(dq_v && dq_p == k[7:0])) count[k] <= count[k] - 2'd1;
897      end else if (dq_v && dq_p == k[7:0]) count[k] <= count[k] + 2'd1;
898    end
899    if (tile_in) begin
900      if (!(held == 8'd0 || tile_end)) begin held_next <= tile_rows; has_next <= 1'b1; end
901    end else if (tile_end) has_next <= 1'b0;
902    if (rst) begin
903      gather <= {{(8*GB){{1'b0}}}}; fill <= 16'd0; held <= 8'd0; has_next <= 1'b0; j <= 8'd0; jl <= 1'b0; b <= 16'd0;
904      dq_v <= 1'b0;
905      for (k = 0; k < N; k = k + 1) begin count[k] <= 2'd0; put_q[k] <= 1'b0; end
906    end
907  end
908endmodule"#,
909            top = bits - 1,
910            beat = 2 * db,
911            beat_top = 2 * db - 1,
912            act_top = set.act_bits() - 1,
913            act_bytes = set.act_bits() / 8,
914            codes_top = 16 * elems - 1,
915            unit_top = 8 * units - 1,
916            fc = f.c,
917            fn = f.n,
918            fa = f.a,
919            fb = f.b,
920            fq = f.q,
921            decode = decode(&decs, elems),
922            zsel = zsel(&decs),
923            digits = digits(&decs),
924            words_n = set.word_count(),
925            feed_bb = feed_block_bytes(&decs),
926            gather_shift = if set.slot > 1 { format!("{} * kept[15:{}]", 8 * set.slot, set.slot.trailing_zeros()) } else { "8 * kept".to_string() },
927            drop = feed_drop(&decs),
928            lat = COLUMN_VALUE_LATENCY,
929            add = FADD_LATENCY,
930            pl = PASS_LATENCY,
931            lb_bits = lane.beat_bits,
932            lb_in = lane.beat_packed,
933            la_bits = lane.act_bits,
934            la_in = lane.act_packed,
935            lf_out = lane.unpacked,
936            act_ram = super::ram::bram_name(set.act_bits()),
937            stagger = if paired {
938                format!(
939                    "  // The pass's word for the pairs' processing elements, its excess codes chosen at the read and its units' sums,\n  \
940                     // once for every core; each core's codes out to its pair and its partials back.\n  reg word_a;\n  \
941                     always @(posedge clk) word_a <= word;\n  wire [{code_top}:0] act_un = {st_sel};\n  \
942                     wire [{bus12}:0] s_un = {};\n  localparam CODE = {}, PART = {};\n  wire [C*CODE-1:0] pc1, pc2;\n  \
943                     wire [C*PART-1:0] pp1, pp2;\n",
944                    s_sel.replace("s1_word", "word_a"),
945                    code_top + 1,
946                    bus24 + 1
947                )
948            } else {
949                format!(
950                    "  // The pass's excess codes for the systolic elements, the word chosen at the read and each element's block\n  \
951                     // delayed for it, once for every core.\n  reg word_a;\n  always @(posedge clk) word_a <= word;\n  \
952                     wire [{code_top}:0] act_st;\n  logos_column_act_stagger #(.ELEMS({elems})) st(.clk(clk), .codes({st_sel}), .st(act_st));\n"
953                )
954            },
955            clk2_in = if set.pe_pairs { " input clk2," } else { "" },
956            core_mod = if paired { format!("bare_{name}") } else { name.to_string() },
957            core_pe = if paired {
958                ".pe_code1(pc1[g*CODE +: CODE]), .pe_code2(pc2[g*CODE +: CODE]), .pe_p1(pp1[g*PART +: PART]), .pe_p2(pp2[g*PART +: PART]),\n        ".to_string()
959            } else {
960                format!(".act_st(act_sts[(g/N)*{w} +: {w}]), ", w = code_top + 1)
961            },
962            act_w = set.act_bits(),
963            wm_top = set.act_bits() / 8 - 1,
964            ach_decls = if paired {
965                format!(
966                    "  // Each channel's word for its pairs' processing elements, its excess codes chosen at the read and its units'\n  \
967                     // sums; each core's codes out to its pair and its partials back.\n  \
968                     wire [CH*{cw}-1:0] act_uns;\n  wire [CH*{sw}-1:0] s_uns;\n  localparam CODE = {cw}, PART = {pw};\n  \
969                     wire [C*CODE-1:0] pc1, pc2;\n  wire [C*PART-1:0] pp1, pp2;\n",
970                    cw = code_top + 1,
971                    sw = bus12 + 1,
972                    pw = bus24 + 1
973                )
974            } else {
975                format!("  // Each channel's excess codes for its cores' systolic elements, each element's block delayed for it.\n  wire [CH*{}-1:0] act_sts;\n", code_top + 1)
976            },
977            ach_body = if paired {
978                format!(
979                    "      reg word_a;\n      always @(posedge clk) word_a <= word;\n      \
980                     assign act_uns[ch*{cw} +: {cw}] = {st_sel};\n      assign s_uns[ch*{sw} +: {sw}] = {};\n",
981                    s_sel.replace("s1_word", "word_a"),
982                    cw = code_top + 1,
983                    sw = bus12 + 1
984                )
985            } else {
986                format!(
987                    "      reg word_a;\n      always @(posedge clk) word_a <= word;\n      wire [{code_top}:0] act_st;\n      \
988                     logos_column_act_stagger #(.ELEMS({elems})) st(.clk(clk), .codes({st_sel}), .st(act_st));\n      \
989                     assign act_sts[ch*{cw} +: {cw}] = act_st;\n",
990                    cw = code_top + 1
991                )
992            },
993            pairs = if paired {
994                format!(
995                    "    // Each pair of cores on one set of processing elements in clk2, core 2g's codes at its first turn.\n    \
996                     for (g = 0; g < C / 2; g = g + 1) begin : pair\n      \
997                     logos_matvec_pass_x2 #(.UNITS({units})) pe(.clk(clk), .clk2(clk2), .code1a(pc1[2*g*CODE +: CODE]),\n        \
998                     .code2a(pc2[2*g*CODE +: CODE]), .code1b(pc1[(2*g+1)*CODE +: CODE]), .code2b(pc2[(2*g+1)*CODE +: CODE]),\n        \
999                     .w(act_uns[((2*g)/N)*CODE +: CODE]), .s(s_uns[((2*g)/N)*{sw} +: {sw}]), .digit(digit), .p1a(pp1[2*g*PART +: PART]), .p2a(pp2[2*g*PART +: PART]), .p1b(pp1[(2*g+1)*PART +: PART]),\n        \
1000                     .p2b(pp2[(2*g+1)*PART +: PART]));\n    end\n",
1001                    sw = bus12 + 1
1002                )
1003            } else {
1004                String::new()
1005            },
1006            beat_s7 = if set.octets { format!("  assign beat[{s} +: 112] = s71_d;\n  assign beat[{} +: 112] = s72_d;\n", db + f.s7, s = f.s7) } else { String::new() },
1007            core_octet_ports = oct.ports,
1008            core_octets = oct.body,
1009            octet_lanes = oct.lanes,
1010            octet_lanes_end = oct.lanes_end,
1011            lanes_from = oct.from,
1012            slots = set.act_slots(),
1013            act_slot = if set.octets { "act_s" } else { "3'd0" },
1014            arr_act_s = arr["arr_act_s"],
1015            arr_octets = arr["arr_octets"],
1016            pe_ok = arr["pe_ok"],
1017            arr_busy = arr["arr_busy"],
1018            act_wm = arr["act_wm"],
1019            core_issue = arr["core_issue"],
1020            core_octet_conn = arr["core_octet_conn"],
1021            tags_in = arr["tags_in"],
1022            arr_octet_step = arr["arr_octet_step"],
1023            arr_octet_reset = arr["arr_octet_reset"],
1024        )
1025        .unwrap();
1026        if set.octets || set.token_pairs {
1027            // An octet job, and a token pair's issue, runs on the array alone: the set builds no lone column.
1028            let at = v.rfind(&format!("module logos_column_{name} ")).expect("the set's lone column");
1029            let start = v[..at].rfind("\n\n").map_or(at, |i| i + 1);
1030            let end = at + v[at..].find("endmodule").expect("its end") + "endmodule".len();
1031            v.replace_range(start..end, "");
1032        }
1033        if set.lanes > 1 {
1034            v = octet_lanes(&v, set);
1035        }
1036        if paired {
1037            v = bare_core(&v, set, units, code_top, bus24);
1038        }
1039        if set.token_pairs {
1040            v = token_pairs(&v, set);
1041        }
1042    }
1043    if sets.iter().any(|s| s.octets) {
1044        v.push_str(OCTET_DECODER);
1045    }
1046    if sets.iter().any(|s| s.pe_pairs && !s.token_pairs) {
1047        v.push_str(&matvec_pass_x2_verilog());
1048    }
1049    if sets.iter().any(|s| s.pe_pairs && s.token_pairs) {
1050        v.push_str(&matvec_pass_x2t_verilog());
1051    }
1052    v
1053}
1054
1055/// `v` with set `set`'s core beside it as `logos_column_core_bare_<set>`: the core, its pass's codes out to processing
1056/// elements it shares with another core (`pe_code1`, `pe_code2`) and their partials in (`pe_p1`, `pe_p2`) where its own
1057/// elements were, for [`ColumnSet::pe_pairs`].
1058fn bare_core(v: &str, set: &ColumnSet, units: usize, code_top: usize, bus24: usize) -> String {
1059    let name = &set.name;
1060    let head = format!("module logos_column_core_{name} ");
1061    let at = v.find(&head).unwrap_or_else(|| panic!("`{head}`"));
1062    let end = at + v[at..].find("endmodule").expect("its end") + "endmodule".len();
1063    let bare = replaced(
1064        &v[at..end],
1065        &[
1066            (&head, &format!("module logos_column_core_bare_{name} ")),
1067            (
1068                &format!("input [{code_top}:0] act_st,"),
1069                &format!("output [{code_top}:0] pe_code1, output [{code_top}:0] pe_code2,\n   input [{bus24}:0] pe_p1, input [{bus24}:0] pe_p2,"),
1070            ),
1071            (
1072                &format!(
1073                    "  logos_matvec_pass_p #(.UNITS({units})) parts(.clk(clk), .code1(code1), .code2(code2), .w(act_st), .s(s_act), .digit(digit),\n    .p1(w_p1), .p2(w_p2));\n"
1074                ),
1075                "  assign pe_code1 = code1;\n  assign pe_code2 = code2;\n  assign w_p1 = pe_p1;\n  assign w_p2 = pe_p2;\n",
1076            ),
1077        ],
1078    );
1079    let mut out = v.to_string();
1080    out.insert_str(
1081        end,
1082        &format!(
1083            "\n\n// logos_column_core_{name} with its processing elements outside it, shared with another core: its pass's codes out\n\
1084             // (`pe_code1`, `pe_code2`) and their partials in (`pe_p1`, `pe_p2`), as its own elements would give them.\n{bare}"
1085        ),
1086    );
1087    out
1088}
1089
1090/// The fields of an issue's beat and activations its lanes take — both rows' unit sums of codes, multipliers, offset
1091/// terms and scales, the activation's scale, the pass's word's unit sums, and for a set with octets the other seven
1092/// blocks' scales of both rows and the activation's — the beat's packed in that order into one of the core's
1093/// `logos_delay`s from the beat held and the activations' into the other, and unpacked from their words out (`lfb`,
1094/// `lfa`, [`PASS_LATENCY`] clocks after the pass's activations) under their names.
1095struct LaneFields {
1096    beat_bits: usize,
1097    beat_packed: String,
1098    act_bits: usize,
1099    act_packed: String,
1100    unpacked: String,
1101}
1102
1103fn lane_fields(set: &ColumnSet) -> LaneFields {
1104    let (f, a, db, u) = (set.decoded_fields(), set.act_fields(), set.decoded_bits(), set.elems() / UNIT);
1105    let beat = |name: &str, at: usize, w: usize| (name.to_string(), format!("held[{at} +: {w}]"), w);
1106    let mut fields = vec![
1107        beat("f_q1", f.q, 12 * u),
1108        beat("f_q2", db + f.q, 12 * u),
1109        beat("f_c1", f.c, 8 * u),
1110        beat("f_n1", f.n, 8 * u),
1111        beat("f_c2", db + f.c, 8 * u),
1112        beat("f_n2", db + f.n, 8 * u),
1113        beat("f_a1", f.a, 16),
1114        beat("f_b1", f.b, 16),
1115        beat("f_a2", db + f.a, 16),
1116        beat("f_b2", db + f.b, 16),
1117        ("f_m".to_string(), format!("act_word[{} +: 8]", a.m), 8),
1118        ("f_e".to_string(), format!("act_word[{} +: 12]", a.e), 12),
1119        ("f_s".to_string(), "s_act".to_string(), 12 * u),
1120    ];
1121    if set.octets {
1122        fields.push(beat("f_s71", f.s7, 112));
1123        fields.push(beat("f_s72", db + f.s7, 112));
1124        for k in 1..8 {
1125            fields.push((format!("f_ms{k}"), format!("{{act_word[{} +: 12], act_word[{} +: 8]}}", a.e + 24 * k, a.m + 24 * k), 20));
1126        }
1127    }
1128    let (beats, acts): (Vec<_>, Vec<_>) = fields.iter().partition(|(_, e, _)| e.starts_with("held["));
1129    let mut unpacked = String::new();
1130    let mut pack = |part: &[&(String, String, usize)], out: &str| {
1131        let mut at = 0;
1132        for (name, _, w) in part {
1133            writeln!(unpacked, "  wire [{}:0] {name} = {out}[{at} +: {w}];", w - 1).unwrap();
1134            at += w;
1135        }
1136        let packed: Vec<&str> = part.iter().rev().map(|(_, e, _)| e.as_str()).collect();
1137        (at, packed.join(", "))
1138    };
1139    let (beat_bits, beat_packed) = pack(&beats, "lfb");
1140    let (act_bits, act_packed) = pack(&acts, "lfa");
1141    LaneFields { beat_bits, beat_packed, act_bits, act_packed, unpacked }
1142}
1143
1144/// The core and the array of `set`, a set of [`ColumnSet::lanes`] octet lanes a row, in `v`: the core's lanes past the
1145/// first (`logos_lane_slim_p`, a row's each) taking a token-row's next blocks of its group, lane j's value added into
1146/// the row's sum j adds after the first add, its value delayed j adds to meet it; an octet job's sums written back, and
1147/// the array's last-block taps read, `lanes` adds after the value lands, a K-quant's one add after as before.
1148fn octet_lanes(v: &str, set: &ColumnSet) -> String {
1149    let (name, l) = (&set.name, set.lanes);
1150    let words = set.word_count();
1151    let core_head = format!("module logos_column_core_{name} ");
1152    let array_head = format!("module logos_column_array_{name} ");
1153    let span = |v: &str, head: &str| {
1154        let at = v.find(head).unwrap_or_else(|| panic!("`{head}`"));
1155        (at, at + v[at..].find("endmodule").expect("its end"))
1156    };
1157    let mut out = v.to_string();
1158    let (a, b) = span(&out, &core_head);
1159    let mut chain = String::from(
1160        "  // The octet lanes past the first: lane j's value of a token-row's block o_k + j added into the row's sum j adds after\n  \
1161         // the first add, the value delayed j adds to meet it; an octet job's sum is the chain's last.\n",
1162    );
1163    for r in 1..=2 {
1164        writeln!(chain, "  wire [31:0] ch{r}_0 = sum{r};").unwrap();
1165        for j in 1..l {
1166            let d = j * FADD_LATENCY;
1167            writeln!(
1168                chain,
1169                "  wire [31:0] sv{r}_{j};\n  logos_lane_slim_p #(.UNITS(2), .WORDS({words})) slim{r}_{j}(.clk(clk), .p_hi(sl{j}_p{r}h), .s_hi(sl{j}_sh), .p_lo(sl{j}_p{r}l),\n    \
1170                 .s_lo(sl{j}_sl), .q(sl{j}_q{r}), .z(z), .a(sl{j}_a{r}), .m(sl{j}_me[7:0]), .e(sl{j}_me[19:8]), .two(two), .value(sv{r}_{j}));\n  \
1171                 reg [31:0] dv{r}_{j} [0:{dt}];\n  integer dk{r}_{j};\n  always @(posedge clk) begin\n    dv{r}_{j}[0] <= sv{r}_{j};\n    \
1172                 for (dk{r}_{j} = 1; dk{r}_{j} < {d}; dk{r}_{j} = dk{r}_{j} + 1) dv{r}_{j}[dk{r}_{j}] <= dv{r}_{j}[dk{r}_{j} - 1];\n  end\n  \
1173                 wire [31:0] ch{r}_{j};\n  logos_fadd_p chain{r}_{j}(.clk(clk), .a(ch{r}_{p}), .b(dv{r}_{j}[{dt}]), .y(ch{r}_{j}));",
1174                dt = d - 1,
1175                p = j - 1,
1176            )
1177            .unwrap();
1178        }
1179        writeln!(chain, "  wire [31:0] chain{r} = ch{r}_{};", l - 1).unwrap();
1180    }
1181    let core = replaced(
1182        &out[a..b],
1183        &[
1184            (
1185                &format!("  localparam LAT = {}, ADD = {}, TAGS = LAT + ADD;\n", COLUMN_VALUE_LATENCY, FADD_LATENCY),
1186                &format!("  localparam LAT = {}, ADD = {}, TAGS = LAT + {l} * ADD, KT = LAT + ADD;\n", COLUMN_VALUE_LATENCY, FADD_LATENCY),
1187            ),
1188            ("  // This core's issues in flight.\n", &format!("{chain}  // This core's issues in flight.\n")),
1189            (
1190                "    if (alive[TAGS-1]) begin\n      acc1[done] <= last ? 32'd0 : sum1;\n      acc2[done] <= last ? 32'd0 : sum2;\n      \
1191                 if (last) begin o_valid <= 1'b1; o_val1 <= sum1; o_val2 <= sum2; end\n    end\n",
1192                "    if (oct ? alive[TAGS-1] : alive[KT-1]) begin\n      acc1[done] <= last ? 32'd0 : (oct ? chain1 : sum1);\n      \
1193                 acc2[done] <= last ? 32'd0 : (oct ? chain2 : sum2);\n      \
1194                 if (last) begin o_valid <= 1'b1; o_val1 <= oct ? chain1 : sum1; o_val2 <= oct ? chain2 : sum2; end\n    end\n",
1195            ),
1196        ],
1197    );
1198    out.replace_range(a..b, &core);
1199    let (a, b) = span(&out, &array_head);
1200    let array = replaced(
1201        &out[a..b],
1202        &[
1203            (
1204                &format!("  localparam LAT = {}, ADD = {}, TAGS = LAT + ADD, C = CH * N;\n", COLUMN_VALUE_LATENCY, FADD_LATENCY),
1205                &format!("  localparam LAT = {}, ADD = {}, TAGS = LAT + {l} * ADD, KT = LAT + ADD, C = CH * N;\n", COLUMN_VALUE_LATENCY, FADD_LATENCY),
1206            ),
1207            (".land(tag[LAT-1]), .done(tag[TAGS-1]), .last(last[TAGS-1]),", ".land(tag[LAT-1]), .done(oct ? tag[TAGS-1] : tag[KT-1]), .last(oct ? last[TAGS-1] : last[KT-1]),"),
1208            (
1209                "    if (live[TAGS-1] && last[TAGS-1]) begin o_stripe <= ts[TAGS-1]; o_t <= tt[TAGS-1]; end\n",
1210                "    if (oct ? live[TAGS-1] && last[TAGS-1] : live[KT-1] && last[KT-1]) begin\n      \
1211                 o_stripe <= oct ? ts[TAGS-1] : ts[KT-1]; o_t <= oct ? tt[TAGS-1] : tt[KT-1];\n    end\n",
1212            ),
1213        ],
1214    );
1215    out.replace_range(a..b, &array);
1216    out
1217}
1218
1219/// `text` with every identifier of `names` (a whole word: no letter, digit or `_` either side) given `suffix`, but a
1220/// port's name (after a `.`).
1221fn suffixed(text: &str, names: &[&str], suffix: &str) -> String {
1222    let ident = |c: char| c.is_ascii_alphanumeric() || c == '_';
1223    let mut out = String::with_capacity(text.len() + 64);
1224    let mut word = String::new();
1225    let flush = |word: &mut String, out: &mut String| {
1226        if names.contains(&word.as_str()) && !out.ends_with('.') {
1227            out.push_str(word);
1228            out.push_str(suffix);
1229        } else {
1230            out.push_str(word);
1231        }
1232        word.clear();
1233    };
1234    for c in text.chars() {
1235        if ident(c) {
1236            word.push(c);
1237        } else {
1238            flush(&mut word, &mut out);
1239            out.push(c);
1240        }
1241    }
1242    flush(&mut word, &mut out);
1243    out
1244}
1245
1246/// The one line of `text` holding `needle`, with its newline.
1247fn line_with<'a>(text: &'a str, needle: &str) -> &'a str {
1248    let lines: Vec<&str> = text.split_inclusive('\n').filter(|l| l.contains(needle)).collect();
1249    assert_eq!(lines.len(), 1, "one line holding `{needle}`");
1250    lines[0]
1251}
1252
1253/// The span of `text` from the start of the one line holding `needle` to the end of `to`'s first place after it.
1254fn from_line<'a>(text: &'a str, needle: &str, to: &str) -> &'a str {
1255    between(text, line_with(text, needle), to)
1256}
1257
1258/// The span of `text` from `from`'s one place to the end of `to`'s first place after it.
1259fn between<'a>(text: &'a str, from: &str, to: &str) -> &'a str {
1260    assert_eq!(text.matches(from).count(), 1, "`{from}`");
1261    let a = text.find(from).unwrap();
1262    let b = a + text[a..].find(to).unwrap_or_else(|| panic!("`{to}` after `{from}`")) + to.len();
1263    &text[a..b]
1264}
1265
1266/// The core and the array of a set of token pairs ([`ColumnSet::token_pairs`]) in `v`: the core's second token-row
1267/// (`_b`) beside its first — its word's unit sums, processing elements of its own on the held beat's codes, its word's
1268/// lane fields, its sums over the passes, an octet job's slots, its lanes' inputs, its two lanes, its two rows' sums
1269/// and their values out, the beat's fields and the issue's flags shared; the array's issues a token pair each, a
1270/// sum's place a pair's, its copies of the activations a pair a word.
1271fn token_pairs(v: &str, set: &ColumnSet) -> String {
1272    let name = &set.name;
1273    let (u, elems) = (set.elems() / UNIT, set.elems());
1274    let (aw, cw) = (set.act_bits(), 8 * elems);
1275    let span = |v: &str, head: &str| {
1276        let at = v.find(head).unwrap_or_else(|| panic!("`{head}`"));
1277        (at, at + v[at..].find("endmodule").expect("its end"))
1278    };
1279    let mut out = v.to_string();
1280
1281    let (a, b) = span(&out, &format!("module logos_column_core_{name} "));
1282    let core = out[a..b].to_string();
1283    // Token-row b's word: its unit sums as token-row a's are chosen, and processing elements of its own.
1284    let s_act_line = line_with(&core, "] s_act = ");
1285    let parts = from_line(&core, "] w_p1, w_p2;", ".p1(w_p1), .p2(w_p2));\n");
1286    let b_word = format!(
1287        "  // Token-row b's word of the pass: its unit sums, and the pass's partials of the same codes through elements of its own.\n{}{}",
1288        suffixed(s_act_line, &["s_act", "act_word"], "_b"),
1289        suffixed(parts, &["s_act", "act_st", "w_p1", "w_p2", "parts"], "_b"),
1290    );
1291    // With pe_pairs the two words' passes run on one set of elements in clk2, each word unstaggered (`act_st`, `act_st_b`).
1292    let x2t = format!(
1293        "  // Token-row b's unit sums, and both token-rows' partials of the beat's codes on one set of elements in clk2, token-row\n  \
1294         // a's word at its edge between clk's and b's at clk's next.\n{}  wire [{t24}:0] w_p1, w_p2, w_p1_b, w_p2_b;\n  \
1295         logos_matvec_pass_x2t #(.UNITS({u})) parts(.clk(clk), .clk2(clk2), .code1(code1), .code2(code2), .wa(act_st), .wb(act_st_b),\n    \
1296         .sa(s_act), .sb(s_act_b), .digit(digit), .p1a(w_p1), .p2a(w_p2), .p1b(w_p1_b), .p2b(w_p2_b));\n",
1297        suffixed(s_act_line, &["s_act", "act_word"], "_b"),
1298        t24 = 24 * u - 1,
1299    );
1300    let la_in = between(&core, "  wire [LA-1:0] la_in = ", ";\n");
1301    let lf_b: String = core.lines().filter(|l| l.contains("= lfa[")).map(|l| format!("{}\n", suffixed(l, &["lfa", "f_m", "f_e", "f_s", "f_ms1", "f_ms2", "f_ms3", "f_ms4", "f_ms5", "f_ms6", "f_ms7"], "_b"))).collect();
1302    let last_lfa = core.lines().filter(|l| l.contains("= lfa[")).last().expect("the word's lane fields").to_string();
1303    let sums_names = ["a1_hi", "a2_hi", "a1_lo", "a2_lo", "t1_hi", "t2_hi", "t1_lo", "t2_lo", "as_hi", "as_lo", "ts_hi", "ts_lo", "sh1", "sh2", "b1_hi", "b2_hi", "b1_lo", "b2_lo", "u", "w_p1", "w_p2", "f_s"];
1304    let sums = between(&core, "  // The pass's partials, its digit's in their place", "as_hi <= ts_hi; as_lo <= ts_lo;\n  end\n");
1305    let sums_b = suffixed(&sums.replacen("  // The pass's partials, its digit's in their place, added to its word's sums so far (none at an issue's first pass).\n", "  // Token-row b's partials and sums, as token-row a's.\n", 1), &sums_names, "_b");
1306    let lanes = between(&core, "  wire [31:0] value1, value2;\n", ".value(value2));\n");
1307    let lane_names = ["p1_hi", "p2_hi", "p1_lo", "p2_lo", "s_hi", "s_lo", "lm", "le", "value1", "value2", "l1", "l2"];
1308    let accs = between(&core, "  (* ramstyle = \"logic\" *) reg [31:0] acc1 [0:SUMS-1];\n", "logos_fadd_p add2(.clk(clk), .a(acc2[land]), .b(value2), .y(sum2));\n");
1309    let acc_names = ["acc1", "acc2", "sum1", "sum2", "add1", "add2", "value1", "value2"];
1310    let wide = set.words == Words::Two;
1311    let zero = |w: usize| if w > 0 { format!("{{{w}{{1'b0}}}}, ") } else { String::new() };
1312    let (pad24, pad12) = (zero(24 * u - 48), zero(12 * u - 24));
1313    let mut lane_in = format!(
1314        "  // Token-row b's lanes' inputs: its sums over the issue's passes{}; the beat's fields are the issue's.\n  \
1315         reg [{t24}:0] p1_hi_b, p2_hi_b, p1_lo_b, p2_lo_b;\n  reg [{t12}:0] s_hi_b, s_lo_b;\n  reg [7:0] lm_b;\n  reg signed [11:0] le_b;\n  \
1316         always @(posedge clk) begin\n",
1317        if set.octets { ", or for an octet job from its slot" } else { "" },
1318        t24 = 24 * u - 1,
1319        t12 = 12 * u - 1,
1320    );
1321    if set.octets {
1322        lane_in.push_str(&format!("    if (oct) begin\n      p1_hi_b <= {{{pad24}ob_p1h_b}};\n      p2_hi_b <= {{{pad24}ob_p2h_b}};\n"));
1323        if wide {
1324            lane_in.push_str(&format!("      p1_lo_b <= {{{pad24}ob_p1l_b}};\n      p2_lo_b <= {{{pad24}ob_p2l_b}};\n      s_lo_b <= {{{pad12}ob_sl_b}};\n"));
1325        } else {
1326            lane_in.push_str("      p1_lo_b <= 0; p2_lo_b <= 0; s_lo_b <= 0;\n");
1327        }
1328        lane_in.push_str(&format!("      s_hi_b <= {{{pad12}ob_sh_b}};\n      lm_b <= ob_me_b[7:0];\n      le_b <= ob_me_b[19:8];\n"));
1329        for j in 1..set.lanes {
1330            lane_in.push_str(&format!(
1331                "      sl{j}_p1h_b <= sb{j}_p1h_b; sl{j}_p2h_b <= sb{j}_p2h_b; sl{j}_p1l_b <= sb{j}_p1l_b; sl{j}_p2l_b <= sb{j}_p2l_b;\n      \
1332                 sl{j}_sh_b <= sb{j}_sh_b; sl{j}_sl_b <= sb{j}_sl_b; sl{j}_me_b <= sb{j}_me_b;\n"
1333            ));
1334        }
1335        lane_in.push_str("    end else begin\n");
1336    }
1337    lane_in.push_str(
1338        "    p1_hi_b <= t1_hi_b; p2_hi_b <= t2_hi_b; p1_lo_b <= t1_lo_b; p2_lo_b <= t2_lo_b;\n    s_hi_b <= ts_hi_b; s_lo_b <= ts_lo_b;\n    lm_b <= f_m_b;\n    le_b <= f_e_b;\n",
1339    );
1340    if set.octets {
1341        lane_in.push_str("    end\n");
1342    }
1343    lane_in.push_str("  end\n");
1344    let mut edits: Vec<(String, String)> = vec![
1345        (
1346            format!("input [{}:0] act_word, input [{}:0] act_st,", aw - 1, cw - 1),
1347            format!("input [{a}:0] act_word, input [{c}:0] act_st,\n   input [{a}:0] act_word_b, input [{c}:0] act_st_b,", a = aw - 1, c = cw - 1),
1348        ),
1349        (
1350            "output reg o_valid, output reg [31:0] o_val1, output reg [31:0] o_val2);".to_string(),
1351            "output reg o_valid, output reg [31:0] o_val1, output reg [31:0] o_val2,\n   output reg [31:0] o_val1_b, output reg [31:0] o_val2_b);".to_string(),
1352        ),
1353        if set.pe_pairs { (parts.to_string(), x2t) } else { (parts.to_string(), format!("{parts}{b_word}")) },
1354        (la_in.to_string(), format!("{la_in}{}", suffixed(la_in, &["la_in", "act_word", "s_act"], "_b"))),
1355        ("  wire [LA-1:0] lfa;\n".to_string(), "  wire [LA-1:0] lfa, lfa_b;\n".to_string()),
1356        (
1357            "  logos_delay #(.W(LA), .D(PL)) lad(.clk(clk), .d(la_in), .q(lfa));\n".to_string(),
1358            "  logos_delay #(.W(LA), .D(PL)) lad(.clk(clk), .d(la_in), .q(lfa));\n  logos_delay #(.W(LA), .D(PL)) lad_b(.clk(clk), .d(la_in_b), .q(lfa_b));\n".to_string(),
1359        ),
1360        (format!("{last_lfa}\n"), format!("{last_lfa}\n{lf_b}")),
1361        (sums.to_string(), format!("{sums}{sums_b}")),
1362        ("  wire [31:0] value1, value2;\n".to_string(), format!("{lane_in}  wire [31:0] value1, value2;\n")),
1363        (lanes.to_string(), format!("{lanes}{}", suffixed(lanes, &lane_names, "_b"))),
1364        (accs.to_string(), format!("{accs}{}", suffixed(accs, &acc_names, "_b"))),
1365        if set.lanes > 1 {
1366            (
1367                "      acc2[done] <= last ? 32'd0 : (oct ? chain2 : sum2);\n      \
1368                 if (last) begin o_valid <= 1'b1; o_val1 <= oct ? chain1 : sum1; o_val2 <= oct ? chain2 : sum2; end\n"
1369                    .to_string(),
1370                "      acc2[done] <= last ? 32'd0 : (oct ? chain2 : sum2);\n      acc1_b[done] <= last ? 32'd0 : (oct ? chain1_b : sum1_b);\n      \
1371                 acc2_b[done] <= last ? 32'd0 : (oct ? chain2_b : sum2_b);\n      \
1372                 if (last) begin\n        o_valid <= 1'b1; o_val1 <= oct ? chain1 : sum1; o_val2 <= oct ? chain2 : sum2;\n        \
1373                 o_val1_b <= oct ? chain1_b : sum1_b; o_val2_b <= oct ? chain2_b : sum2_b;\n      end\n"
1374                    .to_string(),
1375            )
1376        } else {
1377            (
1378                "      acc2[done] <= last ? 32'd0 : sum2;\n      if (last) begin o_valid <= 1'b1; o_val1 <= sum1; o_val2 <= sum2; end\n".to_string(),
1379                "      acc2[done] <= last ? 32'd0 : sum2;\n      acc1_b[done] <= last ? 32'd0 : sum1_b;\n      acc2_b[done] <= last ? 32'd0 : sum2_b;\n      \
1380                 if (last) begin o_valid <= 1'b1; o_val1 <= sum1; o_val2 <= sum2; o_val1_b <= sum1_b; o_val2_b <= sum2_b; end\n"
1381                    .to_string(),
1382            )
1383        },
1384        (
1385            "for (k = 0; k < SUMS; k = k + 1) begin acc1[k] <= 32'd0; acc2[k] <= 32'd0; end".to_string(),
1386            "for (k = 0; k < SUMS; k = k + 1) begin acc1[k] <= 32'd0; acc2[k] <= 32'd0; acc1_b[k] <= 32'd0; acc2_b[k] <= 32'd0; end".to_string(),
1387        ),
1388    ];
1389    if set.lanes > 1 {
1390        let SlotFields { p1h, p2h, p1l, p2l, sh, sl, me, .. } = slot_fields(set);
1391        let mut fields = String::from("  // Token-row b's octet lanes past the first: its next blocks' sums and scales out of its slot's read, the blocks' own\n  // scales the pair's.\n");
1392        for j in 1..set.lanes {
1393            let lo = if wide {
1394                format!("    sb{j}_p1l_b <= o_rd_b[{p1l} + 48*o_k{j} +: 48]; sb{j}_p2l_b <= o_rd_b[{p2l} + 48*o_k{j} +: 48]; sb{j}_sl_b <= o_rd_b[{sl} + 24*o_k{j} +: 24];\n")
1395            } else {
1396                format!("    sb{j}_p1l_b <= 48'd0; sb{j}_p2l_b <= 48'd0; sb{j}_sl_b <= 24'd0;\n")
1397            };
1398            fields.push_str(&format!(
1399                "  reg [47:0] sb{j}_p1h_b, sb{j}_p2h_b, sb{j}_p1l_b, sb{j}_p2l_b, sl{j}_p1h_b, sl{j}_p2h_b, sl{j}_p1l_b, sl{j}_p2l_b;\n  \
1400                 reg [23:0] sb{j}_sh_b, sb{j}_sl_b, sl{j}_sh_b, sl{j}_sl_b;\n  reg [19:0] sb{j}_me_b, sl{j}_me_b;\n  always @(posedge clk) begin\n    \
1401                 sb{j}_p1h_b <= o_rd_b[{p1h} + 48*o_k{j} +: 48]; sb{j}_p2h_b <= o_rd_b[{p2h} + 48*o_k{j} +: 48];\n    \
1402                 sb{j}_sh_b <= o_rd_b[{sh} + 24*o_k{j} +: 24]; sb{j}_me_b <= o_rd_b[{me} + 20*o_k{j} +: 20];\n{lo}  end\n"
1403            ));
1404        }
1405        let chain = between(&core, "  // The octet lanes past the first: lane j's value", &format!("  wire [31:0] chain2 = ch2_{};\n", set.lanes - 1));
1406        let mut names: Vec<String> = vec!["sum1".into(), "sum2".into(), "chain1".into(), "chain2".into()];
1407        for r in 1..=2 {
1408            for j in 0..set.lanes {
1409                for n in ["ch", "sv", "slim", "dv", "dk", "chain"] {
1410                    names.push(format!("{n}{r}_{j}"));
1411                }
1412            }
1413        }
1414        for j in 1..set.lanes {
1415            for f in ["p1h", "p2h", "p1l", "p2l", "sh", "sl", "me"] {
1416                names.push(format!("sl{j}_{f}"));
1417            }
1418        }
1419        let names: Vec<&str> = names.iter().map(String::as_str).collect();
1420        let chain_b = suffixed(chain, &names, "_b").replacen("  // The octet lanes past the first: lane j's value", "  // Token-row b's octet lanes past the first, as token-row a's: lane j's value", 1);
1421        edits.extend([
1422            ("  // The lanes' inputs: the sums over the issue's passes".to_string(), format!("{fields}  // The lanes' inputs: the sums over the issue's passes")),
1423            (chain.to_string(), format!("{chain}{chain_b}")),
1424        ]);
1425    }
1426    if set.octets {
1427        let SlotFields { p1h, p2h, p1l, p2l, sh, sl, me, .. } = slot_fields(set);
1428        let o_in: String = core.lines().filter(|l| l.starts_with("  assign o_in[")).map(|l| format!("{l}\n")).collect();
1429        let o_in_b = suffixed(&o_in, &["o_in", "t1_hi", "t2_hi", "t1_lo", "t2_lo", "ts_hi", "ts_lo", "f_e", "f_m", "f_ms1", "f_ms2", "f_ms3", "f_ms4", "f_ms5", "f_ms6", "f_ms7"], "_b");
1430        let read_b = if wide {
1431            format!("    ob_p1l_b <= o_rd_b[{p1l} + 48*o_k +: 48]; ob_p2l_b <= o_rd_b[{p2l} + 48*o_k +: 48]; ob_sl_b <= o_rd_b[{sl} + 24*o_k +: 24];\n")
1432        } else {
1433            String::new()
1434        };
1435        edits.extend([
1436            ("  wire [OW-1:0] o_in;\n".to_string(), "  wire [OW-1:0] o_in, o_in_b;\n".to_string()),
1437            (o_in.clone(), format!("{o_in}{o_in_b}")),
1438            (
1439                "  `LOGOS_RAMSTYLE_ASYNC((2 << SW)) reg [OW-1:0] obuf [0:(2 << SW)-1];\n".to_string(),
1440                "  `LOGOS_RAMSTYLE_ASYNC((2 << SW)) reg [OW-1:0] obuf [0:(2 << SW)-1];\n  `LOGOS_RAMSTYLE_ASYNC((2 << SW)) reg [OW-1:0] obuf_b [0:(2 << SW)-1];\n".to_string(),
1441            ),
1442            (
1443                "  wire [OW-1:0] o_rd = obuf[eslot_d[PL-1]];\n".to_string(),
1444                "  wire [OW-1:0] o_rd = obuf[eslot_d[PL-1]], o_rd_b = obuf_b[eslot_d[PL-1]];\n".to_string(),
1445            ),
1446            (
1447                "  reg [19:0] ob_me;\n".to_string(),
1448                "  reg [19:0] ob_me;\n  reg [47:0] ob_p1h_b, ob_p2h_b, ob_p1l_b, ob_p2l_b;\n  reg [23:0] ob_sh_b, ob_sl_b;\n  reg [19:0] ob_me_b;\n".to_string(),
1449            ),
1450            (
1451                "    if (cap_d[PL]) obuf[wslot_d[PL]] <= o_in;\n".to_string(),
1452                "    if (cap_d[PL]) begin obuf[wslot_d[PL]] <= o_in; obuf_b[wslot_d[PL]] <= o_in_b; end\n".to_string(),
1453            ),
1454            (
1455                format!("ob_me <= o_rd[{me} + 20*o_k +: 20];\n"),
1456                format!(
1457                    "ob_me <= o_rd[{me} + 20*o_k +: 20];\n    ob_p1h_b <= o_rd_b[{p1h} + 48*o_k +: 48]; ob_p2h_b <= o_rd_b[{p2h} + 48*o_k +: 48];\n    \
1458                     ob_sh_b <= o_rd_b[{sh} + 24*o_k +: 24]; ob_me_b <= o_rd_b[{me} + 20*o_k +: 20];\n{read_b}"
1459                ),
1460            ),
1461        ]);
1462    }
1463    if set.pe_pairs {
1464        edits.push(("(input clk, input rst, input clear".to_string(), "(input clk, input clk2, input rst, input clear".to_string()));
1465    }
1466    let pairs_note = "// With token pairs (ColumnSet::token_pairs) each issue runs the beat held against two token-rows, a and b (the `_b`\n\
1467                      // signals): a word, processing elements, lane fields, sums over the passes, lanes and sums of each, the beat's fields\n\
1468                      // and the issue's flags shared, a sum's place a token pair's.\n";
1469    let mut core = replaced(&core, &edits.iter().map(|(a, b)| (a.as_str(), b.as_str())).collect::<Vec<_>>());
1470    core.insert_str(0, pairs_note);
1471    out.replace_range(a..b, &core);
1472
1473    let (a, b) = span(&out, &format!("module logos_column_array_{name} "));
1474    let array = out[a..b].to_string();
1475    let st_full = between(&array, &format!("      wire [{}:0] act_st;\n", cw - 1), &format!("      assign act_sts[ch*{cw} +: {cw}] = act_st;\n"));
1476    // A core whose elements run in clk2 staggers its words itself: each channel's words go to it unstaggered.
1477    let st_new = if set.pe_pairs {
1478        let at = st_full.find(".codes(").expect("the stagger's codes") + ".codes(".len();
1479        let codes = &st_full[at..at + st_full[at..].find("), .st(act_st));").expect("the stagger's codes' end")];
1480        format!(
1481            "      // The channel's words for its cores' elements in clk2, unstaggered.\n      wire [{c}:0] act_st = {codes};\n      \
1482             assign act_sts[ch*{cw} +: {cw}] = act_st;\n",
1483            c = cw - 1
1484        )
1485    } else {
1486        st_full.to_string()
1487    };
1488    let wm = between(&array, "wd <= act_wd; wm <= ", "; ra <= ra_n[AA-1:0]; end");
1489    let wm_expr = &wm["wd <= act_wd; wm <= ".len()..wm.len() - "; ra <= ra_n[AA-1:0]; end".len()];
1490    let wb = aw / 8;
1491    let mut edits: Vec<(String, String)> = vec![
1492        (
1493            "output [CH*N*32-1:0] o_val2, output reg [15:0] o_stripe,".to_string(),
1494            "output [CH*N*32-1:0] o_val2,\n   output [CH*N*32-1:0] o_val1_b, output [CH*N*32-1:0] o_val2_b, output reg [15:0] o_stripe,".to_string(),
1495        ),
1496        ("  localparam TW = (T_MAX > 1) ? $clog2(T_MAX) : 1;\n".to_string(), "  localparam TW = (T_MAX > 2) ? $clog2(T_MAX) - 1 : 1;\n".to_string()),
1497        ("  wire [TW-1:0] at = t[TW-1:0];\n".to_string(), "  wire [TW-1:0] at = t[TW:1];\n".to_string()),
1498        ("t_up <= t_n + 8'd1;".to_string(), "t_up <= t_n + 8'd2;".to_string()),
1499        ("  wire [31:0] ra_n = ({24'd0, t_n} << NB_L) | ({16'd0, hb_n} & (NB_P - 1));\n".to_string(), "  wire [31:0] ra_n = ({24'd0, t_n >> 1} << NB_L) | ({16'd0, hb_n} & (NB_P - 1));\n".to_string()),
1500        ("  localparam AN = T_MAX * NB_P,".to_string(), "  localparam AN = ((T_MAX + 1) / 2) * NB_P,".to_string()),
1501        ("  wire [31:0] act_wa = ({24'd0, act_t} << NB_L) | (act_b & (NB_P - 1));\n".to_string(), "  wire [31:0] act_wa = ({24'd0, act_t >> 1} << NB_L) | (act_b & (NB_P - 1));\n".to_string()),
1502        (
1503            format!("      reg [{}:0] wd;\n      reg [{}:0] wm;\n", aw - 1, wb - 1),
1504            format!("      reg [{}:0] wd;\n      reg [{}:0] wm;\n", 2 * aw - 1, 2 * wb - 1),
1505        ),
1506        (
1507            format!("wd <= act_wd; wm <= {wm_expr}; ra <= ra_n[AA-1:0]; end"),
1508            format!("wd <= {{act_wd, act_wd}}; wm <= act_t[0] ? {{{wm_expr}, {{{wb}{{1'b0}}}}}} : {{{{{wb}{{1'b0}}}}, {wm_expr}}}; ra <= ra_n[AA-1:0]; end"),
1509        ),
1510        (
1511            format!("      wire [{}:0] act_word;\n      {} #(.D(AN), .A(AA)) act_ram(.clk(clk), .ra(ra), .rd(act_word),", aw - 1, super::ram::bram_name(aw)),
1512            format!(
1513                "      wire [{p}:0] act_pair;\n      {} #(.D(AN), .A(AA)) act_ram(.clk(clk), .ra(ra), .rd(act_pair),",
1514                super::ram::bram_name(2 * aw),
1515                p = 2 * aw - 1
1516            ),
1517        ),
1518        (
1519            format!("      assign act_words[ch*{aw} +: {aw}] = act_word;\n"),
1520            format!(
1521                "      wire [{t}:0] act_word = act_pair[{t}:0], act_word_b = act_pair[{}:{aw}];\n      assign act_words[ch*{aw} +: {aw}] = act_word;\n      \
1522                 assign act_words_b[ch*{aw} +: {aw}] = act_word_b;\n",
1523                2 * aw - 1,
1524                t = aw - 1
1525            ),
1526        ),
1527        (st_full.to_string(), format!("{st_new}{}", suffixed(&st_new, &["act_st", "st", "act_word", "act_sts"], "_b"))),
1528        (
1529            format!("  wire [CH*{cw}-1:0] act_sts;\n"),
1530            format!("  wire [CH*{cw}-1:0] act_sts, act_sts_b;\n  wire [CH*{aw}-1:0] act_words_b;\n"),
1531        ),
1532        ("#(.SUMS(T_MAX), .SW(TW),".to_string(), "#(.SUMS((T_MAX + 1) / 2), .SW(TW),".to_string()),
1533        (
1534            format!(".act_word(act_words[(g/N)*{aw} +: {aw}]), "),
1535            format!(".act_word(act_words[(g/N)*{aw} +: {aw}]), .act_word_b(act_words_b[(g/N)*{aw} +: {aw}]),\n        "),
1536        ),
1537        (
1538            format!(".act_st(act_sts[(g/N)*{cw} +: {cw}]), "),
1539            format!(".act_st(act_sts[(g/N)*{cw} +: {cw}]), .act_st_b(act_sts_b[(g/N)*{cw} +: {cw}]), "),
1540        ),
1541        (
1542            ".o_val1(o_val1[g*32 +: 32]), .o_val2(o_val2[g*32 +: 32]));".to_string(),
1543            ".o_val1(o_val1[g*32 +: 32]), .o_val2(o_val2[g*32 +: 32]),\n        .o_val1_b(o_val1_b[g*32 +: 32]), .o_val2_b(o_val2_b[g*32 +: 32]));".to_string(),
1544        ),
1545        (
1546            "    if (issue) begin t <= t_last ? 8'd0 : t + 8'd1; t_last <= t_last ? tokens == 8'd1 : t + 8'd2 == tokens; end\n".to_string(),
1547            "    if (issue) begin t <= t_last ? 8'd0 : t + 8'd2; t_last <= t_last ? tokens <= 8'd2 : t + 8'd4 >= tokens; end\n".to_string(),
1548        ),
1549        ("t_last <= tokens == 8'd1; ready <=".to_string(), "t_last <= tokens <= 8'd2; ready <=".to_string()),
1550    ];
1551    if set.octets {
1552        edits.extend([
1553            ("  wire [TW-1:0] eat = et[TW-1:0];\n".to_string(), "  wire [TW-1:0] eat = et[TW:1];\n".to_string()),
1554            ("&& et + 8'd1 == tokens;\n".to_string(), "&& et + 8'd2 >= tokens;\n".to_string()),
1555            (
1556                format!("      if (et + 8'd1 == tokens) begin et <= 8'd0; eb <= eb + 3'd{l}; end else et <= et + 8'd1;\n", l = set.lanes),
1557                format!("      if (et + 8'd2 >= tokens) begin et <= 8'd0; eb <= eb + 3'd{l}; end else et <= et + 8'd2;\n", l = set.lanes),
1558            ),
1559        ]);
1560    }
1561    if set.pe_pairs {
1562        edits.push(("u(.clk(clk), .rst(rst), .clear(start && !busy),".to_string(), "u(.clk(clk), .clk2(clk2), .rst(rst), .clear(start && !busy),".to_string()));
1563    }
1564    let note = "// With token pairs (ColumnSet::token_pairs) an issue is a token pair's, t even, and each core takes the pair's two words\n\
1565                // from its channel's copy of the activations, a word a pair (the first token-row's in its low half), with each half's\n\
1566                // excess codes staggered for its elements; a sum's place is the pair's, and each core's sums emerge a pair a clock,\n\
1567                // the second token-row's (o_val1_b, o_val2_b) the one after o_t, a job's only when it has it.\n";
1568    let mut array = replaced(&array, &edits.iter().map(|(a, b)| (a.as_str(), b.as_str())).collect::<Vec<_>>());
1569    array.insert_str(0, note);
1570    out.replace_range(a..b, &array);
1571    out
1572}
1573
1574/// `text` with every `(from, to)` of `edits` made, each `from` there exactly once.
1575fn replaced(text: &str, edits: &[(&str, &str)]) -> String {
1576    let mut out = text.to_string();
1577    for (from, to) in edits {
1578        assert_eq!(out.matches(from).count(), 1, "`{from}`");
1579        out = out.replace(from, to);
1580    }
1581    out
1582}
1583
1584/// What a set with octets adds to its core (`logos_column_core_<name>`): the ports its array drives an octet job by,
1585/// the slots an issue's sums wait in, and the lanes' inputs taken from a slot's block; nothing for a set without.
1586struct CoreOctets {
1587    ports: String,
1588    body: String,
1589    lanes: String,
1590    lanes_end: String,
1591    from: &'static str,
1592}
1593
1594/// Where each field lies in an octet core's slot, from its lowest bit: both rows' unit sums over the passes (each
1595/// word's), the activation's unit sums, both rows' unit sums of codes, both rows' eight scales, the activation's eight
1596/// scales; `ow` the slot's bits.
1597struct SlotFields {
1598    p1h: usize,
1599    p2h: usize,
1600    p1l: usize,
1601    p2l: usize,
1602    sh: usize,
1603    sl: usize,
1604    q1: usize,
1605    q2: usize,
1606    sc1: usize,
1607    sc2: usize,
1608    me: usize,
1609    ow: usize,
1610}
1611
1612fn slot_fields(set: &ColumnSet) -> SlotFields {
1613    let u = set.elems() / UNIT;
1614    let words = if set.words == Words::Two { 2 } else { 1 };
1615    let p1h = 0;
1616    let p2h = p1h + 24 * u;
1617    let p1l = p2h + 24 * u;
1618    let p2l = p1l + 24 * u * (words - 1);
1619    let sh = p2l + 24 * u * (words - 1);
1620    let sl = sh + 12 * u;
1621    let q1 = sl + 12 * u * (words - 1);
1622    let q2 = q1 + 12 * u;
1623    let sc1 = q2 + 12 * u;
1624    let sc2 = sc1 + 128;
1625    let me = sc2 + 128;
1626    SlotFields { p1h, p2h, p1l, p2l, sh, sl, q1, q2, sc1, sc2, me, ow: me + 160 }
1627}
1628
1629fn core_octets(set: &ColumnSet) -> CoreOctets {
1630    if !set.octets {
1631        return CoreOctets { ports: String::new(), body: String::new(), lanes: String::new(), lanes_end: String::new(), from: "" };
1632    }
1633    let u = set.elems() / UNIT;
1634    let wide = set.words == Words::Two;
1635    let SlotFields { p1h, p2h, p1l, p2l, sh, sl, q1, q2, sc1, sc2, me, ow } = slot_fields(set);
1636    let mut body = format!(
1637        "  // An octet job (oct): each issue's sums over its passes, its blocks' eight scales and its activation's eight kept in\n  \
1638         // its slot (wslot) the clock after its last pass (capture); a slot's block (eslot, eblk) into the lanes the clock\n  \
1639         // after it is asked, its two units as a block's first two, the rest zero, with its scale and its activation's.\n  \
1640         localparam OW = {ow};\n  reg [PL:0] cap_d;\n  reg [SW:0] wslot_d [0:PL];\n  integer cw;\n  \
1641         always @(posedge clk) begin\n    cap_d <= rst ? {{(PL+1){{1'b0}}}} : {{cap_d[PL-1:0], capture}};\n    wslot_d[0] <= wslot;\n    \
1642         for (cw = 1; cw <= PL; cw = cw + 1) wslot_d[cw] <= wslot_d[cw-1];\n  end\n  wire [OW-1:0] o_in;\n  \
1643         assign o_in[{p1h} +: {w24}] = t1_hi;\n  assign o_in[{p2h} +: {w24}] = t2_hi;\n",
1644        w24 = 24 * u
1645    );
1646    if wide {
1647        body.push_str(&format!("  assign o_in[{p1l} +: {w24}] = t1_lo;\n  assign o_in[{p2l} +: {w24}] = t2_lo;\n", w24 = 24 * u));
1648    }
1649    body.push_str(&format!("  assign o_in[{sh} +: {w12}] = ts_hi;\n", w12 = 12 * u));
1650    if wide {
1651        body.push_str(&format!("  assign o_in[{sl} +: {w12}] = ts_lo;\n", w12 = 12 * u));
1652    }
1653    body.push_str(&format!(
1654        "  assign o_in[{q1} +: {w12}] = f_q1;\n  assign o_in[{q2} +: {w12}] = f_q2;\n  \
1655         assign o_in[{sc1} +: 128] = {{f_s71, f_a1}};\n  assign o_in[{sc2} +: 128] = {{f_s72, f_a2}};\n  \
1656         assign o_in[{me} +: 20] = {{f_e, f_m}};\n",
1657        w12 = 12 * u,
1658    ));
1659    for k in 1..8 {
1660        body.push_str(&format!("  assign o_in[{} +: 20] = f_ms{k};\n", me + 20 * k));
1661    }
1662    let wide_sel = if wide {
1663        format!("    ob_p1l <= o_rd[{p1l} + 48*o_k +: 48]; ob_p2l <= o_rd[{p2l} + 48*o_k +: 48]; ob_sl <= o_rd[{sl} + 24*o_k +: 24];\n")
1664    } else {
1665        String::new()
1666    };
1667    body.push_str(&format!(
1668        "  // A block asked for leaves the slots PL clocks on, as an issue's value meets the lanes PL clocks after its pass,\n  \
1669         // its fields taken out of its slot as the slot is read.\n  \
1670         `LOGOS_RAMSTYLE_ASYNC((2 << SW)) reg [OW-1:0] obuf [0:(2 << SW)-1];\n  reg [SW:0] eslot_d [0:PL-1];\n  reg [2:0] eblk_d [0:PL-1];\n  \
1671         wire [OW-1:0] o_rd = obuf[eslot_d[PL-1]];\n  wire [2:0] o_k = eblk_d[PL-1];\n  \
1672         reg [47:0] ob_p1h, ob_p2h, ob_p1l, ob_p2l;\n  reg [23:0] ob_sh, ob_sl, ob_q1, ob_q2;\n  reg [15:0] ob_a1, ob_a2;\n  \
1673         reg [19:0] ob_me;\n  integer ce;\n  always @(posedge clk) begin\n    \
1674         if (cap_d[PL]) obuf[wslot_d[PL]] <= o_in;\n    eslot_d[0] <= eslot; eblk_d[0] <= eblk;\n    \
1675         for (ce = 1; ce < PL; ce = ce + 1) begin eslot_d[ce] <= eslot_d[ce-1]; eblk_d[ce] <= eblk_d[ce-1]; end\n    \
1676         ob_p1h <= o_rd[{p1h} + 48*o_k +: 48]; ob_p2h <= o_rd[{p2h} + 48*o_k +: 48];\n    \
1677         ob_sh <= o_rd[{sh} + 24*o_k +: 24]; ob_q1 <= o_rd[{q1} + 24*o_k +: 24]; ob_q2 <= o_rd[{q2} + 24*o_k +: 24];\n    \
1678         ob_a1 <= o_rd[{sc1} + 16*o_k +: 16]; ob_a2 <= o_rd[{sc2} + 16*o_k +: 16]; ob_me <= o_rd[{me} + 20*o_k +: 20];\n{wide_sel}  end\n",
1679    ));
1680    let mut takes = String::new();
1681    if set.lanes > 1 {
1682        let mut fields = String::from(
1683            "  // The octet lanes past the first: a token-row's next blocks of its group, o_k + 1 on, out of the same slot's read.\n",
1684        );
1685        for j in 1..set.lanes {
1686            writeln!(fields, "  reg [47:0] sb{j}_p1h, sb{j}_p2h, sb{j}_p1l, sb{j}_p2l;\n  reg [23:0] sb{j}_sh, sb{j}_sl, sb{j}_q1, sb{j}_q2;\n  reg [15:0] sb{j}_a1, sb{j}_a2;\n  reg [19:0] sb{j}_me;").unwrap();
1687            writeln!(fields, "  reg [47:0] sl{j}_p1h, sl{j}_p2h, sl{j}_p1l, sl{j}_p2l;\n  reg [23:0] sl{j}_sh, sl{j}_sl, sl{j}_q1, sl{j}_q2;\n  reg [15:0] sl{j}_a1, sl{j}_a2;\n  reg [19:0] sl{j}_me;").unwrap();
1688            writeln!(fields, "  reg [2:0] o_k{j};\n  always @(posedge clk) o_k{j} <= eblk_d[PL-2] + 3'd{j};").unwrap();
1689            let lo = if wide {
1690                format!(
1691                    "    sb{j}_p1l <= o_rd[{p1l} + 48*o_k{j} +: 48]; sb{j}_p2l <= o_rd[{p2l} + 48*o_k{j} +: 48]; sb{j}_sl <= o_rd[{sl} + 24*o_k{j} +: 24];\n"
1692                )
1693            } else {
1694                format!("    sb{j}_p1l <= 48'd0; sb{j}_p2l <= 48'd0; sb{j}_sl <= 24'd0;\n")
1695            };
1696            writeln!(
1697                fields,
1698                "  always @(posedge clk) begin\n    sb{j}_p1h <= o_rd[{p1h} + 48*o_k{j} +: 48]; sb{j}_p2h <= o_rd[{p2h} + 48*o_k{j} +: 48];\n    \
1699                 sb{j}_sh <= o_rd[{sh} + 24*o_k{j} +: 24]; sb{j}_q1 <= o_rd[{q1} + 24*o_k{j} +: 24]; sb{j}_q2 <= o_rd[{q2} + 24*o_k{j} +: 24];\n    \
1700                 sb{j}_a1 <= o_rd[{sc1} + 16*o_k{j} +: 16]; sb{j}_a2 <= o_rd[{sc2} + 16*o_k{j} +: 16]; sb{j}_me <= o_rd[{me} + 20*o_k{j} +: 20];\n{lo}  end"
1701            )
1702            .unwrap();
1703            writeln!(
1704                takes,
1705                "      sl{j}_p1h <= sb{j}_p1h; sl{j}_p2h <= sb{j}_p2h; sl{j}_p1l <= sb{j}_p1l; sl{j}_p2l <= sb{j}_p2l; sl{j}_sh <= sb{j}_sh; sl{j}_sl <= sb{j}_sl;\n      \
1706                 sl{j}_q1 <= sb{j}_q1; sl{j}_q2 <= sb{j}_q2; sl{j}_a1 <= sb{j}_a1; sl{j}_a2 <= sb{j}_a2; sl{j}_me <= sb{j}_me;"
1707            )
1708            .unwrap();
1709        }
1710        body.push_str(&fields);
1711    }
1712    let zero = |w: usize| if w > 0 { format!("{{{w}{{1'b0}}}}, ") } else { String::new() };
1713    let pad24 = zero(24 * u - 48);
1714    let pad12 = zero(12 * u - 24);
1715    let mut lanes = format!(
1716        "    if (oct) begin\n      \
1717         p1_hi <= {{{pad24}ob_p1h}};\n      p2_hi <= {{{pad24}ob_p2h}};\n"
1718    );
1719    if wide {
1720        lanes.push_str(&format!("      p1_lo <= {{{pad24}ob_p1l}};\n      p2_lo <= {{{pad24}ob_p2l}};\n"));
1721        lanes.push_str(&format!("      s_lo <= {{{pad12}ob_sl}};\n"));
1722    } else {
1723        lanes.push_str("      p1_lo <= 0; p2_lo <= 0; s_lo <= 0;\n");
1724    }
1725    lanes.push_str(&format!(
1726        "      s_hi <= {{{pad12}ob_sh}};\n      q1 <= {{{pad12}ob_q1}};\n      q2 <= {{{pad12}ob_q2}};\n      \
1727         lc1 <= {{{pad8}16'h0101}}; ln1 <= 0; lc2 <= {{{pad8}16'h0101}}; ln2 <= 0;\n      \
1728         la1 <= ob_a1; lb1 <= 16'd0; la2 <= ob_a2; lb2 <= 16'd0;\n      \
1729         lm <= ob_me[7:0];\n      le <= ob_me[19:8];\n{takes}    end else begin\n",
1730        pad8 = zero(8 * u - 16)
1731    ));
1732    CoreOctets {
1733        ports: ", input oct, input capture, input [SW:0] wslot, input [SW:0] eslot, input [2:0] eblk".to_string(),
1734        body,
1735        lanes,
1736        lanes_end: "    end\n".to_string(),
1737        from: ", or for an octet job from the slot its array asks for",
1738    }
1739}
1740
1741/// What a set with octets adds to its array (`logos_column_array_<name>`), keyed by the template's placeholders; a set
1742/// without keeps the array as it is.
1743fn array_octets(set: &ColumnSet) -> Vec<(&'static str, String)> {
1744    let all = format!("{{{}{{1'b1}}}}", set.act_bits() / 8);
1745    if !set.octets {
1746        return vec![
1747            ("arr_act_s", String::new()),
1748            ("arr_octets", String::new()),
1749            ("pe_ok", "!stall".to_string()),
1750            ("arr_busy", String::new()),
1751            ("act_wm", all),
1752            ("core_issue", "issue && need[g]".to_string()),
1753            ("core_octet_conn", String::new()),
1754            ("tags_in", "    live[0] <= issue; tag[0] <= at; ts[0] <= hs; tt[0] <= t; last[0] <= step_end;".to_string()),
1755            ("arr_octet_step", String::new()),
1756            ("arr_octet_reset", String::new()),
1757        ];
1758    }
1759    let place = set.formats.iter().position(|&t| set.octet(t)).expect("a set with octets takes Q8_0");
1760    let oct = if set.formats.len() > 1 { format!("fmt_q == 4'd{place}") } else { "1'b1".to_string() };
1761    // The bytes an octet's block `k` writes of a word: each word's codes of the block, each word's unit sums of its two
1762    // units, and its scale.
1763    let (e, u, w) = (set.elems(), set.elems() / UNIT, set.word_count());
1764    let (a, ab) = (set.act_fields(), set.act_bits() / 8);
1765    let mask = |k: usize| -> String {
1766        let mut bytes = vec![false; ab];
1767        for word in 0..w {
1768            for b in word * e + 32 * k..word * e + 32 * k + 32 {
1769                bytes[b] = true;
1770            }
1771            let sums = (a.sums + word * 12 * u) / 8 + 3 * k;
1772            bytes[sums..sums + 3].iter_mut().for_each(|b| *b = true);
1773        }
1774        let scale = (a.m + 24 * k) / 8;
1775        bytes[scale..(scale + 3).min(ab)].iter_mut().for_each(|b| *b = true);
1776        let bits: String = bytes.iter().rev().map(|&b| if b { '1' } else { '0' }).collect();
1777        format!("{ab}'b{bits}")
1778    };
1779    let mut masks = String::new();
1780    for k in 0..8 {
1781        writeln!(masks, "      3'd{k}: slot_wm = {};", mask(k)).unwrap();
1782    }
1783    let octets = format!(
1784        r#"  // An octet job (oct): a step's issues fill one of two buffers, a slot a token-row, each core keeping its own sums; a
1785  // full buffer's blocks leave for the lanes a block a clock — every token-row's block 0, then every token-row's block
1786  // 1, ... — each the clock no add holds its sum, as an issue of the step's would; a step issues into a buffer once its
1787  // blocks have all left, and a buffer's first block leaves three clocks after its last issue, its slots written. A
1788  // block's activations are written into its octet's word under the mask of its bytes.
1789  wire oct = {oct};
1790  reg pbuf, ebuf;
1791  reg [1:0] full;
1792  `LOGOS_RAMSTYLE_ASYNC(2) reg [1:0] settle [0:1];
1793  `LOGOS_RAMSTYLE_ASYNC(2) reg [15:0] bs [0:1];
1794  `LOGOS_RAMSTYLE_ASYNC(2) reg bl [0:1];
1795  `LOGOS_RAMSTYLE_ASYNC(2) reg [C-1:0] bneed [0:1];
1796  reg [2:0] eb;
1797  reg [7:0] et;
1798  wire [TW-1:0] eat = et[TW-1:0];
1799  reg e_stall;
1800  integer h2;
1801  always @* begin
1802    e_stall = 1'b0;
1803    for (h2 = 0; h2 < {window}; h2 = h2 + 1) if (live[h2] && tag[h2] == eat) e_stall = 1'b1;
1804  end
1805  wire emit = oct && full[ebuf] && settle[ebuf] == 2'd0 && !hold && !e_stall;
1806  wire e_end = emit && eb == 3'd{last_group} && et + 8'd1 == tokens;
1807  reg [{abt}:0] slot_wm;
1808  always @*
1809    case (act_s)
1810{masks}      default: slot_wm = {{{ab}{{1'b0}}}};
1811    endcase
1812  wire [{abt}:0] act_wm = oct ? slot_wm : {all};
1813"#,
1814        abt = ab - 1,
1815        window = if set.lanes == 1 { "ADD".to_string() } else { format!("{} * ADD", set.lanes) },
1816        last_group = 8 - set.lanes,
1817    );
1818    vec![
1819        ("arr_act_s", ", input [2:0] act_s".to_string()),
1820        ("arr_octets", octets),
1821        ("pe_ok", "(oct ? !full[pbuf] : !stall)".to_string()),
1822        ("arr_busy", " || (|full)".to_string()),
1823        ("act_wm", "act_wm".to_string()),
1824        ("core_issue", "oct ? emit && bneed[ebuf][g] : issue && need[g]".to_string()),
1825        ("core_octet_conn", ", .oct(oct), .capture(oct && issue && need[g]), .wslot({pbuf, at}), .eslot({ebuf, eat}), .eblk(eb)".to_string()),
1826        (
1827            "tags_in",
1828            format!("    live[0] <= oct ? emit : issue;\n    tag[0] <= oct ? eat : at;\n    ts[0] <= oct ? bs[ebuf] : hs;\n    \
1829             tt[0] <= oct ? et : t;\n    last[0] <= oct ? bl[ebuf] && eb == 3'd{} : step_end;",
1830            8 - set.lanes
1831        ),
1832        ),
1833        (
1834            "arr_octet_step",
1835            format!("    if (settle[0] != 2'd0) settle[0] <= settle[0] - 2'd1;\n    if (settle[1] != 2'd0) settle[1] <= settle[1] - 2'd1;\n    \
1836             if (oct && fin) begin\n      full[pbuf] <= 1'b1; settle[pbuf] <= 2'd3; bs[pbuf] <= hs; bl[pbuf] <= step_end; bneed[pbuf] <= need;\n      \
1837             pbuf <= ~pbuf;\n    end\n    if (emit) begin\n      if (et + 8'd1 == tokens) begin et <= 8'd0; eb <= eb + 3'd{}; end else et <= et + 8'd1;\n      \
1838             if (e_end) begin full[ebuf] <= 1'b0; ebuf <= ~ebuf; end\n    end\n",
1839            set.lanes
1840        ),
1841        ),
1842        ("arr_octet_reset", " pbuf <= 1'b0; ebuf <= 1'b0; full <= 2'b00; eb <= 3'd0; et <= 8'd0;".to_string()),
1843    ]
1844}
1845
1846/// The decoder of an octet: eight Q8_0 blocks of a row, one after another, as one 256-element block — block `k`'s
1847/// codes in units 2k and 2k + 1, every unit's multiplier 1 and no offset term, block 0's scale `a` and blocks 1 to 7's
1848/// in `s7`, as eight `logos_dec_q8_0` decode them.
1849const OCTET_DECODER: &str = r#"
1850module logos_dec_q8_0x8(input [2175:0] blk, output [2047:0] code, output [255:0] c, output [255:0] n, output [15:0] a,
1851                        output [15:0] b, output [111:0] s7);
1852  genvar k;
1853  generate
1854    for (k = 0; k < 8; k = k + 1) begin : block
1855      wire [255:0] ck;
1856      wire [31:0] cc, nn;
1857      wire [15:0] ak, bk;
1858      logos_dec_q8_0 d(.blk(blk[272*k +: 272]), .code(ck), .c(cc), .n(nn), .a(ak), .b(bk));
1859      assign code[256*k +: 256] = ck;
1860      assign c[32*k +: 32] = cc;
1861      assign n[32*k +: 32] = nn;
1862      if (k == 0) begin : first
1863        assign a = ak;
1864      end else begin : later
1865        assign s7[16*(k-1) +: 16] = ak;
1866      end
1867    end
1868  endgenerate
1869  assign b = 16'd0;
1870endmodule
1871"#;
1872
1873/// A format as a set's column takes it: its decoder, and whether its blocks come in octets ([`ColumnSet::octets`]).
1874#[derive(Clone, Copy)]
1875struct Coded {
1876    dec: &'static Decoder,
1877    octet: bool,
1878    slot: usize,
1879}
1880
1881impl Coded {
1882    /// The elements of one of the column's blocks.
1883    fn len(&self) -> usize {
1884        if self.octet { 8 * self.dec.ty.block_len() } else { self.dec.ty.block_len() }
1885    }
1886
1887    /// The bytes of one of the column's blocks.
1888    fn bytes(&self) -> usize {
1889        if self.octet { 8 * self.dec.ty.block_bytes() } else { self.dec.ty.block_bytes() }
1890    }
1891
1892    /// The bytes one of the column's blocks takes in the image, its pad to the set's slot after it.
1893    fn slot_bytes(&self) -> usize {
1894        self.bytes().next_multiple_of(self.slot)
1895    }
1896
1897    /// The decoder module: the format's, or its octets' (`logos_dec_q8_0x8`).
1898    fn module(&self) -> String {
1899        if self.octet { format!("{}x8", decoder_name(self.dec.ty)) } else { decoder_name(self.dec.ty) }
1900    }
1901
1902    /// The format's name, an octet's with `x8`.
1903    fn name(&self) -> String {
1904        if self.octet { format!("{}x8", self.dec.ty.name()) } else { self.dec.ty.name().to_string() }
1905    }
1906}
1907
1908/// The decode of a beat: one format's decoders, or each format's with the job's chosen, a narrower format's codes and
1909/// multipliers zero past its own; an octet's decoder its blocks' scales past the first beside them (`s7`).
1910fn decode(decs: &[Coded], elems: usize) -> String {
1911    let (code_top, unit_top) = (8 * elems - 1, 16 * (elems / UNIT) - 1);
1912    let s7 = |d: &Coded, k: &str| if d.octet { format!(", .s7({k})") } else { String::new() };
1913    let octets = decs.iter().any(|d| d.octet);
1914    if let [d] = decs {
1915        let dec = d.module();
1916        return format!(
1917            "  wire [{code_top}:0] code1_d, code2_d;\n  wire [{unit_top}:0] c1_d, n1_d, c2_d, n2_d;\n  wire [15:0] a1_d, b1_d, a2_d, b2_d;\n{}  \
1918             {dec} d1(.blk(blk1), .code(code1_d), .c(c1_d), .n(n1_d), .a(a1_d), .b(b1_d){});\n  \
1919             {dec} d2(.blk(blk2), .code(code2_d), .c(c2_d), .n(n2_d), .a(a2_d), .b(b2_d){});\n",
1920            if octets { "  wire [111:0] s71_d, s72_d;\n" } else { "" },
1921            s7(d, "s71_d"),
1922            s7(d, "s72_d"),
1923        );
1924    }
1925    let mut v = String::new();
1926    for (i, d) in decs.iter().enumerate() {
1927        let (len, bits) = (d.len(), 8 * d.bytes());
1928        let (ct, ut) = (8 * len - 1, 16 * (len / UNIT) - 1);
1929        let dec = d.module();
1930        writeln!(v, "  wire [{ct}:0] code1_{i}, code2_{i};\n  wire [{ut}:0] c1_{i}, n1_{i}, c2_{i}, n2_{i};\n  wire [15:0] a1_{i}, b1_{i}, a2_{i}, b2_{i};").unwrap();
1931        if d.octet {
1932            writeln!(v, "  wire [111:0] s71_{i}, s72_{i};").unwrap();
1933        }
1934        writeln!(v, "  {dec} d1_{i}(.blk(blk1[{}:0]), .code(code1_{i}), .c(c1_{i}), .n(n1_{i}), .a(a1_{i}), .b(b1_{i}){});", bits - 1, s7(d, &format!("s71_{i}"))).unwrap();
1935        writeln!(v, "  {dec} d2_{i}(.blk(blk2[{}:0]), .code(code2_{i}), .c(c2_{i}), .n(n2_{i}), .a(a2_{i}), .b(b2_{i}){});", bits - 1, s7(d, &format!("s72_{i}"))).unwrap();
1936    }
1937    writeln!(v, "  reg [{code_top}:0] code1_d, code2_d;\n  reg [{unit_top}:0] c1_d, n1_d, c2_d, n2_d;\n  reg [15:0] a1_d, b1_d, a2_d, b2_d;").unwrap();
1938    if octets {
1939        v.push_str("  reg [111:0] s71_d, s72_d;\n");
1940    }
1941    v.push_str("  always @* begin\n    code1_d = 0; code2_d = 0; c1_d = 0; n1_d = 0; c2_d = 0; n2_d = 0; a1_d = 0; b1_d = 0; a2_d = 0; b2_d = 0;\n");
1942    if octets {
1943        v.push_str("    s71_d = 0; s72_d = 0;\n");
1944    }
1945    v.push_str("    case (fmt)\n");
1946    for (i, d) in decs.iter().enumerate() {
1947        let scales = if d.octet { format!(" s71_d = s71_{i}; s72_d = s72_{i};") } else { String::new() };
1948        writeln!(
1949            v,
1950            "      4'd{i}: begin code1_d = code1_{i}; code2_d = code2_{i}; c1_d = c1_{i}; n1_d = n1_{i}; c2_d = c2_{i}; n2_d = n2_{i}; \
1951             a1_d = a1_{i}; b1_d = b1_{i}; a2_d = a2_{i}; b2_d = b2_{i};{scales} end"
1952        )
1953        .unwrap();
1954    }
1955    v.push_str("      default: ;\n    endcase\n  end\n");
1956    v
1957}
1958
1959/// The job's format's code offset `z`: one format's, or the job's format's.
1960fn zsel(decs: &[Coded]) -> String {
1961    if let [d] = decs {
1962        return format!("  wire [7:0] z = 8'd{};\n", d.dec.offset);
1963    }
1964    let mut v = String::from("  reg [7:0] z;\n  always @*\n    case (fmt_q)\n");
1965    for (i, d) in decs.iter().enumerate() {
1966        writeln!(v, "      4'd{i}: z = 8'd{};", d.dec.offset).unwrap();
1967    }
1968    writeln!(v, "      default: z = 8'd{};\n    endcase", decs[0].dec.offset).unwrap();
1969    v
1970}
1971
1972/// The feed's block bytes `BB` and its gather register's bytes `GB`: one format's, or the job's format's beside room
1973/// for the set's widest.
1974fn feed_block_bytes(decs: &[Coded]) -> String {
1975    if let [d] = decs {
1976        return format!("  localparam [15:0] BB = 16'd{b};\n  localparam GB = {b} + 64;\n", b = d.slot_bytes());
1977    }
1978    let widest = decs.iter().map(Coded::slot_bytes).max().unwrap();
1979    // The job's format holds through its job and its first beat comes clocks after it is set: its block's bytes are
1980    // registered from the feed's copy.
1981    let mut v = format!("  localparam GB = {widest} + 64;\n  reg [15:0] BB;\n  always @(posedge clk)\n    case (fmt_q)\n");
1982    for (i, d) in decs.iter().enumerate() {
1983        writeln!(v, "      4'd{i}: BB <= 16'd{};", d.slot_bytes()).unwrap();
1984    }
1985    writeln!(v, "      default: BB <= 16'd{};\n    endcase", decs[0].slot_bytes()).unwrap();
1986    v
1987}
1988
1989/// The gather register with its first block dropped: by one format's block's slot, or by the job's format's.
1990fn feed_drop(decs: &[Coded]) -> String {
1991    if let [_] = decs {
1992        return "gather >> (8 * BB)".to_string();
1993    }
1994    let mut v = String::new();
1995    for (i, d) in decs.iter().enumerate().take(decs.len() - 1) {
1996        write!(v, "(fmt_q == 4'd{i}) ? gather >> {} : ", 8 * d.slot_bytes()).unwrap();
1997    }
1998    write!(v, "gather >> {}", 8 * decs[decs.len() - 1].slot_bytes()).unwrap();
1999    v
2000}
2001
2002/// The job's format's code digits, `digits`: one format's, or the job's format's.
2003fn digits(decs: &[Coded]) -> String {
2004    let of = |d: &Coded| if d.dec.code_bits > 4 { 2 } else { 1 };
2005    if let [d] = decs {
2006        return format!("  wire [1:0] digits = 2'd{};\n", of(d));
2007    }
2008    let mut v = String::from("  reg [1:0] digits;\n  always @*\n    case (fmt_q)\n");
2009    for (i, d) in decs.iter().enumerate() {
2010        writeln!(v, "      4'd{i}: digits = 2'd{};", of(d)).unwrap();
2011    }
2012    writeln!(v, "      default: digits = 2'd{};\n    endcase", of(&decs[0])).unwrap();
2013    v
2014}
2015
2016/// The activation codes of a pass as the systolic elements take them.
2017const ACT_STAGGER: &str = r#"
2018// A pass's excess codes for logos_pe8_p: code e of each eight given (e mod 8) / 2 clocks after it comes in, the element's
2019// block (e mod 8) / 2 taking its operands that many clocks after its block 0 does.
2020module logos_column_act_stagger #(parameter ELEMS = 256) (input clk, input [8*ELEMS-1:0] codes, output [8*ELEMS-1:0] st);
2021  reg [8*ELEMS-1:0] d1, d2, d3;
2022  always @(posedge clk) begin
2023    d1 <= codes;
2024    d2 <= d1;
2025    d3 <= d2;
2026  end
2027  genvar e;
2028  generate
2029    for (e = 0; e < ELEMS; e = e + 1) begin : code
2030      localparam K = (e % 8) / 2;
2031      assign st[8*e +: 8] = K == 0 ? codes[8*e +: 8] : K == 1 ? d1[8*e +: 8] : K == 2 ? d2[8*e +: 8] : d3[8*e +: 8];
2032    end
2033  endgenerate
2034endmodule
2035"#;
2036
2037/// One pass of a block of two rows' codes against one token-row's activation word: each 16-element unit's partial
2038/// sums for both rows from the systolic processing elements of one 4-bit code digit, the word's excess codes and their
2039/// unit sums given, [`PASS_LATENCY`] clocks after the word, the codes and their digit given a clock before it.
2040const MATVEC_PASS: &str = r#"
2041// A pass of two rows' codes against one activation word's excess codes `w`, for the user clock: digit `digit` of every
2042// code, given with the codes a clock before the word and registered, through the systolic elements; each 16-element
2043// unit's partial sums for both rows, row one's from the low lanes and row two's from the high lanes with its offset
2044// restored by the unit's sum `s` of the word's excess codes, @PE@ clocks after the word (`s` delayed beside the
2045// elements).
2046module logos_matvec_pass_p #(parameter UNITS = 16)
2047  (input clk, input [128*UNITS-1:0] code1, input [128*UNITS-1:0] code2, input [128*UNITS-1:0] w, input [12*UNITS-1:0] s,
2048   input digit, output [24*UNITS-1:0] p1, output [24*UNITS-1:0] p2);
2049  wire [12*UNITS-1:0] s_pe;
2050  logos_delay #(.W(12*UNITS), .D(@PE@)) sd(.clk(clk), .d(s), .q(s_pe));
2051  genvar i, u;
2052  generate
2053    for (u = 0; u < UNITS; u = u + 1) begin : unit
2054      wire [31:0] q1a, q2a, q1b, q2b;
2055      for (i = 0; i < 8; i = i + 1) begin : nib
2056        assign q1a[4*i +: 4] = digit ? code1[8*(16*u + i) + 4 +: 4] : code1[8*(16*u + i) +: 4];
2057        assign q2a[4*i +: 4] = digit ? code2[8*(16*u + i) + 4 +: 4] : code2[8*(16*u + i) +: 4];
2058        assign q1b[4*i +: 4] = digit ? code1[8*(16*u + 8 + i) + 4 +: 4] : code1[8*(16*u + 8 + i) +: 4];
2059        assign q2b[4*i +: 4] = digit ? code2[8*(16*u + 8 + i) + 4 +: 4] : code2[8*(16*u + 8 + i) +: 4];
2060      end
2061      reg [31:0] q1a_r, q2a_r, q1b_r, q2b_r;
2062      always @(posedge clk) begin q1a_r <= q1a; q2a_r <= q2a; q1b_r <= q1b; q2b_r <= q2b; end
2063      wire [14:0] lo_a, lo_b;
2064      wire signed [16:0] hi_a, hi_b;
2065      logos_pe8_p pa(.clk(clk), .a(w[8*16*u +: 64]), .q1(q1a_r), .q2(q2a_r), .lo(lo_a), .hi(hi_a));
2066      logos_pe8_p pb(.clk(clk), .a(w[8*(16*u + 8) +: 64]), .q1(q1b_r), .q2(q2b_r), .lo(lo_b), .hi(hi_b));
2067      wire [23:0] one = {9'd0, lo_a} + {9'd0, lo_b};
2068      wire signed [23:0] other = $signed(hi_a) + $signed(hi_b) + $signed({9'd0, s_pe[12*u +: 12], 3'd0});
2069      assign p1[24*u +: 24] = one;
2070      assign p2[24*u +: 24] = other;
2071    end
2072  endgenerate
2073endmodule
2074"#;
2075
2076/// `logos_matvec_pass_x2`: two cores' passes, each `logos_matvec_pass_p`'s at its latency ([`PASS_LATENCY`]), on one set
2077/// of processing elements in a clock of twice the cores'.
2078pub fn matvec_pass_x2_verilog() -> String {
2079    MATVEC_PASS_X2.replace("@PE@", &PASS_LATENCY.to_string())
2080}
2081
2082// The pair's sums are realigned to the cores' clock for a chain of four blocks: core a's leave the elements CHAIN + 2
2083// clocks of clk2 after its word, core b's one later, and two of clk and one bring them to PASS_LATENCY.
2084const _: () = assert!(CHAIN == 4 && PASS_LATENCY == CHAIN + 1);
2085
2086const MATVEC_PASS_X2: &str = r#"
2087// Two cores' passes of logos_matvec_pass_p on one set of systolic elements in a clock of twice theirs (clk2, its rising
2088// edges on clk's and between them): the word the cores share (`w`, unstaggered) and core a's code digits taken at
2089// clk2's edge between clk's, core b's at clk's next edge, the elements' blocks staggered in clk2; each core's partials
2090// as logos_matvec_pass_p gives them, @PE@ clocks of clk after its word, the codes and their digit given a clock before it.
2091module logos_matvec_pass_x2 #(parameter UNITS = 16)
2092  (input clk, input clk2,
2093   input [128*UNITS-1:0] code1a, input [128*UNITS-1:0] code2a, input [128*UNITS-1:0] code1b, input [128*UNITS-1:0] code2b,
2094   input [128*UNITS-1:0] w, input [12*UNITS-1:0] s, input digit,
2095   output [24*UNITS-1:0] p1a, output [24*UNITS-1:0] p2a, output [24*UNITS-1:0] p1b, output [24*UNITS-1:0] p2b);
2096  wire [12*UNITS-1:0] s_pe;
2097  logos_delay #(.W(12*UNITS), .D(@PE@)) sd(.clk(clk), .d(s), .q(s_pe));
2098  // Core a's turn on clk2: the clock after its edge between clk's, a toggle of clk's seen changed.
2099  reg tf = 1'b0, tf2 = 1'b0, a_turn = 1'b0;
2100  always @(posedge clk) tf <= ~tf;
2101  always @(posedge clk2) begin tf2 <= tf; a_turn <= tf != tf2; end
2102  // The word, held across both turns of its clock.
2103  reg [128*UNITS-1:0] w0;
2104  always @(posedge clk2) w0 <= w;
2105  genvar i, u;
2106  generate
2107    for (u = 0; u < UNITS; u = u + 1) begin : unit
2108      // Each core's digits of the two rows, registered on clk a clock before the word, then held on clk2 for its turn.
2109      wire [63:0] da, db, ea, eb;
2110      for (i = 0; i < 16; i = i + 1) begin : nib
2111        assign da[4*i +: 4] = digit ? code1a[8*(16*u + i) + 4 +: 4] : code1a[8*(16*u + i) +: 4];
2112        assign db[4*i +: 4] = digit ? code1b[8*(16*u + i) + 4 +: 4] : code1b[8*(16*u + i) +: 4];
2113        assign ea[4*i +: 4] = digit ? code2a[8*(16*u + i) + 4 +: 4] : code2a[8*(16*u + i) +: 4];
2114        assign eb[4*i +: 4] = digit ? code2b[8*(16*u + i) + 4 +: 4] : code2b[8*(16*u + i) +: 4];
2115      end
2116      reg [127:0] ra, rb, ha, hb;
2117      always @(posedge clk) begin ra <= {ea, da}; rb <= {eb, db}; end
2118      always @(posedge clk2) begin
2119        if (!a_turn) ha <= ra;
2120        if (a_turn) hb <= rb;
2121      end
2122      wire [127:0] q = a_turn ? ha : hb;
2123      // The word's codes for the unit's two elements, each block of two codes the clocks of clk2 its place in the chain.
2124      reg [127:0] w1, w2, w3;
2125      always @(posedge clk2) begin w1 <= w0[8*16*u +: 128]; w2 <= w1; w3 <= w2; end
2126      wire [127:0] st;
2127      for (i = 0; i < 16; i = i + 1) begin : stag
2128        localparam K = (i % 8) / 2;
2129        assign st[8*i +: 8] = K == 0 ? w0[8*(16*u + i) +: 8] : K == 1 ? w1[8*i +: 8] : K == 2 ? w2[8*i +: 8] : w3[8*i +: 8];
2130      end
2131      wire [14:0] lo_a, lo_b;
2132      wire signed [16:0] hi_a, hi_b;
2133      logos_pe8_p pa(.clk(clk2), .a(st[0 +: 64]), .q1(q[0 +: 32]), .q2(q[64 +: 32]), .lo(lo_a), .hi(hi_a));
2134      logos_pe8_p pb(.clk(clk2), .a(st[64 +: 64]), .q1(q[32 +: 32]), .q2(q[96 +: 32]), .lo(lo_b), .hi(hi_b));
2135      wire [23:0] one = {9'd0, lo_a} + {9'd0, lo_b};
2136      wire signed [23:0] other = $signed(hi_a) + $signed(hi_b);
2137      // Core a's sums caught at the edge between clk's after them, core b's at clk's edge after them, then on clk.
2138      reg [47:0] ca, cb, fa, fa2, fb;
2139      always @(posedge clk2) begin
2140        if (!a_turn) ca <= {other, one};
2141        if (a_turn) cb <= {other, one};
2142      end
2143      always @(posedge clk) begin fa <= ca; fa2 <= fa; fb <= cb; end
2144      assign p1a[24*u +: 24] = fa2[23:0];
2145      assign p2a[24*u +: 24] = $signed(fa2[47:24]) + $signed({9'd0, s_pe[12*u +: 12], 3'd0});
2146      assign p1b[24*u +: 24] = fb[23:0];
2147      assign p2b[24*u +: 24] = $signed(fb[47:24]) + $signed({9'd0, s_pe[12*u +: 12], 3'd0});
2148    end
2149  endgenerate
2150endmodule
2151"#;
2152
2153/// `logos_matvec_pass_x2t`: a core's two token-rows' passes, each `logos_matvec_pass_p`'s at its latency
2154/// ([`PASS_LATENCY`]), on one set of processing elements in a clock of twice the core's.
2155pub fn matvec_pass_x2t_verilog() -> String {
2156    MATVEC_PASS_X2T.replace("@PE@", &PASS_LATENCY.to_string())
2157}
2158const MATVEC_PASS_X2T: &str = r#"
2159// A core's two token-rows' passes of logos_matvec_pass_p on one set of systolic elements in a clock of twice theirs (clk2,
2160// its rising edges on clk's and between them): the beat's codes, the same for both, and token-row a's word (`wa`,
2161// unstaggered) taken at clk2's edge between clk's, token-row b's (`wb`) at clk's next edge, the elements' blocks staggered
2162// in clk2; each token-row's partials as logos_matvec_pass_p gives them, @PE@ clocks of clk after its word, the codes and
2163// their digit given a clock before it.
2164module logos_matvec_pass_x2t #(parameter UNITS = 16)
2165  (input clk, input clk2, input [128*UNITS-1:0] code1, input [128*UNITS-1:0] code2,
2166   input [128*UNITS-1:0] wa, input [128*UNITS-1:0] wb, input [12*UNITS-1:0] sa, input [12*UNITS-1:0] sb, input digit,
2167   output [24*UNITS-1:0] p1a, output [24*UNITS-1:0] p2a, output [24*UNITS-1:0] p1b, output [24*UNITS-1:0] p2b);
2168  wire [12*UNITS-1:0] sa_pe, sb_pe;
2169  logos_delay #(.W(12*UNITS), .D(@PE@)) sda(.clk(clk), .d(sa), .q(sa_pe));
2170  logos_delay #(.W(12*UNITS), .D(@PE@)) sdb(.clk(clk), .d(sb), .q(sb_pe));
2171  // Token-row a's turn on clk2: the clock after its edge between clk's, a toggle of clk's seen changed.
2172  reg tf = 1'b0, tf2 = 1'b0, a_turn = 1'b0;
2173  always @(posedge clk) tf <= ~tf;
2174  always @(posedge clk2) begin tf2 <= tf; a_turn <= tf != tf2; end
2175  // Each turn's word, taken at the edge before its turn.
2176  reg [128*UNITS-1:0] w0;
2177  always @(posedge clk2) w0 <= a_turn ? wb : wa;
2178  genvar i, u;
2179  generate
2180    for (u = 0; u < UNITS; u = u + 1) begin : unit
2181      // The beat's digits of the two rows, registered on clk a clock before the words, then held on clk2 for each turn.
2182      wire [63:0] d, e;
2183      for (i = 0; i < 16; i = i + 1) begin : nib
2184        assign d[4*i +: 4] = digit ? code1[8*(16*u + i) + 4 +: 4] : code1[8*(16*u + i) +: 4];
2185        assign e[4*i +: 4] = digit ? code2[8*(16*u + i) + 4 +: 4] : code2[8*(16*u + i) +: 4];
2186      end
2187      reg [127:0] r, ha, hb;
2188      always @(posedge clk) r <= {e, d};
2189      always @(posedge clk2) begin
2190        if (!a_turn) ha <= r;
2191        if (a_turn) hb <= r;
2192      end
2193      wire [127:0] q = a_turn ? ha : hb;
2194      // The turn's word's codes for the unit's two elements, each block of two codes the clocks of clk2 its place in the
2195      // chain.
2196      reg [127:0] w1, w2, w3;
2197      always @(posedge clk2) begin w1 <= w0[8*16*u +: 128]; w2 <= w1; w3 <= w2; end
2198      wire [127:0] st;
2199      for (i = 0; i < 16; i = i + 1) begin : stag
2200        localparam K = (i % 8) / 2;
2201        assign st[8*i +: 8] = K == 0 ? w0[8*(16*u + i) +: 8] : K == 1 ? w1[8*i +: 8] : K == 2 ? w2[8*i +: 8] : w3[8*i +: 8];
2202      end
2203      wire [14:0] lo_a, lo_b;
2204      wire signed [16:0] hi_a, hi_b;
2205      logos_pe8_p pa(.clk(clk2), .a(st[0 +: 64]), .q1(q[0 +: 32]), .q2(q[64 +: 32]), .lo(lo_a), .hi(hi_a));
2206      logos_pe8_p pb(.clk(clk2), .a(st[64 +: 64]), .q1(q[32 +: 32]), .q2(q[96 +: 32]), .lo(lo_b), .hi(hi_b));
2207      wire [23:0] one = {9'd0, lo_a} + {9'd0, lo_b};
2208      wire signed [23:0] other = $signed(hi_a) + $signed(hi_b);
2209      // Token-row a's sums caught at the edge between clk's after them, b's at clk's edge after them, then on clk.
2210      reg [47:0] ca, cb, fa, fa2, fb;
2211      always @(posedge clk2) begin
2212        if (!a_turn) ca <= {other, one};
2213        if (a_turn) cb <= {other, one};
2214      end
2215      always @(posedge clk) begin fa <= ca; fa2 <= fa; fb <= cb; end
2216      assign p1a[24*u +: 24] = fa2[23:0];
2217      assign p2a[24*u +: 24] = $signed(fa2[47:24]) + $signed({9'd0, sa_pe[12*u +: 12], 3'd0});
2218      assign p1b[24*u +: 24] = fb[23:0];
2219      assign p2b[24*u +: 24] = $signed(fb[47:24]) + $signed({9'd0, sb_pe[12*u +: 12], 3'd0});
2220    end
2221  endgenerate
2222endmodule
2223"#;
2224
2225/// An activation block as its words' excess codes and their unit sums: what every core's processing elements read.
2226const ACT_WORD: &str = r#"
2227// A token-row's activation block as block RAM holds it for the column: each activation word's excess codes — the high
2228// (or only) word in excess 128, the low byte of a two-word code as it is — and each 16-element unit's sum of them, then
2229// SLOTS scales' significands and exponents, 24 bits a scale, the scale `m`·2^`e` at `slot` and zeros at the others (a
2230// word of an octet's eight blocks is written a block at a time under a mask of its bytes); built for one word (WORDS 1),
2231// `two` is never set.
2232module logos_column_act_word #(parameter ELEMS = 256, WORDS = 1, SLOTS = 1)
2233  (input [16*ELEMS-1:0] codes, input two, input [7:0] m, input [11:0] e, input [2:0] slot,
2234   output [((WORDS*(8*ELEMS + 12*(ELEMS/16)) + 20 + 24*(SLOTS-1) + 7)/8)*8-1:0] word);
2235  localparam UNITS = ELEMS / 16, SUMS = WORDS * 8 * ELEMS, SCALE = SUMS + WORDS * 12 * UNITS;
2236  localparam TOP = ((WORDS*(8*ELEMS + 12*(ELEMS/16)) + 20 + 24*(SLOTS-1) + 7)/8)*8, SCALES = 20 + 24 * (SLOTS - 1);
2237  // A unit's sixteen excess codes summed in a balanced tree of four adds.
2238  function [11:0] sum16(input [127:0] c);
2239    reg [8:0] a0, a1, a2, a3, a4, a5, a6, a7;
2240    reg [9:0] b0, b1, b2, b3;
2241    reg [10:0] d0, d1;
2242    begin
2243      a0 = c[7:0] + c[15:8]; a1 = c[23:16] + c[31:24]; a2 = c[39:32] + c[47:40]; a3 = c[55:48] + c[63:56];
2244      a4 = c[71:64] + c[79:72]; a5 = c[87:80] + c[95:88]; a6 = c[103:96] + c[111:104]; a7 = c[119:112] + c[127:120];
2245      b0 = a0 + a1; b1 = a2 + a3; b2 = a4 + a5; b3 = a6 + a7;
2246      d0 = b0 + b1; d1 = b2 + b3;
2247      sum16 = d0 + d1;
2248    end
2249  endfunction
2250  wire [8*ELEMS-1:0] hi, lo;
2251  genvar i, u;
2252  generate
2253    for (i = 0; i < ELEMS; i = i + 1) begin : element
2254      wire [15:0] code = codes[16*i +: 16];
2255      assign hi[8*i +: 8] = (WORDS > 1 && two) ? (code[15:8] + 8'd128) : (code[7:0] + 8'd128);
2256      assign lo[8*i +: 8] = (WORDS > 1 && two) ? code[7:0] : 8'd0;
2257    end
2258    for (u = 0; u < UNITS; u = u + 1) begin : unit
2259      wire [11:0] sh = sum16(hi[128*u +: 128]), sl = sum16(lo[128*u +: 128]);
2260      assign word[SUMS + 12*u +: 12] = sh;
2261      if (WORDS > 1) begin : low_sum
2262        assign word[SUMS + 12*UNITS + 12*u +: 12] = sl;
2263      end
2264    end
2265    if (WORDS > 1) begin : low_word
2266      assign word[16*ELEMS-1:8*ELEMS] = lo;
2267    end
2268    for (u = 0; u < SLOTS; u = u + 1) begin : scale
2269      localparam [2:0] AT = u;
2270      assign word[SCALE + 24*u +: 8] = (slot == AT) ? m : 8'd0;
2271      assign word[SCALE + 24*u + 8 +: 12] = (slot == AT) ? e : 12'd0;
2272      if (u + 1 < SLOTS) begin : gap
2273        assign word[SCALE + 24*u + 20 +: 4] = 4'd0;
2274      end
2275    end
2276    if (TOP > SCALE + SCALES) begin : pad
2277      assign word[TOP-1:SCALE+SCALES] = {(TOP-SCALE-SCALES){1'b0}};
2278    end
2279  endgenerate
2280  assign word[8*ELEMS-1:0] = hi;
2281endmodule
2282"#;