Skip to main content

logicaffeine_compile/spatial/decode_engine/rtl/
engine.rs

1//! The engine as Verilog: the sequencer running the machine's jobs ([`crate::spatial::decode_engine::machine`]) over
2//! the token-row store and the card's memory, the units doing each job's work.
3//!
4//! The host writes the engine's tables ([`tables`]) through its port, an entry a cycle: the jobs' words (table 0), the
5//! step's row table (1: each row's region, its position and its ring index), the store's slots (2: each slot's first
6//! beat and beats a row), the tensors (3) and the rotary tables (4), each block's place in a region (5), and the store
7//! (6), which it reads back a beat at a time. A step runs from the word `job_at` to the word `job_end`, a job after
8//! another; the host has put each row's embedding in the store, so the engine starts after the embedding's job. The card's memory is a port of whole 64-byte lines, read
9//! a line a request and written a line at a time under a byte mask. A job starts at a word, runs to its end and stops;
10//! the engine is busy from a step's start until its last job is done and every write the step made is on the card.
11//!
12//! A vector job's passes each load the vector unit's registers a beat at a time — a token-row's beat from the store,
13//! a tensor's or a rotary table's line from memory, a ring row's line as the state stores it (binary16 halves widened)
14//! — run the job's instructions one after another on [`super::simd`], then store a beat at a time: into the store under
15//! a lane mask, or into memory as the sink keeps it (f32, binary16 halves, int8 codes, a binary16 scale) under a byte
16//! mask. The vector unit's registers and scalars carry over between passes; a pass reads only what it loads and what
17//! its own instructions write, so what a pass leaves never reaches the next. A slot's lanes past the width of the
18//! vector a job last wrote there are no vector's: nothing reads them, so a job neither clears nor keeps them.
19
20use std::collections::{BTreeMap, HashMap};
21use std::fmt::Write as _;
22
23use crate::model::quant::GgmlType;
24use crate::model::spec::{Mixer, ModelSpec};
25use crate::spatial::decode_engine::machine::{encode, BlockState, Fold, Job, Layout, LogFields, Row, StoreLayout, LANES, LINE};
26use crate::spatial::decode_engine::model::{CacheFormat, StateFormat};
27use crate::spatial::decode_engine::numerics::{ActScaling, FpUnit, Words};
28use crate::spatial::decode_engine::vector::{Bin, Func, Op};
29use crate::spatial::serving::StateFlush;
30use super::ram::STORE_READ_LATENCY;
31
32use crate::codegen_sva::fp_hard::{self, hard_verilog, hard_verilog_for, plan, plan_for, Block, HardError};
33use crate::codegen_sva::fp_hypotheses::Semantics;
34use crate::codegen_sva::fp_oracle::{hard_model_verilog, Depths};
35
36use super::column::ColumnSet;
37use super::fp32::{FADD_LATENCY, FMUL_LATENCY};
38use super::super::image::ImageLayout;
39
40/// The engine's tables for one step, as the host writes them.
41#[derive(Clone, Debug, PartialEq)]
42pub struct Tables {
43    /// The jobs' words ([`crate::spatial::decode_engine::machine::encode`]).
44    pub jobs: Vec<u64>,
45    /// Per row: its region's first line `[31:0]`, its position `[63:32]`, its ring index `[71:64]`.
46    pub rows: Vec<u128>,
47    /// Per slot code: its first beat `[31:0]`, its beats a row `[63:32]`.
48    pub slots: Vec<u64>,
49    /// Per tensor: its first line `[31:0]`, its ggml type `[39:32]`, its row's elements `[63:40]`, its rows `[87:64]`.
50    pub tensors: Vec<u128>,
51    /// Per rotary table: its first line `[31:0]`, its pairs `[63:32]`.
52    pub ropes: Vec<u64>,
53    /// Per layer, sixteen 32-bit fields from the lowest. A Gated-DeltaNet block's: its ring's offset in bytes, its
54    /// channels, its ring rows, its memory's and its log's offsets, a log entry's bytes, its key and value heads, its
55    /// width, its log entry's values, decay exponents and gates (elements), the tokens of a flush (0 for a memory
56    /// rewritten every pass), its read-out's scale's bits. An attention block's: its keys' and values' offsets, its key
57    /// and value scales' offsets, its cached heads, its widths of key and value. The last field 0 for a Gated-DeltaNet
58    /// block, 1 for an attention block.
59    pub blocks: Vec<[u32; 16]>,
60    /// Whether the state stores binary16, whether the cache stores int8 codes, whether an activation crosses the array
61    /// in two words, whether activations are scaled per weight block.
62    pub state_f16: bool,
63    pub cache_int8: bool,
64    pub two_words: bool,
65    pub per_block: bool,
66}
67
68/// The tokens a Gated-DeltaNet memory carried as `flush` logs before the engine replays them: 1 for a memory rewritten
69/// every pass. A unit that replays a whole log at once runs these; a trailing flush runs on head lanes alone
70/// ([`recurrence_log`]).
71pub fn logged(flush: StateFlush) -> usize {
72    match flush {
73        StateFlush::EveryPass => 1,
74        StateFlush::Every { tokens } => tokens as usize,
75        StateFlush::Trailing { .. } => panic!("a trailing flush, which only the recurrence's head lanes replay"),
76    }
77}
78
79/// The part of a streaming unit's name a recurrence carrying its memories as `flush` adds: `+behind<T>` for a trailing
80/// flush of `T`, nothing otherwise.
81pub fn behind(flush: Option<StateFlush>) -> String {
82    match flush {
83        Some(StateFlush::Trailing { tokens }) => format!("+behind{tokens}"),
84        _ => String::new(),
85    }
86}
87
88/// The tokens a recurrence unit's log holds at its longest and the window a trailing flush replays a window behind:
89/// [`logged`]'s, or a trailing flush's two windows and its window.
90pub fn recurrence_log(flush: StateFlush) -> (usize, Option<usize>) {
91    match flush {
92        StateFlush::Trailing { tokens } => (2 * tokens as usize, Some(tokens as usize)),
93        f => (logged(f), None),
94    }
95}
96
97/// The tables of `jobs` over `layout`, the store laid out as `store`, for the step's `rows`.
98pub fn tables(layout: &Layout, store: &StoreLayout, jobs: &[Job], rows: &[Row]) -> Tables {
99    tables_at(layout, store, jobs, rows, &|at| Some(at))
100}
101
102/// [`tables`] with every address the engine reads the card's memory at — a tensor's, a rotary table's, a sequence's
103/// region — placed by `place`: the hybrid engine's tables over its compacted memory
104/// ([`crate::spatial::decode_engine::physical::Compaction::place`]). A tensor `place` gives no place, one the hybrid
105/// engine streams and never reads by address, has address 0.
106pub fn tables_at(layout: &Layout, store: &StoreLayout, jobs: &[Job], rows: &[Row], place: &dyn Fn(u64) -> Option<u64>) -> Tables {
107    let placed = |at: u64| place(at).expect("a place for every region and rotary table");
108    let line = |bytes: u64| -> u32 {
109        assert_eq!(bytes % LINE, 0, "a place off a line");
110        u32::try_from(bytes / LINE).expect("a line within 32 bits")
111    };
112    Tables {
113        jobs: encode(jobs),
114        rows: rows
115            .iter()
116            .map(|r| line(placed(layout.region_of(r.sequence))) as u128 | (r.position as u128) << 32 | (r.ring as u128) << 64)
117            .collect(),
118        slots: store.slots.iter().map(|&(base, beats)| base as u64 | (beats as u64) << 32).collect(),
119        tensors: layout
120            .tensors
121            .iter()
122            .map(|t| {
123                let dims = |i: usize| t.dims.get(i).copied().unwrap_or(1) as u128;
124                line(place(t.at).unwrap_or(0)) as u128 | (t.ty.id() as u128) << 32 | dims(0) << 40 | dims(1) << 64
125            })
126            .collect(),
127        ropes: layout.rotary.iter().map(|(rope, at)| line(placed(*at)) as u64 | ((rope.dims / 2) as u64) << 32).collect(),
128        blocks: layout
129            .blocks
130            .iter()
131            .map(|b| match *b {
132                BlockState::Delta { ring, channels, taps, memory, log, entry, k_heads, v_heads, width } => {
133                    let f = LogFields::of(k_heads, v_heads, width);
134                    let tokens = match layout.numerics.flush {
135                        StateFlush::EveryPass => 0,
136                        StateFlush::Trailing { tokens } => tokens,
137                        flush => logged(flush) as u32,
138                    };
139                    [
140                        ring as u32,
141                        channels as u32,
142                        taps.saturating_sub(1) as u32,
143                        memory as u32,
144                        log.unwrap_or(0) as u32,
145                        entry as u32,
146                        k_heads as u32,
147                        v_heads as u32,
148                        width as u32,
149                        f.values as u32,
150                        f.log_decays as u32,
151                        f.gates as u32,
152                        tokens,
153                        ((1.0 / (width as f64).sqrt()) as f32).to_bits(),
154                        0,
155                        0,
156                    ]
157                }
158                BlockState::Attention { keys, values, key_scales, value_scales, kv_heads, width, value_width } => [
159                    keys as u32,
160                    values as u32,
161                    key_scales.unwrap_or(0) as u32,
162                    value_scales.unwrap_or(0) as u32,
163                    kv_heads as u32,
164                    width as u32,
165                    value_width as u32,
166                    0,
167                    0,
168                    0,
169                    0,
170                    0,
171                    0,
172                    0,
173                    0,
174                    1,
175                ],
176            })
177            .collect(),
178        state_f16: layout.numerics.state == StateFormat::F16,
179        cache_int8: layout.numerics.cache != CacheFormat::F16,
180        two_words: layout.numerics.activations == Words::Two,
181        per_block: layout.numerics.scaling == ActScaling::PerBlock,
182    }
183}
184
185/// What an engine is printed for: the most token-rows a step holds, the most beats a vector register holds and the
186/// most a Hadamard or rotary operand does, the store's beats, the words of the step's jobs, the tables' entries,
187/// every block format its matrices are in with the most blocks a row of that format holds, and the function units its
188/// vector unit's sixteen lanes share.
189#[derive(Clone, Debug, PartialEq)]
190pub struct EngineParams {
191    pub rows: usize,
192    pub vector_rows: usize,
193    /// The beats the vector unit's side holds a register to: the deepest vector job it cannot run a chunk at a time
194    /// ([`super::super::machine::VectorJob::chunked`]); a deeper job runs that many rows of its vectors at a time. The
195    /// reference engine's own vector unit runs every job whole, as deep as [`EngineParams::vector_rows`].
196    pub side_rows: usize,
197    /// The beats of the vector unit's pair copies: a far Hadamard stage's partner rows and a rotary block's rows are
198    /// rows of the instruction's operand, so they are within the longest operand a Hadamard or rotary job loads.
199    pub pair_rows: usize,
200    pub store_beats: usize,
201    pub job_words: usize,
202    pub tensors: usize,
203    pub layers: usize,
204    pub ropes: usize,
205    pub formats: Vec<(GgmlType, usize)>,
206    /// The Gated-DeltaNet blocks' head width and how their memories are carried ([`logged`]), or `None` for a model of
207    /// none.
208    pub delta: Option<(usize, StateFlush)>,
209    /// The attention blocks' query and value head widths, the most positions a sequence reaches, and whether the math
210    /// runs on the array, or `None` for a model of none.
211    pub attend: Option<(usize, usize, usize, bool)>,
212    /// The vector unit's function units, a divisor of sixteen: a function's row takes `16 / function_units` cycles.
213    pub function_units: usize,
214    /// Whether a matrix job folds its sums into its output slot ([`super::super::machine::Fold`]): a step lowered with
215    /// its residual adds and gated products folded ([`super::super::machine::fold`]).
216    pub folds: bool,
217    /// The streaming unit a hybrid engine runs its matvec jobs on, or `None` for the reference engine, which reads each
218    /// job's matrix a line at a time through its memory port.
219    pub streaming: Option<Streaming>,
220}
221
222/// The streaming unit of a hybrid engine ([`super::streaming::streaming_verilog`]), in the fast clock beside the
223/// engine's: the column set it is built for and the image layout its matrices lie in on the channels, whose stripe is
224/// the row pairs a channel's share of a stripe holds; and whether the recurrence unit and the vector unit's side stand
225/// beside it.
226#[derive(Clone, Debug, PartialEq)]
227pub struct Streaming {
228    pub set: ColumnSet,
229    pub layout: ImageLayout,
230    /// Whether a lazily flushed memory's token-rows run on the fast side too (`logos_gdn_side` on the channels through
231    /// `logos_fast_port`, [`GdnJob`]): each row's log entry and count, fold, read-out and flush at the channels' rate in
232    /// the user clock, the core handing the row over and waiting for it. The unit walks a head's memory a row of whole
233    /// lines at a time, so it serves heads whose binary16 rows are whole lines, a width a multiple of 32.
234    pub recurrence: bool,
235    /// The sixteens of elements a clock the recurrence unit's read-out and flush take: 1 half a binary16 line
236    /// (`logos_gdn_read_p`, `logos_gdn_flush_p`), 2 a whole line (`logos_gdn_read_w`, `logos_gdn_flush_w`, the flush
237    /// holding a head's rows and running its dot and update passes beside each other).
238    pub recurrence_width: usize,
239    /// The head lanes the recurrence unit runs a row's value heads on: 1 the side alone; more a coordinator and that
240    /// many lanes (`logos_gdn_lanes`, [`super::gdn::lanes_verilog`]), lane k taking value heads k, k + lanes, … through
241    /// its own fast port, the lanes' ports and the shared one on the channels at once (`logos_channel_mux`), so with
242    /// four lanes and a head a granule each lane reads and writes a channel of its own.
243    pub recurrence_lanes: usize,
244    /// The lines each lane's copy of the log holds ([`super::super::machine::Layout::log_lines`] of the layouts the
245    /// build runs), a lanes build only: a row whose log would take more faults the recurrence unit, which runs none of
246    /// it.
247    pub log_lines: usize,
248    /// The flush units each head lane splits a flushing head's rows over, a lanes build at a binary16 line a clock only:
249    /// each unit holds HD / flushes of the rows through the same passes over the log, so a flushing head's passes take
250    /// 1 / flushes of the clocks and every row flushes as one unit flushes it. 1 a unit a lane.
251    pub recurrence_flushes: usize,
252    /// The lines the recurrence's coordinator and each head lane ask ahead of those they have taken, a lanes build only
253    /// ([`super::gdn::LANE_CREDIT`] unless given): behind a memory that answers `latency` clocks after an ask, a lane
254    /// reads at most `credit / latency` lines a clock, so a credit past the channels' latency reads a line a clock.
255    pub recurrence_credit: usize,
256    /// Whether the vector jobs run on the fast side too (`logos_vector_side`, [`super::vector_side`], on the recurrence
257    /// unit's port, [`VectorSideJob`]): each job's loads, micro-program and stores in the user clock on the user clock's
258    /// vector unit, the core handing the job over and waiting for it.
259    pub vectors: bool,
260    /// Whether the attention jobs run on the fast side too (`logos_attend_side`, [`super::attend`], on the recurrence
261    /// unit's port, [`AttendJob`]): each token-row's query heads quantized and attended over the int8 cache in the user
262    /// clock, the core handing each row over and waiting for it; the core keeps no attention unit or quantizer.
263    pub attention: bool,
264    /// The card's floating-point DSP block the fast side's f32 multiply and add are built on ([`HardFloat`]), or `None`
265    /// for both in logic.
266    pub hard_float: Option<HardFloat>,
267    /// The token-rows the vector unit's side holds the inputs of while a float matrix's weights stream past
268    /// ([`super::vector_side::FM_ROWS`] unless given): 16 lanes a row, the weights read once for each group.
269    pub float_rows: usize,
270    /// The passes of a job whose passes stand alone ([`super::super::machine::VectorJob::grouped`]) the vector unit's
271    /// side runs at once, each pass's registers rows and scalars of its own (`logos_simd_p`'s `PASSES`), so a pass's
272    /// reductions and functions fill the pipeline its one chain leaves idle: 1 a pass at a time; at most the add's
273    /// latency and one, the slots a round of the unit's reductions interleaves.
274    pub vector_passes: usize,
275    /// The passes of the deepest vector job it runs whole the vector unit's side's registers hold
276    /// ([`Streaming::with_vector_depth`]): its unit that many times as deep as that job's vectors, so a job whose vectors
277    /// are as deep as the side runs in groups as a shallower job's do. 1 a register a vector deep.
278    pub vector_depth: usize,
279    /// The sequencer contexts sharing the units: each a core of its own over its own token-rows, its rows table and its
280    /// bank of the token-row store, the matvec unit serving one context while the row units (the recurrence, vector and
281    /// attention sides) serve another, so one context's weights stream while another's rows run. 1 for one sequencer.
282    pub contexts: usize,
283    /// The fold lanes the streaming unit folds a job's sums into its output slot over
284    /// ([`super::super::machine::Fold`], [`super::streaming::streaming_verilog_folding`]): a beat each `16 / folds`
285    /// clocks, read from the slot, folded and written back; 0 a unit that writes each sum as it is.
286    pub folds: usize,
287    /// Whether a recurrence job's gated norm ([`super::super::machine::DeltaNorm`]) is made on its head lanes
288    /// ([`super::gdn::delta_normed_lanes`]): each lane's read-outs normed by the job's weight and gated by the SiLU of
289    /// the row's gate as they are made, so a step whose gated norms are folded into its recurrence jobs
290    /// ([`super::super::machine::fold_delta_norms`]) runs on it, a vector job fewer a Gated-DeltaNet block.
291    pub delta_norm: bool,
292    /// The lanes a convolution job's rows run through on the vector unit's side's path of its own
293    /// ([`super::vector_side::conv_path_verilog`]), 0 for none, and the most beats of its channels a tap of the kernel
294    /// the path holds.
295    pub conv_lanes: usize,
296    pub conv_beats: usize,
297}
298
299/// The floating-point DSP block's add, and its multiply unless the multiplies stay soft, as the oracle measured them
300/// ([`crate::codegen_sva::fp_hard::Block`]): a hybrid engine whose fast side builds `logos_fadd_p`, and `logos_fmul_p`
301/// given a `product`, on the block ([`crate::codegen_sva::fp_hard::hard_verilog_for`]), each the soft unit's ports and
302/// latency, the logic around the block correcting what it computes otherwise. The add computes the engine's own
303/// arithmetic; the multiply the arithmetic of `fp_unit`: at [`FpUnit::Soft`] the engine's, so a block whose multiply
304/// decides a product tiny before the engine does is refused, at [`FpUnit::Hard`] the card's ([`FpUnit::CARD`]), the
305/// arithmetic the numerics of an `fp_unit: Hard` knob point multiply by.
306#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307pub struct HardFloat {
308    pub product: Option<Block>,
309    pub sum: Block,
310    pub fp_unit: FpUnit,
311}
312
313impl HardFloat {
314    /// The arithmetic the product's wrapper computes: the engine's at [`FpUnit::Soft`], the card's at [`FpUnit::Hard`].
315    pub fn product_target(&self) -> Semantics {
316        match self.fp_unit {
317            FpUnit::Soft => Semantics::ENGINE,
318            FpUnit::Hard => FpUnit::CARD,
319        }
320    }
321}
322
323impl Streaming {
324    /// The unit for `set` over `layout`, which lays its streamed matrices in stripes.
325    pub fn new(set: ColumnSet, layout: ImageLayout) -> Streaming {
326        assert!(layout.stripe.is_some_and(|n| n > 0), "a streaming unit's matrices lie in stripes");
327        assert!(
328            layout.octets == set.octets && layout.slot == set.slot as u64,
329            "a streaming unit's set and its image lay blocks alike: octets {} and {}, slots {} and {}",
330            set.octets,
331            layout.octets,
332            set.slot,
333            layout.slot
334        );
335        Streaming {
336            set,
337            layout,
338            recurrence: false,
339            recurrence_width: 1,
340            recurrence_lanes: 1,
341            log_lines: 0,
342            recurrence_flushes: 1,
343            recurrence_credit: super::gdn::LANE_CREDIT,
344            vectors: false,
345            attention: false,
346            hard_float: None,
347            float_rows: super::vector_side::FM_ROWS,
348            vector_passes: 1,
349            vector_depth: 1,
350            contexts: 1,
351            folds: 0,
352            delta_norm: false,
353            conv_lanes: 0,
354            conv_beats: 0,
355        }
356    }
357
358    /// The unit with each convolution job (the machine's conv job, four taps, its ring rows whole lines) run on the
359    /// vector unit's side's path of its own: the kernel read once a job, each row's ring rows and x streamed through
360    /// `lanes` conv lanes (1, 2, 4, 8 or 16 channels a clock), its output and ring row written as they come; `beats` the
361    /// most beats of a convolution's channels, the kernel's depth a tap. A job of any other shape runs on the vector unit,
362    /// which holds [`CONV_PATH_VREGS`] registers; one whose loads, stores or instructions name a register past them faults.
363    pub fn with_conv_path(self, lanes: usize, beats: usize) -> Streaming {
364        assert!(self.vectors, "a convolution's path is the vector unit's side's");
365        assert!([1, 2, 4, 8, 16].contains(&lanes), "a conv path of 1, 2, 4, 8 or 16 lanes, not {lanes}");
366        assert!(beats >= 1, "a conv path's kernel holds a beat a tap at least");
367        Streaming { conv_lanes: lanes, conv_beats: beats, ..self }
368    }
369
370    /// The `beats` of [`Streaming::with_conv_path`] for checkpoints of the specs `specs`: the most channels of any of
371    /// their Gated-DeltaNet layers' convolutions (the keys twice and the values), sixteen a beat; 1 for none.
372    pub fn conv_beats<'a>(specs: impl IntoIterator<Item = &'a ModelSpec>) -> usize {
373        specs
374            .into_iter()
375            .flat_map(|spec| spec.layers.iter())
376            .filter_map(|layer| match &layer.mixer {
377                Mixer::GatedDelta(g) => Some((2 * g.k_heads + g.v_heads) * g.head_dim),
378                _ => None,
379            })
380            .max()
381            .map_or(1, |channels| channels.div_ceil(16))
382    }
383
384    /// The unit with the recurrence unit beside it.
385    pub fn with_recurrence(self) -> Streaming {
386        Streaming { recurrence: true, ..self }
387    }
388
389    /// The unit with its recurrence unit taking `width` sixteens of elements a clock, 1 or 2.
390    pub fn with_recurrence_width(self, width: usize) -> Streaming {
391        assert!(self.recurrence, "a recurrence unit's width needs the recurrence unit");
392        assert!(width == 1 || width == 2, "the recurrence unit takes half a binary16 line a clock or a whole one, not {width} sixteens");
393        Streaming { recurrence_width: width, ..self }
394    }
395
396    /// The unit with its recurrence unit's value heads on `lanes` head lanes, 1 to 4, each lane's copy of the log
397    /// `log_lines` lines.
398    pub fn with_recurrence_lanes(self, lanes: usize, log_lines: usize) -> Streaming {
399        assert!(self.recurrence, "head lanes are the recurrence unit's");
400        assert!((1..=4).contains(&lanes), "the recurrence unit runs on 1 to 4 head lanes, not {lanes}");
401        assert!(lanes == 1 || log_lines >= 1, "head lanes read the log from copies of at least a line");
402        Streaming { recurrence_lanes: lanes, log_lines: if lanes == 1 { 0 } else { log_lines }, ..self }
403    }
404
405    /// The unit with each head lane's flushing head split over `flushes` flush units by rows, 1, 2, 4 or 8.
406    pub fn with_recurrence_flushes(self, flushes: usize) -> Streaming {
407        assert!(self.recurrence_lanes > 1, "flush units are a head lane's");
408        assert!(self.recurrence_width == 2, "a head lane's flush units take a binary16 line a clock");
409        assert!([1, 2, 4, 8].contains(&flushes), "a head lane's flush over 1, 2, 4 or 8 units, not {flushes}");
410        Streaming { recurrence_flushes: flushes, ..self }
411    }
412
413    /// The unit with its recurrence's coordinator and head lanes asking up to `lines` lines ahead of those they have taken,
414    /// from [`super::gdn::LANE_CREDIT`] to [`super::gdn::LANE_CREDIT_MAX`].
415    pub fn with_recurrence_credit(self, lines: usize) -> Streaming {
416        use super::gdn::{LANE_CREDIT, LANE_CREDIT_MAX};
417        assert!(self.recurrence_lanes > 1, "a read credit is the head lanes'");
418        assert!(
419            (LANE_CREDIT..=LANE_CREDIT_MAX).contains(&lines),
420            "a head lane asks {LANE_CREDIT} to {LANE_CREDIT_MAX} lines ahead, not {lines}"
421        );
422        Streaming { recurrence_credit: lines, ..self }
423    }
424
425    /// The unit with the vector unit's side beside it, on the recurrence unit's port onto the channels.
426    pub fn with_vectors(self) -> Streaming {
427        assert!(self.recurrence, "the vector unit's side runs on the recurrence unit's port");
428        assert_eq!(self.layout.line_bytes, 64, "the vector unit's side reads and writes the channels' 64-byte lines");
429        Streaming { vectors: true, ..self }
430    }
431
432    /// The unit with its vector unit's side holding `rows` token-rows of a float matrix's inputs at once.
433    pub fn with_float_rows(self, rows: usize) -> Streaming {
434        assert!(self.vectors, "a float matrix's rows are the vector unit's side's");
435        assert!(rows >= 1, "a float matrix's group of no rows");
436        Streaming { float_rows: rows, ..self }
437    }
438
439    /// The unit with its vector unit's side running a grouped job's passes `passes` at a time, 1 to
440    /// [`VECTOR_PASSES_MAX`]: a reduction's passes in subgroups of the add's latency and one, a slot of its round each.
441    pub fn with_vector_passes(self, passes: usize) -> Streaming {
442        assert!(self.vectors, "a group of passes is the vector unit's side's");
443        assert!(
444            (1..=VECTOR_PASSES_MAX).contains(&passes),
445            "the vector unit runs 1 to {VECTOR_PASSES_MAX} passes at once, not {passes}"
446        );
447        Streaming { vector_passes: passes, ..self }
448    }
449
450    /// The unit with its vector unit's side's registers `depth` passes of the deepest vector job it runs whole deep, 1 to
451    /// [`VECTOR_PASSES_MAX`]: a job whose vectors are that deep runs `depth` of its passes a group (up to the side's
452    /// passes a group), its chunks and its float matrices' held inputs still a vector deep.
453    pub fn with_vector_depth(self, depth: usize) -> Streaming {
454        assert!(self.vectors, "a register's depth is the vector unit's side's");
455        assert!(
456            (1..=VECTOR_PASSES_MAX).contains(&depth),
457            "the vector unit's side holds 1 to {VECTOR_PASSES_MAX} passes of its deepest job, not {depth}"
458        );
459        Streaming { vector_depth: depth, ..self }
460    }
461
462    /// The unit with the attention side beside the vector unit's side, on the recurrence unit's port.
463    pub fn with_attention(self) -> Streaming {
464        assert!(self.vectors, "the attention side runs beside the vector unit's side, on the recurrence unit's port");
465        Streaming { attention: true, ..self }
466    }
467
468    /// The unit with `contexts` sequencer contexts, 1 or 2, sharing its units: every unit must be on the fast side (the
469    /// attention side beside the rest), since only the fast side's units run beside one another, and a context's
470    /// program counter crosses to the shell in 16 bits.
471    pub fn with_contexts(self, contexts: usize) -> Streaming {
472        assert!(self.attention, "contexts share the fast side's units, so every unit must be on it: the attention side too");
473        assert!(contexts == 1 || contexts == 2, "an engine of 1 or 2 contexts, not {contexts}");
474        Streaming { contexts, ..self }
475    }
476
477    /// The unit with its fast side's f32 adds, and its multiplies given a product block, on the floating-point DSP block
478    /// `hard`: refused when a wrapper cannot give the soft unit's latency on it, or the engine's add, or the multiply of
479    /// `hard`'s unit ([`HardFloat::product_target`]); a build of the card's products has its product block.
480    pub fn with_hard_float(self, hard: HardFloat) -> Result<Streaming, HardError> {
481        assert!(hard.fp_unit == FpUnit::Soft || hard.product.is_some(), "a build of the card's products builds them on its block");
482        if let Some(product) = &hard.product {
483            plan_for(fp_hard::Op::Multiply, product, FMUL_LATENCY, &hard.product_target())?;
484        }
485        plan(fp_hard::Op::Add, &hard.sum, FADD_LATENCY)?;
486        Ok(Streaming { hard_float: Some(hard), ..self })
487    }
488
489    /// The unit folding a job's sums into its output slot over `lanes` fold lanes, 1, 2, 4, 8 or 16: a step whose residual
490    /// adds and gated products are folded into its matrix jobs ([`super::super::machine::fold`]) runs on it.
491    pub fn with_folds(self, lanes: usize) -> Streaming {
492        assert!([1, 2, 4, 8, 16].contains(&lanes), "a fold over 1, 2, 4, 8 or 16 lanes, not {lanes}");
493        Streaming { folds: lanes, ..self }
494    }
495
496    /// The head lanes making each recurrence job's gated norm as its read-outs are made ([`Streaming::delta_norm`]): a
497    /// step whose gated norms are folded into its recurrence jobs runs on it.
498    pub fn with_delta_norm(self) -> Streaming {
499        assert!(self.recurrence_lanes > 1, "a recurrence job's gated norm is made on its head lanes");
500        Streaming { delta_norm: true, ..self }
501    }
502
503    /// The name the build and the programs written for it give the unit (`LOGOS_D5005_STREAMING`, `engine.txt`'s
504    /// `streaming`): its column set, the row pairs a channel's stripe holds, whether it folds, and whether its recurrence
505    /// makes its gated norms.
506    pub fn signature(&self) -> String {
507        format!(
508            "{}/{}{}{}",
509            self.set.signature(),
510            self.pairs(),
511            if self.folds > 0 { "+folds" } else { "" },
512            if self.delta_norm { "+norm" } else { "" }
513        )
514    }
515
516    /// The row pairs a channel's share of a stripe holds: the column array's cores a channel.
517    pub fn pairs(&self) -> u64 {
518        self.layout.stripe.expect("a stripe")
519    }
520}
521
522/// The function units the vector unit's sixteen lanes share: a quarter of a unit a lane, the vector unit's largest
523/// part (each unit an exp, a reciprocal, an ln and a reciprocal square root) in a quarter of the area.
524pub const FUNCTION_UNITS: usize = 4;
525
526/// The most passes a vector unit's side runs at once ([`Streaming::with_vector_passes`]): each pass holds its own scalar
527/// registers and running sums, so the latency every instruction pays is shared by the group.
528pub const VECTOR_PASSES_MAX: usize = 32;
529
530/// The vector registers of the vector unit of a side whose convolutions run on their path ([`Streaming::with_conv_path`]),
531/// half of the sixteen a job's fields can name: every job the machine lowers but a convolution names fewer.
532pub const CONV_PATH_VREGS: usize = 8;
533
534impl EngineParams {
535    /// The name the build and its programs give its streaming unit, the step program refusing a program of another: the
536    /// unit's own ([`Streaming::signature`]), and `+behind<T>` when its recurrence replays logs a window of `T` tokens
537    /// behind, its lanes built for that window; empty for an engine that streams nothing.
538    pub fn streaming_signature(&self) -> String {
539        self.streaming.as_ref().map_or(String::new(), |s| format!("{}{}", s.signature(), behind(self.delta.map(|(_, f)| f))))
540    }
541
542    /// The engine `jobs` over `layout` need, the store laid out as `store`.
543    pub fn of(layout: &Layout, store: &StoreLayout, jobs: &[Job]) -> EngineParams {
544        let mut formats: Vec<(GgmlType, usize)> = Vec::new();
545        let (mut vector_rows, mut side_rows, mut pair_rows) = (1, 1, 1);
546        for job in jobs {
547            match job {
548                Job::Matvec { tensor, .. } => {
549                    let t = &layout.tensors[*tensor];
550                    let blocks = t.dims[0] as usize / t.ty.block_len();
551                    match formats.iter_mut().find(|(ty, _)| *ty == t.ty) {
552                        Some(f) => f.1 = f.1.max(blocks),
553                        None => formats.push((t.ty, blocks)),
554                    }
555                }
556                Job::Vector(v) => {
557                    let rows = v.loads.iter().map(|l| l.len.div_ceil(LANES)).fold(1, usize::max);
558                    vector_rows = vector_rows.max(rows);
559                    if !v.chunked() {
560                        side_rows = side_rows.max(rows);
561                    }
562                    if v.program.iter().any(|op| matches!(op, Op::Hadamard { .. } | Op::Rotate { .. })) {
563                        pair_rows = pair_rows.max(rows);
564                    }
565                }
566                _ => {}
567            }
568        }
569        formats.sort_by_key(|(ty, _)| ty.id());
570        EngineParams {
571            rows: store.rows,
572            vector_rows,
573            side_rows,
574            pair_rows,
575            store_beats: store.beats as usize,
576            job_words: encode(jobs).len().max(1),
577            tensors: layout.tensors.len(),
578            layers: layout.blocks.len(),
579            ropes: layout.rotary.len(),
580            formats,
581            delta: layout.blocks.iter().find_map(|b| match b {
582                BlockState::Delta { width, .. } => Some((*width, layout.numerics.flush)),
583                _ => None,
584            }),
585            attend: layout.blocks.iter().find_map(|b| match b {
586                BlockState::Attention { width, value_width, .. } => {
587                    Some((*width, *value_width, layout.ctx, layout.numerics.attention == crate::spatial::serving::AttentionMath::Array))
588                }
589                _ => None,
590            }),
591            function_units: FUNCTION_UNITS,
592            folds: jobs.iter().any(|j| matches!(j, Job::Matvec { fold, .. } if *fold != Fold::Put)),
593            streaming: None,
594        }
595    }
596
597    /// One engine that runs every engine of `all`: the most token-rows, vector rows, store beats, job words and table
598    /// entries any needs, every block format any reads at the most blocks a row of it holds in any, the most positions
599    /// any attends over. Engines whose recurrence or attention units differ in shape — head widths, a log's tokens,
600    /// where the attention math runs —, whose vector units share a different number of function units, or that run
601    /// their matvec jobs on different streaming units have no union.
602    pub fn union(all: &[EngineParams]) -> Result<EngineParams, String> {
603        let (first, rest) = all.split_first().ok_or("no engines")?;
604        let mut u = first.clone();
605        for p in rest {
606            if p.function_units != u.function_units {
607                return Err(format!("{} and {} function units", u.function_units, p.function_units));
608            }
609            if p.streaming != u.streaming {
610                return Err(format!("streaming units {:?} and {:?}", u.streaming, p.streaming));
611            }
612            u.delta = match (u.delta, p.delta) {
613                (Some(a), Some(b)) if a != b => return Err(format!("recurrence units {a:?} and {b:?}")),
614                (a, b) => a.or(b),
615            };
616            u.attend = match (u.attend, p.attend) {
617                (Some((hd, vd, ctx, array)), Some((hd2, vd2, ctx2, array2))) => {
618                    if (hd, vd, array) != (hd2, vd2, array2) {
619                        return Err(format!("attention units {:?} and {:?}", (hd, vd, array), (hd2, vd2, array2)));
620                    }
621                    Some((hd, vd, ctx.max(ctx2), array))
622                }
623                (a, b) => a.or(b),
624            };
625            u.rows = u.rows.max(p.rows);
626            u.vector_rows = u.vector_rows.max(p.vector_rows);
627            u.side_rows = u.side_rows.max(p.side_rows);
628            u.pair_rows = u.pair_rows.max(p.pair_rows);
629            u.store_beats = u.store_beats.max(p.store_beats);
630            u.job_words = u.job_words.max(p.job_words);
631            u.tensors = u.tensors.max(p.tensors);
632            u.layers = u.layers.max(p.layers);
633            u.ropes = u.ropes.max(p.ropes);
634            u.folds |= p.folds;
635            for &(ty, blocks) in &p.formats {
636                match u.formats.iter_mut().find(|(t, _)| *t == ty) {
637                    Some(f) => f.1 = f.1.max(blocks),
638                    None => u.formats.push((ty, blocks)),
639                }
640            }
641        }
642        u.formats.sort_by_key(|(ty, _)| ty.id());
643        Ok(u)
644    }
645}
646
647/// Verilog sources as one: every module once — a module two sources define being the same text in each — with the
648/// text before it, the macros it reads among it.
649pub fn compose(sources: &[&str]) -> String {
650    let mut seen: HashMap<String, String> = HashMap::new();
651    let mut out = String::new();
652    for src in sources {
653        let mut rest: &str = src;
654        loop {
655            let start = rest.match_indices("module ").map(|(i, _)| i).find(|&i| i == 0 || rest.as_bytes()[i - 1] == b'\n');
656            let Some(i) = start else {
657                if !rest.trim().is_empty() && !out.contains(rest.trim()) {
658                    out.push_str(rest);
659                }
660                break;
661            };
662            let end = i + rest[i..].find("endmodule").expect("a module's end") + "endmodule".len();
663            let name: String = rest[i + 7..].chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
664            let body = &rest[i..end];
665            match seen.get(&name) {
666                Some(prior) => assert_eq!(prior, body, "two sources define the module {name} two ways"),
667                None => {
668                    seen.insert(name, body.to_string());
669                    out.push_str(&rest[..end]);
670                    out.push('\n');
671                }
672            }
673            rest = &rest[end..];
674        }
675    }
676    out
677}
678
679/// The instance statements under `top` in `verilog`, counted through the hierarchy: every module `top` instantiates,
680/// directly or through the modules it instantiates, with the number of statements that reach it. A statement inside a
681/// generate loop counts once.
682pub fn census(verilog: &str, top: &str) -> BTreeMap<String, usize> {
683    let mut bodies: HashMap<&str, &str> = HashMap::new();
684    let mut rest = verilog;
685    while let Some(i) = rest.match_indices("module ").map(|(i, _)| i).find(|&i| i == 0 || rest.as_bytes()[i - 1] == b'\n') {
686        let end = i + rest[i..].find("endmodule").expect("a module's end");
687        let name = &rest[i + 7..];
688        let name = &name[..name.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(name.len())];
689        bodies.insert(name, &rest[i..end]);
690        rest = &rest[end + "endmodule".len()..];
691    }
692    fn walk(module: &str, reach: usize, bodies: &HashMap<&str, &str>, out: &mut BTreeMap<String, usize>) {
693        let Some(body) = bodies.get(module) else { return };
694        for line in body.lines() {
695            let t = line.trim_start();
696            let child = &t[..t.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(t.len())];
697            let after = t[child.len()..].trim_start();
698            if bodies.contains_key(child) && after.starts_with(|c: char| c == '#' || c.is_alphabetic() || c == '_') {
699                *out.entry(child.to_string()).or_default() += reach;
700                walk(child, reach, bodies, out);
701            }
702        }
703    }
704    let mut out = BTreeMap::new();
705    walk(top, 1, &bodies, &mut out);
706    out
707}
708
709/// The engine for `p`, with the vector unit, the array's columns for every format `p` names, the activation
710/// quantizer they and the query heads share and the f32 units they are built from; a hybrid engine's matvec jobs on its
711/// streaming unit instead of the columns, the quantizer the query heads' alone.
712pub fn engine_verilog(p: &EngineParams) -> String {
713    assert!(
714        !p.folds || p.streaming.as_ref().is_some_and(|s| s.folds > 0),
715        "a step that folds its matrices' sums into their output slots runs on a streaming unit that folds them (Streaming::with_folds)"
716    );
717    let columns = if p.streaming.is_some() { &[][..] } else { &p.formats[..] };
718    let mut block_lens: Vec<usize> = columns.iter().map(|(ty, _)| ty.block_len()).collect();
719    // A query head on the array is quantized as one block of its width.
720    if let Some((hd, _, _, true)) = p.attend {
721        block_lens.push(hd);
722    }
723    block_lens.sort();
724    block_lens.dedup();
725    let widest = p.formats.iter().map(|(ty, _)| 8 * ty.block_bytes()).max().unwrap_or(8);
726    let mut cases = String::new();
727    let mut units = String::new();
728    let mut ready = String::from("1'b0");
729    let mut col_busy = String::from("1'b0");
730    let mut out_mux = String::new();
731    for (i, (ty, blocks)) in columns.iter().enumerate() {
732        let name = ty.name().to_lowercase();
733        let (len, bytes) = (ty.block_len(), ty.block_bytes());
734        writeln!(cases, "      8'd{}: begin mv_known = 1'b1; mv_fmt = 4'd{i}; mv_blen = 16'd{len}; mv_bbytes = 16'd{bytes}; end", ty.id()).unwrap();
735        writeln!(
736            units,
737            r#"  wire mv_busy_{i}, mv_ready_{i}, mv_ov_{i};
738  wire [15:0] mv_op_{i};
739  wire [7:0] mv_ot_{i};
740  wire [31:0] mv_o1_{i}, mv_o2_{i};
741  logos_matvec_engine_{name} #(.T_MAX(ROW_MAX), .NB_MAX({blocks})) col_{i}(.clk(clk), .rst(rst), .start(mv_start && mv_fmt == 4'd{i}),
742    .pairs(mv_pairs[15:0]), .blocks(mv_blocks[15:0]), .tokens(rows[7:0]), .two(two_words), .busy(mv_busy_{i}),
743    .act_wr(act_wr && mv_fmt == 4'd{i}), .act_t(act_t), .act_b(act_b), .act_codes(aq_codes[{codes_top}:0]), .act_m(aq_m), .act_e(aq_e),
744    .w_valid(w_valid && mv_fmt == 4'd{i}), .w_ready(mv_ready_{i}), .w_blk1(w_blk1[{top}:0]), .w_blk2(w_blk2[{top}:0]),
745    .o_valid(mv_ov_{i}), .o_pair(mv_op_{i}), .o_t(mv_ot_{i}), .o_val1(mv_o1_{i}), .o_val2(mv_o2_{i}));"#,
746            top = 8 * bytes - 1,
747            codes_top = 16 * len - 1
748        )
749        .unwrap();
750        ready = format!("{ready} | (mv_fmt == 4'd{i} && mv_ready_{i})");
751        col_busy = format!("{col_busy} | mv_busy_{i}");
752        writeln!(out_mux, "    if (mv_fmt == 4'd{i}) begin o_v = mv_ov_{i}; o_p = mv_op_{i}; o_t = mv_ot_{i}; o_1 = mv_o1_{i}; o_2 = mv_o2_{i}; end").unwrap();
753    }
754    let attention_side = p.streaming.as_ref().is_some_and(|s| s.attention);
755    let (quantizers, feeds) = match block_lens.last() {
756        Some(_) if attention_side => ("  wire aq_busy = 1'b0;\n".to_string(), String::new()),
757        Some(&lanes) => {
758            assert!(block_lens.iter().all(|len| len % 16 == 0), "blocks of {block_lens:?} are not whole beats");
759            let at_feeds = matches!(p.attend, Some((_, _, _, true)));
760            let shared = if p.streaming.is_some() { "the query heads share" } else { "every weight block and query head shares" };
761            (
762                format!(
763                    r#"  // The activation quantizer {shared}, fed a beat a cycle from the store.
764  wire aq_in, aq_clear, aq_go, aq_busy;
765  wire [7:0] aq_m;
766  wire signed [11:0] aq_e;
767  wire [{}:0] aq_codes;
768  logos_act_stream #(.LANES({lanes})) aq(.clk(clk), .rst(rst), .clear(aq_clear), .in_v(aq_in), .in_beat(st_rd), .go(aq_go),
769    .two(two_words), .busy(aq_busy), .m(aq_m), .e(aq_e), .codes(aq_codes));
770"#,
771                    16 * lanes - 1
772                ),
773                if p.streaming.is_some() {
774                    "  // A query head's beats while it loads, its codes once the last is in.\n  assign aq_in = state == AT_Q && at_lj != AH_BEATS;\n  \
775                     assign aq_clear = state == AT_Q && at_lj == 32'd0;\n  assign aq_go = state == AT_Q && at_lj == AH_BEATS;\n"
776                        .to_string()
777                } else {
778                    format!(
779                    r#"  // A weight block's beats while it loads, its codes once the last is in; a query head's the same.
780  wire mv_aq = state == MV_ACT && mv_known && {{8'd0, act_t}} != {{8'd0, rows}};
781  wire mv_aq_in = mv_aq && aj < {{16'd0, mv_blen}} >> 4;
782  assign aq_in = mv_aq_in{at_in};
783  assign aq_clear = (mv_aq_in && aj == 32'd0){at_clear};
784  assign aq_go = (mv_aq && aj == {{16'd0, mv_blen}} >> 4){at_go};
785"#,
786                    at_in = if at_feeds { " || (state == AT_Q && at_lj != AH_BEATS)" } else { "" },
787                    at_clear = if at_feeds { " || (state == AT_Q && at_lj == 32'd0)" } else { "" },
788                    at_go = if at_feeds { " || (state == AT_Q && at_lj == AH_BEATS)" } else { "" },
789                    )
790                },
791            )
792        }
793        None => ("  wire aq_busy = 1'b0;\n".to_string(), String::new()),
794    };
795    let fast_recurrence = p.streaming.as_ref().is_some_and(|s| s.recurrence);
796    assert!(!fast_recurrence || p.delta.is_some(), "a recurrence unit runs a model's Gated-DeltaNet blocks");
797    if let (true, Some((hd, _))) = (fast_recurrence, p.delta) {
798        assert!(hd * 2 % 64 == 0, "a recurrence unit walks a head's memory in whole lines, and a head {hd} wide is not whole lines in binary16");
799    }
800    let vectors = p.streaming.as_ref().is_some_and(|s| s.vectors);
801    // A vectored engine whose memories are lazily flushed hands every token-row of them to the recurrence unit and every
802    // vector job to the vector unit's side, so its core runs no vector instruction and updates no memory itself.
803    let handed_off = vectors && p.delta.is_none_or(|(_, f)| recurrence_log(f).0 > 1);
804    if let Some((_, StateFlush::Trailing { .. })) = p.delta {
805        assert!(fast_recurrence && p.streaming.as_ref().is_some_and(|s| s.recurrence_lanes > 1), "a trailing flush runs on the recurrence's head lanes alone");
806    }
807    let normed = p.streaming.as_ref().is_some_and(|s| s.delta_norm);
808    if normed {
809        assert!(
810            p.delta.is_some_and(|(_, f)| recurrence_log(f).0 > 1),
811            "a recurrence makes its gated norms on its head lanes, which run the memories it flushes lazily alone"
812        );
813    }
814    let delta = p.delta.map_or_else(UnitVerilog::default, |(hd, f)| {
815        let t = recurrence_log(f).0;
816        if handed_off {
817            delta_handed_off(hd, t, normed)
818        } else if fast_recurrence {
819            let unit = delta_on_the_fast_side(delta_verilog(hd, t));
820            if normed { delta_normed_core(unit) } else { unit }
821        } else {
822            delta_verilog(hd, t)
823        }
824    });
825    let attention_side = p.streaming.as_ref().is_some_and(|s| s.attention);
826    assert!(!attention_side || matches!(p.attend, Some((_, _, _, true))), "the attention side attends over an int8 cache on the array");
827    let attend_unit = match p.attend {
828        Some(_) if attention_side => attend_handed_off(),
829        Some((hd, vd, ctx, array)) => attend_unit_verilog(hd, vd, ctx, array),
830        None => UnitVerilog::default(),
831    };
832    let delta_pulses = format!(
833        "{}{}",
834        match (p.delta.is_some(), fast_recurrence, handed_off) {
835            (true, false, _) => "dre_v <= 1'b0; dlr_v <= 1'b0; dlr_log_wr <= 1'b0; ",
836            (true, true, false) => "dre_v <= 1'b0; sm_gdn_go <= 1'b0; ",
837            (true, true, true) => "sm_gdn_go <= 1'b0; ",
838            (false, _, _) => "",
839        },
840        match (p.attend.is_some(), attention_side) {
841            (true, false) => "at_start <= 1'b0;",
842            (true, true) => "sm_att_go <= 1'b0;",
843            (false, _) => "",
844        }
845    );
846    let delta_reads = match (p.delta.is_some(), fast_recurrence, handed_off) {
847        (true, false, _) => {
848            "if (dl_rd) begin s_rd_reg = dl_rd_reg; s_rd_row = dl_rd_row; end\n    if (state == DL_KQ_WAIT) s_rd_s = 4'd0;\n    if (state == DL_FEXP_WAIT) s_rd_s = 4'd1;"
849        }
850        (true, true, false) => "if (state == DL_KQ_WAIT) s_rd_s = 4'd0;",
851        (_, _, true) | (false, _, _) => "",
852    };
853    let mv = match &p.streaming {
854        None => MatvecText::reference(),
855        Some(s) => MatvecText::hybrid(s),
856    };
857    let template = if vectors { vector_jobs_on_the_fast_side(ENGINE_VERILOG, handed_off) } else { ENGINE_VERILOG.to_string() };
858    let engine = template
859        .replace("@PORTS@", &mv.ports)
860        .replace("@STORE_RAM@", &mv.store_ram)
861        .replace("@MATVEC_DECLS@", &mv.decls)
862        .replace("@MATVEC_STATES@", &mv.states)
863        .replace("@MATVEC_PULSES@", &mv.pulses)
864        .replace("@MATVEC_CASES@", &mv.cases)
865        .replace("@MATVEC_READS@", &mv.reads)
866        .replace("@STORE_WRITE_NOTE@", &mv.write_note)
867        .replace("@MATVEC_WRITES@", &mv.writes)
868        .replace("@DELTA_DECLS@", &delta.decls)
869        .replace("@DELTA_CASES@", &delta.cases)
870        .replace("@STORE_READS@", &format!("{}{}", delta.reads, attend_unit.reads))
871        .replace("@STORE_WRITES@", &format!("{}{}", delta.writes, attend_unit.writes))
872        .replace("@DELTA_PULSES@", &delta_pulses)
873        .replace("@ATTEND_DECLS@", &attend_unit.decls)
874        .replace("@ATTEND_CASES@", &attend_unit.cases)
875        .replace("@ATTEND_UNIT@", if p.attend.is_some() { "4" } else { "15" })
876        .replace("@DELTA_READS@", delta_reads)
877        .replace("@DELTA_UNIT@", if p.delta.is_some() { "3" } else { "15" })
878        .replace("@ROWS@", &p.rows.to_string())
879        .replace("@VROWS@", &p.vector_rows.to_string())
880        .replace("@FU@", &p.function_units.to_string())
881        .replace("@STORE@", &p.store_beats.to_string())
882        .replace("@JOBS@", &p.job_words.to_string())
883        .replace("@TENSORS@", &p.tensors.max(1).to_string())
884        .replace("@LAYERS@", &p.layers.max(1).to_string())
885        .replace("@ROPES@", &p.ropes.max(1).to_string())
886        .replace("@WIDEST@", &(widest - 1).to_string())
887        .replace("@FORMAT_CASES@", &cases)
888        .replace("@QUANTIZERS@", &quantizers)
889        .replace("@QUANTIZER_FEEDS@", &feeds)
890        .replace("@COLUMNS@", &units)
891        .replace("@READY@", &ready)
892        .replace("@COLUMN_BUSY@", &col_busy)
893        .replace("@OUT_MUX@", &out_mux);
894    let simd = super::simd::simd_verilog();
895    let matvec = if p.formats.is_empty() || p.streaming.is_some() { String::new() } else { super::matvec::matvec_verilog() };
896    let gdn = if p.delta.is_some() { super::gdn::gdn_verilog() } else { String::new() };
897    let attend = if p.attend.is_some() { super::attend::attend_verilog() } else { String::new() };
898    let ram = super::ram::ram_verilog(&[64, 128, 512, 608]);
899    let act = super::act_quant::act_quant_verilog();
900    let core = engine.replacen("module logos_engine #(", "module logos_engine_core #(", 1);
901    // A core that reads the token-row store for the host alone leaves every other read of it to the fast side, which
902    // reads it through the pipelined bank: each reader takes its word STORE_READ_LATENCY clocks after its address, and
903    // the host's read, which takes its word a compute step after asking, the same.
904    let srl = if p.streaming.is_some() && reads_the_store_for_the_host_alone(&engine) { STORE_READ_LATENCY } else { 1 };
905    assert!(
906        p.streaming.as_ref().is_none_or(|s| s.contexts == 1) || !core.contains("wq_v <= 1'b1"),
907        "an engine of contexts shares the card's port for reads alone, and this core writes the card"
908    );
909    let enabled = enable(&compose(&[&ram, &simd, &matvec, &act, &gdn, &attend, &core]));
910    let fast = match &p.streaming {
911        Some(s) => {
912            let recurrence = if s.recurrence {
913                let rows = super::gdn::rows_verilog();
914                let rows = if s.delta_norm { super::gdn::delta_normed_rows(&rows) } else { rows };
915                let rows = if s.contexts > 1 { gdn_rows_contexts(&rows) } else { rows };
916                format!("\n{}\n{rows}\n{}", super::gdn::gdn_stream_verilog(), super::super::physical::fast_port_verilog())
917            } else {
918                String::new()
919            };
920            let recurrence = if s.recurrence_lanes > 1 {
921                let lanes = match p.delta {
922                    Some((_, StateFlush::Trailing { tokens })) => super::gdn::lanes_trailing_verilog(tokens as usize),
923                    _ => super::gdn::lanes_verilog(),
924                };
925                let lanes = match p.delta {
926                    Some((hd, _)) if s.delta_norm => super::gdn::delta_normed_lanes(&lanes, hd, s.hard_float.map_or(FpUnit::Soft, |h| h.fp_unit)),
927                    _ => lanes,
928                };
929                format!("{recurrence}\n{lanes}")
930            } else {
931                recurrence
932            };
933            let recurrence = if s.recurrence_lanes > 1 || s.contexts > 1 {
934                format!("{recurrence}\n{}", super::super::physical::channel_mux_verilog())
935            } else {
936                recurrence
937            };
938            let recurrence = if s.contexts > 1 {
939                format!("{recurrence}\n{}", super::super::physical::read_credit_verilog())
940            } else {
941                recurrence
942            };
943            let vector_side = || {
944                let text = super::vector_side::vector_side_verilog();
945                let text = if s.conv_lanes > 0 {
946                    format!("{}\n{}", vector_side_conv(&text, s.conv_lanes, s.conv_beats), super::vector_side::conv_path_verilog())
947                } else {
948                    text
949                };
950                if s.contexts > 1 {
951                    vector_side_contexts(&text)
952                } else {
953                    text
954                }
955            };
956            let recurrence = if s.vectors { format!("{recurrence}\n{}", vector_side()) } else { recurrence };
957            let recurrence = if s.attention { format!("{recurrence}\n{}", super::attend::attend_side_verilog()) } else { recurrence };
958            let bank = if srl > 1 { super::ram::store_verilog() } else { super::ram::bram_re_verilog(&[512]) };
959            format!(
960                "{}\n{bank}\n{}\n{}{recurrence}",
961                super::super::physical::pipe_verilog(),
962                super::streaming::streaming_verilog_folding(std::slice::from_ref(&s.set), &s.layout, s.folds),
963                fast_store(&fast_verilog(p, s), srl)
964            )
965        }
966        None => String::new(),
967    };
968    let engine = format!("{}{}", super::ram::RAMSTYLE, prune(&compose(&[&enabled, &fast, &bridge_of(&crossing_ports(p)), &top_verilog(p)]), "logos_engine"));
969    match p.streaming.as_ref().and_then(|s| s.hard_float) {
970        Some(hard) => hard_float_verilog(&engine, &hard),
971        None => engine,
972    }
973}
974
975/// The engine `verilog` ([`engine_verilog`]'s text) with its f32 adds, and its multiplies given a product block, on the
976/// floating-point DSP block `hard`: `logos_fadd_p` (and `logos_fmul_p`) replaced by the block's wrappers, the same ports
977/// and latencies. Every instance of either is the fast side's; refuses an engine whose core reaches one, the core
978/// stepping at its enables where the wrappers run free.
979pub fn hard_float_verilog(verilog: &str, hard: &HardFloat) -> String {
980    let core = census(verilog, "logos_engine_core_ce");
981    for unit in ["logos_fmul_p", "logos_fadd_p"] {
982        assert!(!core.contains_key(unit), "the engine's core reaches `{unit}`, which the floating-point block's wrapper runs free of its enables");
983    }
984    let wrapper = |op, unit, block, latency| hard_verilog(op, unit, block, latency).unwrap_or_else(|e| panic!("{unit} on the block: {e}"));
985    modules(verilog)
986        .into_iter()
987        .map(|(name, text)| {
988            let head = &text[..text.find(&format!("module {name}")).unwrap_or(text.len())];
989            match name.as_str() {
990                "logos_fmul_p" => match &hard.product {
991                    Some(product) => format!(
992                        "{head}{}",
993                        hard_verilog_for(fp_hard::Op::Multiply, "logos_fmul_p", product, FMUL_LATENCY, &hard.product_target())
994                            .unwrap_or_else(|e| panic!("logos_fmul_p on the block: {e}"))
995                    ),
996                    None => format!("{text}\n"),
997                },
998                "logos_fadd_p" => format!("{head}{}", wrapper(fp_hard::Op::Add, "logos_fadd_p", &hard.sum, FADD_LATENCY)),
999                _ => format!("{text}\n"),
1000            }
1001        })
1002        .collect()
1003}
1004
1005/// The floating-point DSP block a simulation of an engine on `hard` stands in for the device's (`fourteennm_fp_mac`,
1006/// [`crate::codegen_sva::fp_oracle::hard_model_verilog`]): the measured semantics of its multiply and add at their
1007/// measured depths, the multiply-add as deep as the deeper of them. With the multiplies soft no instance takes the
1008/// block's multiply, and the stand-in's is the add's.
1009pub fn hard_float_model(hard: &HardFloat) -> String {
1010    let (m, a) = (hard.product.unwrap_or(hard.sum), hard.sum);
1011    hard_model_verilog(&m.semantics, &a.semantics, Depths { full: [m.full, a.full, m.full.max(a.full)], least: [m.least, a.least] })
1012}
1013
1014/// Whether an engine's `core` (its template's text, every unit's spliced in) reads the token-row store for the host alone:
1015/// its one read address the host's.
1016fn reads_the_store_for_the_host_alone(core: &str) -> bool {
1017    core.matches("st_ra = ").count() == 1 && core.contains("      default: st_ra = host_raddr;\n")
1018}
1019
1020/// The fast side `text` ([`fast_verilog`]'s) whose token-row store reads in `srl` clocks: at one clock the text itself; at
1021/// [`STORE_READ_LATENCY`] each bank the pipelined `logos_store_w512` and every reader of it — the streaming unit, the
1022/// recurrence, vector and attention sides — given the latency (`SRL`).
1023fn fast_store(text: &str, srl: usize) -> String {
1024    if srl == 1 {
1025        return text.to_string();
1026    }
1027    assert_eq!(srl, STORE_READ_LATENCY, "a store reads in one clock or in the pipelined bank's");
1028    assert_eq!(text.matches("logos_bram_re_w512 #(").count(), 1, "the fast side's one store instance");
1029    let mut t = text.replace("logos_bram_re_w512 #(", "logos_store_w512 #(");
1030    // Each reader's latency its parameters' last, the parameters it had before kept as they were.
1031    let given = |t: &mut String, from: &str| -> bool {
1032        let Some(at) = t.find(from) else { return false };
1033        let open = at + t[at..].find("#(").expect("an instance's parameters") + 1;
1034        let mut depth = 0;
1035        let close = open
1036            + t[open..]
1037                .char_indices()
1038                .find(|&(_, c)| {
1039                    depth += match c {
1040                        '(' => 1,
1041                        ')' => -1,
1042                        _ => 0,
1043                    };
1044                    depth == 0
1045                })
1046                .expect("an instance's parameters closed")
1047                .0;
1048        t.insert_str(close, &format!(", .SRL({srl})"));
1049        true
1050    };
1051    assert!(given(&mut t, "  logos_streaming_matvec_"), "the fast side's streaming unit");
1052    let mut readers = Vec::new();
1053    for reader in ["logos_vector_side", "logos_gdn_side", "logos_gdn_lanes", "logos_attend_side"] {
1054        let from = format!("  {reader} #(");
1055        assert!(t.matches(&from).count() <= 1, "one {reader} on the fast side");
1056        if given(&mut t, &from) {
1057            readers.push(reader);
1058        }
1059    }
1060    for reader in ["logos_vector_side", "logos_attend_side"] {
1061        assert!(readers.contains(&reader), "a fast side whose core reads the store for the host alone runs its {reader}");
1062    }
1063    assert!(
1064        readers.contains(&"logos_gdn_side") || readers.contains(&"logos_gdn_lanes"),
1065        "a fast side whose core reads the store for the host alone runs its recurrence"
1066    );
1067    t
1068}
1069
1070/// The ports past the template's a hybrid engine's crossing takes from the shell for its fast side: the card port's
1071/// map ([`super::super::physical`]), which a recurrence unit's port places its lines by; none for any other engine.
1072fn bridged_map(p: &EngineParams) -> Vec<(bool, String, String)> {
1073    if !p.streaming.as_ref().is_some_and(|s| s.recurrence) {
1074        return Vec::new();
1075    }
1076    vec![
1077        (true, "[31:0]".to_string(), "map_space".to_string()),
1078        (true, format!("[{}:0]", super::stream::AW - 1), "map_base".to_string()),
1079        (true, "[4:0]".to_string(), "map_granule_log2".to_string()),
1080    ]
1081}
1082
1083
1084/// The engine `verilog` ([`engine_verilog`]'s text) as a simulator runs it: the core's clock gated to the user cycles
1085/// the core steps in (`fedge`) or reads its memories in (`cr`), the one difference. Every register of the core steps
1086/// under `step_en` and every memory reads under `read_en` and writes under `ce`, so a rising edge of the user clock with
1087/// neither high changes nothing in the core, and without those edges a simulator evaluates the core's logic in two user
1088/// cycles of an engine cycle instead of in every one. The gate is taken at the falling edge, so the gated clock rises
1089/// only with the user clock. Refuses an engine whose core holds state any other way, or enables an instance by anything
1090/// but its own `step_en` and `read_en`. A simulation model only: a PR persona gates no clock, every global network
1091/// being the FIM's.
1092pub fn simulation_twin(verilog: &str) -> String {
1093    let mut reached: Vec<String> = census(verilog, "logos_engine_core_ce").into_keys().collect();
1094    reached.push("logos_engine_core_ce".to_string());
1095    for (name, text) in modules(verilog).iter().filter(|(n, _)| reached.contains(n)) {
1096        let body = &text[text.find(&format!("module {name}")).expect("its header")..];
1097        let always = body.matches("always @(").count();
1098        if name.starts_with("logos_ram_ce_w") {
1099            let (read, write) = ("  always @(posedge clk) if (cr) rd <= mem[ra];\n", "  always @(posedge clk)\n    if (ce && we) begin\n");
1100            assert!(always == 2 && body.contains(read) && body.contains(write), "`{name}` reads under cr and writes under ce alone");
1101        } else if body.contains("(input clk, input step_en, input read_en, input rst,") {
1102            assert_eq!(body.matches("always @(posedge clk) if (step_en) begin").count(), always, "`{name}`'s every block steps under step_en");
1103        } else {
1104            assert!(!body.contains("posedge") && !body.contains("negedge") && !body.contains("input clk"), "`{name}` holds no state of its own");
1105        }
1106        assert_eq!(body.matches(".clk(").count(), body.matches(".clk(clk)").count(), "`{name}` clocks its instances by its own clock");
1107        let enabled = body.matches("(.clk(clk), .ce(step_en), .cr(read_en), ").count()
1108            + body.matches("(.clk(clk), .step_en(step_en), .read_en(read_en), ").count();
1109        assert_eq!(body.matches(".clk(clk)").count(), enabled, "`{name}` enables its every instance by its own step_en and read_en");
1110    }
1111    // Every core's instance (one, or one a context), each clocked by the one gate.
1112    let instances: Vec<String> = verilog
1113        .match_indices("  logos_engine_core_ce ")
1114        .map(|(at, head)| {
1115            let rest = &verilog[at + head.len()..];
1116            let name: String = rest.chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
1117            assert!(
1118                rest[name.len()..].starts_with("(.clk(fclk), .step_en(fedge), .read_en(cr),"),
1119                "the core's instance `{name}` clocked by the user clock, stepping at fedge and reading at cr"
1120            );
1121            name
1122        })
1123        .collect();
1124    assert!(instances.first().is_some_and(|n| n == "core" || n == "core0"), "the core's instances: {instances:?}");
1125    assert!(instances.len() == 1 || (0..instances.len()).all(|k| instances[k] == format!("core{k}")), "the cores' instances: {instances:?}");
1126    let mut twin = verilog.to_string();
1127    for (k, name) in instances.iter().enumerate() {
1128        let from = format!("  logos_engine_core_ce {name}(.clk(fclk), .step_en(fedge), .read_en(cr),");
1129        let gate = if k == 0 {
1130            "  // The simulator's twin: the core's clock without the user cycles it neither steps nor reads its memories in.\n  \
1131             reg core_gate = 1'b0;\n  always @(negedge fclk) core_gate <= fedge || cr;\n  wire core_clk = fclk && core_gate;\n"
1132        } else {
1133            ""
1134        };
1135        twin = twin.replacen(&from, &format!("{gate}  logos_engine_core_ce {name}(.clk(core_clk), .step_en(fedge), .read_en(cr),"), 1);
1136    }
1137    twin
1138}
1139
1140/// `verilog` with only the modules `top` instantiates, through its hierarchy, and `top` itself: of every other module
1141/// only the text before it, the macros it may define.
1142fn prune(verilog: &str, top: &str) -> String {
1143    let mut reached: Vec<String> = census(verilog, top).into_keys().collect();
1144    reached.push(top.to_string());
1145    modules(verilog)
1146        .into_iter()
1147        .map(|(name, text)| {
1148            if name.is_empty() || reached.contains(&name) {
1149                format!("{text}\n")
1150            } else {
1151                text[..text.find(&format!("module {name}")).expect("its header")].to_string()
1152            }
1153        })
1154        .collect()
1155}
1156
1157/// Every module of `verilog` and its text, the text before it with it.
1158fn modules(verilog: &str) -> Vec<(String, String)> {
1159    let mut out = Vec::new();
1160    let mut rest = verilog;
1161    loop {
1162        let start = rest.match_indices("module ").map(|(i, _)| i).find(|&i| i == 0 || rest.as_bytes()[i - 1] == b'\n');
1163        let Some(i) = start else {
1164            if !rest.trim().is_empty() {
1165                out.push((String::new(), rest.to_string()));
1166            }
1167            return out;
1168        };
1169        let end = i + rest[i..].find("endmodule").expect("a module's end") + "endmodule".len();
1170        let name: String = rest[i + 7..].chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
1171        out.push((name, rest[..end].to_string()));
1172        rest = &rest[end..];
1173    }
1174}
1175
1176/// `text` with every instance `pattern` begins renamed by `to` and given the enables, `enables` after its clock's
1177/// connection, the first `(.clk(clk), ` after it, within its statement.
1178fn enable_instances(text: &str, pattern: &str, to: &str, enables: &str) -> String {
1179    let mut out = String::new();
1180    let mut rest = text;
1181    while let Some(i) = rest.find(pattern) {
1182        out.push_str(&rest[..i]);
1183        out.push_str(to);
1184        rest = &rest[i + pattern.len()..];
1185        let clk = rest.find("(.clk(clk), ").expect("an instance's clock");
1186        assert!(!rest[..clk].contains(';'), "the clock of the instance `{pattern}` in its statement");
1187        out.push_str(&rest[..clk]);
1188        out.push_str("(.clk(clk), ");
1189        out.push_str(enables);
1190        rest = &rest[clk + "(.clk(clk), ".len()..];
1191    }
1192    out.push_str(rest);
1193    out
1194}
1195
1196/// The engine's core with every register stepping on the rising edges of its clock that `step_en` is high on, and
1197/// every memory reading on those `read_en` is high on. Only the modules under `logos_engine_core` are kept. Each one
1198/// with a clock, the memories aside, is renamed `<name>_ce`, given `step_en` and `read_en` beside its clock, and has its
1199/// clocked blocks put under `if (step_en)`; every instance of such a module is renamed and given both enables, and
1200/// every memory instance becomes a `logos_ram_ce_w<W>` given them. With `step_en` high in the clock's cycle that ends
1201/// with an engine step and `read_en` high in the cycle after the engine cycle's middle, the core steps as the engine
1202/// clocked by its own clock does, and a memory reads as its falling-edge read does.
1203fn enable(verilog: &str) -> String {
1204    let parts = modules(verilog);
1205    let is_ram = |n: &str| n.starts_with("logos_ram_w");
1206    let mut reached: Vec<String> = census(verilog, "logos_engine_core").into_keys().collect();
1207    reached.push("logos_engine_core".to_string());
1208    let clocked: Vec<String> =
1209        parts.iter().filter(|(n, t)| reached.contains(n) && !is_ram(n) && t.contains("(input clk,")).map(|(n, _)| n.clone()).collect();
1210    let mut widths: Vec<usize> = Vec::new();
1211    let mut out = String::new();
1212    for (name, text) in &parts {
1213        // A memory the enabled memories stand in for, or a module nothing under the core instantiates: only the text
1214        // before it, the macros it may define.
1215        if is_ram(name) || (!name.is_empty() && !reached.contains(name)) {
1216            out.push_str(&text[..text.find(&format!("module {name}")).expect("its header")]);
1217            continue;
1218        }
1219        let mut t = text.clone();
1220        assert!(!t.contains("step_en") && !t.contains("read_en"), "`{name}` names an enable");
1221        if clocked.contains(name) {
1222            let header = t.find(&format!("module {name}")).expect("its header") + "module ".len() + name.len();
1223            t.insert_str(header, "_ce");
1224            assert_eq!(t.matches("(input clk, input rst,").count(), 1, "`{name}`'s clock and reset");
1225            t = t.replacen("(input clk, input rst,", "(input clk, input step_en, input read_en, input rst,", 1);
1226            let blocks = t.matches("always @(posedge clk) begin").count();
1227            assert!(blocks >= 1 && t.matches("always @(").count() == blocks, "`{name}`'s every clocked block a rising-edge block");
1228            t = t.replace("always @(posedge clk) begin", "always @(posedge clk) if (step_en) begin");
1229        }
1230        for n in &clocked {
1231            t = enable_instances(&t, &format!("{n} #("), &format!("{n}_ce #("), ".step_en(step_en), .read_en(read_en), ");
1232        }
1233        while let Some(i) = t.find("logos_ram_w") {
1234            let digits: String = t[i + "logos_ram_w".len()..].chars().take_while(|c| c.is_ascii_digit()).collect();
1235            let w: usize = digits.parse().expect("a memory's width");
1236            if !widths.contains(&w) {
1237                widths.push(w);
1238            }
1239            let tail = enable_instances(&t[i..], &format!("logos_ram_w{w} #("), &format!("{} #(", super::ram::ram_ce_name(w)), ".ce(step_en), .cr(read_en), ");
1240            t = format!("{}{tail}", &t[..i]);
1241        }
1242        out.push_str(&t);
1243        out.push('\n');
1244    }
1245    widths.sort();
1246    format!("{}\n{out}", super::ram::ram_ce_verilog(&widths))
1247}
1248
1249/// An engine port as its template declares it: whether it is an input, its width's brackets and its name.
1250fn engine_port_decls() -> Vec<(bool, String, String)> {
1251    let (text, _) = engine_ports();
1252    text.split(',')
1253        .map(|d| {
1254            let d = d.trim().trim_start_matches('(').trim();
1255            let input = d.starts_with("input");
1256            let name = d.split_whitespace().last().expect("a port's name").to_string();
1257            let width = d.find('[').map_or(String::new(), |a| d[a..=d.find(']').expect("a width's end")].to_string());
1258            (input, width, name)
1259        })
1260        .collect()
1261}
1262
1263/// `logos_engine_bridge`, the crossing between the engine clock the shell runs in (sclk) and the user clock it is
1264/// divided from (fclk), its every register in the user clock: each value the shell gives the engine taken straight into
1265/// a register (cap_*) two user cycles after the engine clock's rising edge, so the core, stepping at the next rising
1266/// edge and reading its memories the cycle after the middle, sees the values the shell set at the edge before, as an
1267/// engine clocked by the shell's clock does; each value the engine gives the shell taken straight into a register
1268/// (held_*) two user cycles after the middle, holding past the next rising edge, when the shell takes it.
1269pub fn bridge_verilog() -> String {
1270    bridge_of(&engine_port_decls())
1271}
1272
1273/// What crosses between the shell and engine `p`: the engine's ports ([`engine_port_decls`]), each context's token-rows
1274/// side by side for an engine of several ([`Streaming::contexts`]), and the card port's map for a recurrent one
1275/// ([`bridged_map`]).
1276fn crossing_ports(p: &EngineParams) -> Vec<(bool, String, String)> {
1277    let contexts = p.streaming.as_ref().map_or(1, |s| s.contexts);
1278    let mut ports: Vec<(bool, String, String)> = engine_port_decls()
1279        .into_iter()
1280        .map(|(input, width, name)| if name == "rows" && contexts > 1 { (input, format!("[{}:0]", 16 * contexts - 1), name) } else { (input, width, name) })
1281        .collect();
1282    ports.extend(bridged_map(p));
1283    ports
1284}
1285
1286/// [`bridge_verilog`] for `crossing`, each `(input, width, name)` as [`engine_port_decls`] gives them.
1287fn bridge_of(crossing: &[(bool, String, String)]) -> String {
1288    let mut ports = Vec::new();
1289    let mut regs = String::new();
1290    let (mut caps, mut helds, mut assigns) = (String::new(), String::new(), String::new());
1291    for (input, width, name) in crossing.iter().cloned() {
1292        if name == "clk" {
1293            continue;
1294        }
1295        let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1296        if input {
1297            ports.push(format!("input {w}s_{name}, output {w}f_{name}"));
1298            writeln!(regs, "  reg {w}cap_{name};").unwrap();
1299            if name == "rst" {
1300                writeln!(caps, "    if (frst) cap_rst <= 1'b1; else if (emark2) cap_rst <= s_rst;").unwrap();
1301            } else {
1302                writeln!(caps, "    if (emark2) cap_{name} <= s_{name};").unwrap();
1303            }
1304            writeln!(assigns, "  assign f_{name} = cap_{name};").unwrap();
1305        } else {
1306            ports.push(format!("output {w}s_{name}, input {w}f_{name}"));
1307            writeln!(regs, "  reg {w}held_{name};").unwrap();
1308            writeln!(helds, "    if (mark2) held_{name} <= f_{name};").unwrap();
1309            writeln!(assigns, "  assign s_{name} = held_{name};").unwrap();
1310        }
1311    }
1312    format!(
1313        r#"// The crossing between the engine clock the shell runs in (sclk) and the user clock it is divided from (fclk), every
1314// register of it in the user clock (fedge and fmid high the user cycles that end with the engine clock's rising and
1315// falling edges): each value the shell gives the engine taken straight into a register (cap_*) two user cycles after the
1316// rising edge, each value the engine gives the shell taken straight into a register (held_*) two user cycles after the
1317// falling edge. With the engine clock {min} user cycles or more, a path from the shell into a cap_* register has two
1318// user cycles and one from a held_* register into the shell half an engine cycle less two user cycles.
1319module logos_engine_bridge
1320  (input sclk, input fclk, input frst, input fedge, input fmid,
1321   {ports});
1322{regs}  reg emark1, emark2, mark1, mark2;
1323  always @(posedge fclk) begin
1324    emark1 <= fedge;
1325    emark2 <= emark1;
1326    mark1 <= fmid;
1327    mark2 <= mark1;
1328{caps}{helds}  end
1329{assigns}endmodule
1330"#,
1331        min = ENGINE_MIN_DIVIDE,
1332        ports = ports.join(",\n   "),
1333    )
1334}
1335
1336/// `logos_engine`: the shell's engine, its core (`logos_engine_core_ce`) stepping in the user clock (`fclk`) at the
1337/// engine clock's rising edges (`fedge`), its memories reading the user cycle after its falling edges, the shell's
1338/// values and the engine's crossing between the engine clock (`clk`) and the user clock through `logos_engine_bridge`;
1339/// a hybrid engine's matvec jobs on its streaming unit in the user clock (`logos_engine_fast`), with the four channels'
1340/// read ports.
1341fn top_verilog(p: &EngineParams) -> String {
1342    // A streaming unit whose cores are paired runs their processing elements in clk2, twice the fast side's clock.
1343    let (clk2_in, clk2_pass) = if p.streaming.as_ref().is_some_and(|s| s.set.pe_pairs) { (" input clk2,", ".clk2(clk2), ") } else { ("", "") };
1344    if let Some(s) = p.streaming.as_ref().filter(|s| s.contexts > 1) {
1345        return top_contexts_verilog(p, s);
1346    }
1347    let decls = engine_port_decls();
1348    let (ports, _) = engine_ports();
1349    let mut wires = String::new();
1350    let mut bridge = vec![".sclk(clk), .fclk(fclk), .frst(frst), .fedge(fedge), .fmid(fmid)".to_string()];
1351    let mut core = vec![".clk(fclk), .step_en(fedge), .read_en(cr)".to_string()];
1352    for (_, width, name) in &decls {
1353        if name == "clk" {
1354            continue;
1355        }
1356        let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1357        writeln!(wires, "  wire {w}f_{name};").unwrap();
1358        bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
1359        core.push(format!(".{name}(f_{name})"));
1360    }
1361    for (_, width, name) in &bridged_map(p) {
1362        let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1363        writeln!(wires, "  wire {w}f_{name};").unwrap();
1364        bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
1365    }
1366    let (fast_ports, fast) = match &p.streaming {
1367        None => (String::new(), String::new()),
1368        Some(s) => {
1369            use super::stream::{AW, LW};
1370            let sa = store_address_bits(p);
1371            let (j, mdt, drt) = (HybridJob::BITS, 4 * (AW + LW + 1) - 1, 4 * 8 * s.layout.line_bytes - 1);
1372            core.push(".sm_own(own), .sm_go(go), .sm_job(job), .sm_busy(held_busy), .e_ra(e_ra), .e_we(e_we), .e_wa(e_wa), .e_wd(e_wd), .e_wm(e_wm), .e_rd(e_rd)".to_string());
1373            if s.vectors {
1374                core.push(".sm_vec_go(vec_go), .sm_vec_job(vec_job), .sm_vec_fault(vec_fault)".to_string());
1375            }
1376            if s.attention {
1377                core.push(".sm_att_go(att_go), .sm_att_job(att_job)".to_string());
1378            }
1379            let (attention_wires, attention) = if s.attention {
1380                (format!("  wire att_go;\n  wire [{}:0] att_job;\n", AttendJob::BITS - 1), ",\n    .att_go(att_go), .att_job(att_job)".to_string())
1381            } else {
1382                (String::new(), String::new())
1383            };
1384            let (vector_wires, vectors) = if s.vectors {
1385                (
1386                    format!("  wire vec_go, vec_fault;\n  wire [{}:0] vec_job;\n", VectorSideJob::BITS - 1),
1387                    ",\n    .vec_go(vec_go), .vec_job(vec_job), .vec_fault(vec_fault)"
1388                        .to_string(),
1389                )
1390            } else {
1391                (String::new(), String::new())
1392            };
1393            let (recurrence_ports, recurrence_wires, recurrence) = if s.recurrence {
1394                core.push(".sm_gdn_go(gdn_go), .sm_gdn_job(gdn_job), .sm_gdn_fault(gdn_fault)".to_string());
1395                (
1396                    format!(
1397                        ",\n   output [3:0] sm_mw_valid, input [3:0] sm_mw_ready, output [{drt}:0] sm_mw_data,\n   \
1398                         input [3:0] sm_mdone_valid, output [3:0] sm_mdone_ready,\n   \
1399                         input [31:0] map_space, input [{aw}:0] map_base, input [4:0] map_granule_log2",
1400                        aw = AW - 1
1401                    ),
1402                    format!(
1403                        "  wire gdn_go, gdn_fault;\n  wire [{}:0] gdn_job;\n  wire [3:0] p_mw_valid, p_mw_ready, p_mdone_valid, p_mdone_ready;\n  \
1404                         wire [{drt}:0] p_mw_data;\n",
1405                        GdnJob::bits(s) - 1
1406                    ),
1407                    ",\n    .mw_valid(p_mw_valid), .mw_ready(p_mw_ready), .mw_data(p_mw_data), .mdone_valid(p_mdone_valid),\n    \
1408                     .mdone_ready(p_mdone_ready), .gdn_go(gdn_go), .gdn_job(gdn_job), .gdn_fault(gdn_fault),\n    \
1409                     .host_wr(f_host_wr), .host_table(f_host_table), .host_addr(f_host_addr), .host_wdata(f_host_wdata),\n    \
1410                     .map_space(f_map_space), .map_base(f_map_base), .map_granule_log2(f_map_granule_log2)"
1411                        .to_string(),
1412                )
1413            } else {
1414                (String::new(), String::new(), String::new())
1415            };
1416            (
1417                format!(
1418                    ",\n   output [3:0] sm_md_valid, input [3:0] sm_md_ready, output [{mdt}:0] sm_md_data,\n   input [3:0] sm_mr_valid, output [3:0] sm_mr_ready, input [{drt}:0] sm_mr_data{recurrence_ports}"
1419                ),
1420                format!(
1421                    r#"  wire own, go, held_busy, e_we;
1422  wire [{jt}:0] job;
1423  wire [31:0] e_ra, e_wa;
1424  wire [511:0] e_wd, e_rd;
1425  wire [63:0] e_wm;
1426{recurrence_wires}{vector_wires}{attention_wires}{pipes}  logos_engine_fast fast(.clk(fclk), {clk2_pass}.rst(frst), .ce(fedge), .cr(cr), .own(own), .go(go), .job(job), .e_ra(e_ra[{st}:0]),
1427    .e_we(e_we), .e_wa(e_wa[{st}:0]), .e_wd(e_wd), .e_wm(e_wm), .rd(e_rd), .busy_held(held_busy), .md_valid(p_md_valid),
1428    .md_ready(p_md_ready), .md_data(p_md_data), .mr_valid(p_mr_valid), .mr_ready(p_mr_ready), .mr_data(p_mr_data){recurrence}{vectors}{attention});
1429"#,
1430                    pipes = channel_pipes(s),
1431                    jt = j - 1,
1432                    st = sa - 1,
1433                ),
1434            )
1435        }
1436    };
1437    format!(
1438        r#"// The engine: its core stepping in the user clock (fclk) at the engine clock's rising edges (fedge) and reading its
1439// memories the user cycle after the falling edges (cr), the shell's values and its own through the crossing between the
1440// engine clock (clk) and the user clock{hybrid}.
1441module logos_engine
1442{top_ports},
1443   input fclk,{clk2_in} input frst, input fedge, input fmid{fast_ports});
1444  // The cycle after the engine clock's falling edge, the core's memories' read: a register its loads are spread from.
1445  (* maxfan = 2000 *) reg cr;
1446  always @(posedge fclk) cr <= fmid;
1447{wires}  logos_engine_bridge bridge({bridge});
1448{fast}  logos_engine_core_ce core({core});
1449endmodule
1450"#,
1451        hybrid = match &p.streaming {
1452            Some(s) if s.vectors => "; its matvec jobs, its lazily flushed memories' token-rows and its vector jobs on the units in the user clock",
1453            Some(s) if s.recurrence => "; its matvec jobs and its lazily flushed memories' token-rows on the units in the user clock",
1454            Some(_) => "; its matvec jobs on the streaming unit in the user clock",
1455            None => "",
1456        },
1457        top_ports = ports.replace("output reg ", "output "),
1458        bridge = bridge.join(",\n    "),
1459        core = core.join(",\n    "),
1460    )
1461}
1462
1463/// `logos_engine` for an engine of [`Streaming::contexts`] contexts ([`fast_contexts`]): a core a context, each
1464/// started with the step when its context has token-rows, its rows from its own field of `rows`; each takes the host's
1465/// writes to the tables every context shares and those to its own rows table and bank of the store — its context in the
1466/// host address's top four bits, cleared in the core's — and the host reads the bank `host_raddr`'s top bits name. The
1467/// engine is busy while any core is, faults when any does, and gives the shell each core's program counter in 16 bits,
1468/// context 0's lowest. The cores read the card through its one port a read at a time, the first asking after the last
1469/// served, each answer going to the core that asked; no core of such an engine writes the card.
1470fn top_contexts_verilog(p: &EngineParams, s: &Streaming) -> String {
1471    // A streaming unit whose cores are paired runs their processing elements in clk2, twice the fast side's clock.
1472    let (clk2_in, clk2_pass) = if s.set.pe_pairs { (" input clk2,", ".clk2(clk2), ") } else { ("", "") };
1473    let c = s.contexts;
1474    let cb = if c > 1 { (usize::BITS - (c - 1).leading_zeros()) as usize } else { 1 };
1475    let sa = store_address_bits(p);
1476    let (j, g, v, a) = (HybridJob::BITS, GdnJob::bits(s), VectorSideJob::BITS, AttendJob::BITS);
1477    let drt = 4 * 8 * s.layout.line_bytes as usize - 1;
1478    let (ports, _) = engine_ports();
1479    assert_eq!(ports.matches("input [15:0] rows,").count(), 1, "the engine's rows port");
1480    let top_ports = ports.replace("input [15:0] rows,", &format!("input [{}:0] rows,", 16 * c - 1)).replace("output reg ", "output ");
1481    let mut wires = String::new();
1482    let mut bridge = vec![".sclk(clk), .fclk(fclk), .frst(frst), .fedge(fedge), .fmid(fmid)".to_string()];
1483    for (_, width, name) in &crossing_ports(p) {
1484        if name == "clk" {
1485            continue;
1486        }
1487        let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1488        writeln!(wires, "  wire {w}f_{name};").unwrap();
1489        bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
1490    }
1491    let field = |bits: usize, k: usize| format!("[{}:{}]", bits * k + bits - 1, bits * k);
1492    let mut cores = String::new();
1493    for k in 0..c {
1494        let mut conns = vec![".clk(fclk), .step_en(fedge), .read_en(cr)".to_string()];
1495        for (_, _, name) in engine_port_decls() {
1496            if name == "clk" {
1497                continue;
1498            }
1499            let to = match name.as_str() {
1500                "start" => format!("f_start && f_rows{} != 16'd0", field(16, k)),
1501                "rows" => format!("f_rows{}", field(16, k)),
1502                "busy" => format!("c_busy[{k}]"),
1503                "fault" => format!("c_fault[{k}]"),
1504                "pc" => format!("c_pc{}", field(32, k)),
1505                "host_wr" => format!("c_host_wr[{k}]"),
1506                "host_addr" => "c_host_addr".to_string(),
1507                "host_raddr" => "c_host_raddr".to_string(),
1508                "host_rdata" => format!("c_host_rdata{}", field(512, k)),
1509                "mem_rd" => format!("c_mem_rd[{k}]"),
1510                "mem_rd_line" => format!("c_mem_rd_line{}", field(32, k)),
1511                "mem_rd_valid" => format!("c_mem_rd_valid[{k}]"),
1512                "mem_wr" => format!("c_mem_wr[{k}]"),
1513                "mem_wr_line" => format!("c_mem_wr_line{}", field(32, k)),
1514                "mem_wr_data" => format!("c_mem_wr_data{}", field(512, k)),
1515                "mem_wr_mask" => format!("c_mem_wr_mask{}", field(64, k)),
1516                "mem_wr_ready" => "1'b0".to_string(),
1517                _ => format!("f_{name}"),
1518            };
1519            conns.push(format!(".{name}({to})"));
1520        }
1521        conns.push(format!(
1522            ".sm_own(own[{k}]), .sm_go(go[{k}]), .sm_job(job{jf}), .sm_busy(held_busy[{k}]), .e_ra(e_ra{e32}), .e_we(e_we[{k}]),\n    \
1523             .e_wa(e_wa{e32}), .e_wd(e_wd{e512}), .e_wm(e_wm{e64}), .e_rd(e_rd{e512})",
1524            jf = field(j, k),
1525            e32 = field(32, k),
1526            e512 = field(512, k),
1527            e64 = field(64, k),
1528        ));
1529        conns.push(format!(".sm_vec_go(vec_go[{k}]), .sm_vec_job(vec_job{}), .sm_vec_fault(vec_fault[{k}])", field(v, k)));
1530        conns.push(format!(".sm_att_go(att_go[{k}]), .sm_att_job(att_job{})", field(a, k)));
1531        conns.push(format!(".sm_gdn_go(gdn_go[{k}]), .sm_gdn_job(gdn_job{}), .sm_gdn_fault(gdn_fault[{k}])", field(g, k)));
1532        writeln!(cores, "  logos_engine_core_ce core{k}({});", conns.join(",\n    ")).unwrap();
1533        writeln!(cores, "  assign c_host_wr[{k}] = f_host_wr && (host_shared || f_host_addr[31:28] == 4'd{k});").unwrap();
1534    }
1535    let packed = |name: &str| (0..c).rev().map(|k| format!("{name}[{}:{}]", 32 * k + sa - 1, 32 * k)).collect::<Vec<_>>().join(", ");
1536    let pcs = (0..c).rev().map(|k| format!("c_pc[{}:{}]", 32 * k + 15, 32 * k)).collect::<Vec<_>>().join(", ");
1537    format!(
1538        r#"// The engine of {c} contexts: a core a context stepping in the user clock (fclk) at the engine clock's rising edges
1539// (fedge) and reading its memories the user cycle after the falling edges (cr), the shell's values and the engine's
1540// through the crossing between the engine clock (clk) and the user clock; the units in the user clock, each class of
1541// them serving one context at a time.
1542module logos_engine
1543{top_ports},
1544   input fclk,{clk2_in} input frst, input fedge, input fmid,
1545   output [3:0] sm_md_valid, input [3:0] sm_md_ready, output [{mdt}:0] sm_md_data,
1546   input [3:0] sm_mr_valid, output [3:0] sm_mr_ready, input [{drt}:0] sm_mr_data,
1547   output [3:0] sm_mw_valid, input [3:0] sm_mw_ready, output [{drt}:0] sm_mw_data,
1548   input [3:0] sm_mdone_valid, output [3:0] sm_mdone_ready,
1549   input [31:0] map_space, input [{awt}:0] map_base, input [4:0] map_granule_log2);
1550  // The cycle after the engine clock's falling edge, the core's memories' read: a register its loads are spread from.
1551  (* maxfan = 2000 *) reg cr;
1552  always @(posedge fclk) cr <= fmid;
1553{wires}  logos_engine_bridge bridge({bridge});
1554  wire [{ct}:0] own, go, held_busy, e_we;
1555  wire [{cj}:0] job;
1556  wire [{c32}:0] e_ra, e_wa;
1557  wire [{c512}:0] e_wd, e_rd;
1558  wire [{c64}:0] e_wm;
1559  wire [{ct}:0] gdn_go, gdn_fault;
1560  wire [{cg}:0] gdn_job;
1561  wire [3:0] p_mw_valid, p_mw_ready, p_mdone_valid, p_mdone_ready;
1562  wire [{drt}:0] p_mw_data;
1563  wire [{ct}:0] vec_go, vec_fault;
1564  wire [{cv}:0] vec_job;
1565  wire [{ct}:0] att_go;
1566  wire [{ca}:0] att_job;
1567{pipes}  logos_engine_fast fast(.clk(fclk), {clk2_pass}.rst(frst), .ce(fedge), .cr(cr), .own(own), .go(go), .job(job), .e_ra({{{e_ra}}}),
1568    .e_we(e_we), .e_wa({{{e_wa}}}), .e_wd(e_wd), .e_wm(e_wm), .rd(e_rd), .busy_held(held_busy), .md_valid(p_md_valid),
1569    .md_ready(p_md_ready), .md_data(p_md_data), .mr_valid(p_mr_valid), .mr_ready(p_mr_ready), .mr_data(p_mr_data),
1570    .mw_valid(p_mw_valid), .mw_ready(p_mw_ready), .mw_data(p_mw_data), .mdone_valid(p_mdone_valid),
1571    .mdone_ready(p_mdone_ready), .gdn_go(gdn_go), .gdn_job(gdn_job), .gdn_fault(gdn_fault), .map_space(f_map_space),
1572    .map_base(f_map_base), .map_granule_log2(f_map_granule_log2),
1573    .vec_go(vec_go), .vec_job(vec_job), .vec_fault(vec_fault), .host_wr(f_host_wr), .host_table(f_host_table),
1574    .host_addr(f_host_addr), .host_wdata(f_host_wdata),
1575    .att_go(att_go), .att_job(att_job));
1576  // Each core takes the host's writes to the tables every context shares, and those to its own rows table and bank
1577  // of the store, the context in the host address's top four bits, cleared in the core's.
1578  wire host_shared = f_host_table != 3'd1 && f_host_table < 3'd6;
1579  wire [{ct}:0] c_host_wr, c_busy, c_fault, c_mem_rd, c_mem_wr, c_mem_rd_valid;
1580  wire [31:0] c_host_addr = {{4'd0, f_host_addr[27:0]}};
1581  wire [31:0] c_host_raddr = {{4'd0, f_host_raddr[27:0]}};
1582  wire [{c512}:0] c_host_rdata, c_mem_wr_data;
1583  wire [{c32}:0] c_pc, c_mem_rd_line, c_mem_wr_line;
1584  wire [{c64}:0] c_mem_wr_mask;
1585{cores}  assign f_host_rdata = c_host_rdata[512*f_host_raddr[{cbh}:28] +: 512];
1586  assign f_busy = |c_busy;
1587  assign f_fault = |c_fault;
1588  assign f_pc = {{{pcs}}};
1589  // The cores' reads of the card through its one port, a read at a time.
1590  function [{cbt}:0] after(input [{ct}:0] want, input [{cbt}:0] last);
1591    integer i, k;
1592    reg found;
1593    begin
1594      found = 1'b0;
1595      after = last;
1596      for (i = 1; i <= {c}; i = i + 1) begin
1597        k = (last + i) % {c};
1598        if (!found && want[k]) begin found = 1'b1; after = k[{cbt}:0]; end
1599      end
1600    end
1601  endfunction
1602  reg [{ct}:0] rd_pend;
1603  reg [{c32}:0] rd_pend_line;
1604  reg rd_out;
1605  reg [{cbt}:0] rd_owner;
1606  wire [{ct}:0] rd_want = rd_pend | c_mem_rd;
1607  wire [{cbt}:0] rd_to = after(rd_want, rd_owner);
1608  wire rd_issue = rd_want != {c}'d0 && (!rd_out || f_mem_rd_valid);
1609  assign f_mem_rd = rd_issue;
1610  assign f_mem_rd_line = rd_pend[rd_to] ? rd_pend_line[32*rd_to +: 32] : c_mem_rd_line[32*rd_to +: 32];
1611  genvar rd_at;
1612  generate for (rd_at = 0; rd_at < {c}; rd_at = rd_at + 1) begin : rd_route
1613    assign c_mem_rd_valid[rd_at] = rd_out && f_mem_rd_valid && rd_owner == rd_at;
1614  end endgenerate
1615  assign f_mem_wr = 1'b0;
1616  assign f_mem_wr_line = 32'd0;
1617  assign f_mem_wr_data = 512'd0;
1618  assign f_mem_wr_mask = 64'd0;
1619  integer rq;
1620  always @(posedge fclk) if (fedge) begin
1621    if (f_rst) begin rd_pend <= {c}'d0; rd_out <= 1'b0; rd_owner <= {cb}'d0; end
1622    else begin
1623      for (rq = 0; rq < {c}; rq = rq + 1)
1624        if (c_mem_rd[rq] && !(rd_issue && rd_to == rq[{cbt}:0])) rd_pend_line[32*rq +: 32] <= c_mem_rd_line[32*rq +: 32];
1625      rd_pend <= rd_want & ~({{{{{cm}{{1'b0}}}}, rd_issue}} << rd_to);
1626      if (rd_issue) begin rd_out <= 1'b1; rd_owner <= rd_to; end
1627      else if (f_mem_rd_valid) rd_out <= 1'b0;
1628    end
1629  end
1630endmodule
1631"#,
1632        mdt = 4 * (super::stream::AW as usize + super::stream::LW as usize + 1) - 1,
1633        awt = super::stream::AW - 1,
1634        ct = c - 1,
1635        cm = c - 1,
1636        cbt = cb - 1,
1637        cbh = 28 + cb - 1,
1638        cj = c * j - 1,
1639        c32 = 32 * c - 1,
1640        c512 = 512 * c - 1,
1641        c64 = 64 * c - 1,
1642        cg = c * g - 1,
1643        cv = c * v - 1,
1644        ca = c * a - 1,
1645        e_ra = packed("e_ra"),
1646        e_wa = packed("e_wa"),
1647        pipes = channel_pipes(s),
1648        bridge = bridge.join(",\n    "),
1649    )
1650}
1651
1652/// The pipes each channel's fast port crosses at the engine's edge, each way: a path from a channel's bank at the die's
1653/// edge to the fast side a pipe each part of the way.
1654pub const PORT_PIPES: usize = 3;
1655
1656/// Each channel's fast port through [`PORT_PIPES`] pipes each way at the engine's edge ([`top_verilog`]'s): the
1657/// descriptors and lines, and a recurrent engine's writes and write-done tokens.
1658fn channel_pipes(s: &Streaming) -> String {
1659    use super::stream::{AW, LW};
1660    let (mdt, drt) = (4 * (AW + LW + 1) - 1, 4 * 8 * s.layout.line_bytes - 1);
1661    format!(
1662        r#"  // Each channel's fast port through PORT_PIPES pipes each way at the engine's edge, so the path from the channel's bank
1663  // to the fast side crosses the die a pipe each part of the way.
1664  wire [3:0] p_md_valid, p_md_ready, p_mr_valid, p_mr_ready;
1665  wire [{mdt}:0] p_md_data;
1666  wire [{drt}:0] p_mr_data;
1667  genvar pipe_ch;
1668  generate for (pipe_ch = 0; pipe_ch < 4; pipe_ch = pipe_ch + 1) begin : port_pipe
1669    logos_pipe_chain #(.W({mdw}), .K({k})) md(.clk(fclk), .rst_n(!frst), .in_valid(p_md_valid[pipe_ch]), .in_ready(p_md_ready[pipe_ch]),
1670      .in_data(p_md_data[{mdw}*pipe_ch +: {mdw}]), .out_valid(sm_md_valid[pipe_ch]), .out_ready(sm_md_ready[pipe_ch]),
1671      .out_data(sm_md_data[{mdw}*pipe_ch +: {mdw}]));
1672    logos_pipe_chain #(.W({dw}), .K({k})) mr(.clk(fclk), .rst_n(!frst), .in_valid(sm_mr_valid[pipe_ch]), .in_ready(sm_mr_ready[pipe_ch]),
1673      .in_data(sm_mr_data[{dw}*pipe_ch +: {dw}]), .out_valid(p_mr_valid[pipe_ch]), .out_ready(p_mr_ready[pipe_ch]),
1674      .out_data(p_mr_data[{dw}*pipe_ch +: {dw}]));
1675{write_pipes}  end endgenerate
1676"#,
1677        mdw = AW + LW + 1,
1678        dw = 8 * s.layout.line_bytes,
1679        k = PORT_PIPES,
1680        write_pipes = if s.recurrence {
1681            format!(
1682                "    logos_pipe_chain #(.W({dw}), .K({k})) mw(.clk(fclk), .rst_n(!frst), .in_valid(p_mw_valid[pipe_ch]), .in_ready(p_mw_ready[pipe_ch]),\n      \
1683                 .in_data(p_mw_data[{dw}*pipe_ch +: {dw}]), .out_valid(sm_mw_valid[pipe_ch]), .out_ready(sm_mw_ready[pipe_ch]),\n      \
1684                 .out_data(sm_mw_data[{dw}*pipe_ch +: {dw}]));\n    wire done_bit;\n    \
1685                 logos_pipe_chain #(.W(1), .K({k})) mdone(.clk(fclk), .rst_n(!frst), .in_valid(sm_mdone_valid[pipe_ch]), .in_ready(sm_mdone_ready[pipe_ch]),\n      \
1686                 .in_data(1'b0), .out_valid(p_mdone_valid[pipe_ch]), .out_ready(p_mdone_ready[pipe_ch]), .out_data(done_bit));\n",
1687                dw = 8 * s.layout.line_bytes,
1688                k = PORT_PIPES
1689            )
1690        } else {
1691            String::new()
1692        },
1693    )
1694}
1695
1696/// Bits of a store beat's address.
1697fn store_address_bits(p: &EngineParams) -> usize {
1698    if p.store_beats > 1 {
1699        (usize::BITS - (p.store_beats - 1).leading_zeros()) as usize
1700    } else {
1701        1
1702    }
1703}
1704
1705/// The engine's ports as its template declares them, and their names in order.
1706fn engine_ports() -> (&'static str, Vec<&'static str>) {
1707    let start = ENGINE_VERILOG.find("  (input clk").expect("the engine's ports");
1708    let end = ENGINE_VERILOG.find("@PORTS@);").expect("the engine's ports' end");
1709    let text = &ENGINE_VERILOG[start..end];
1710    let names = text.split(',').map(|d| d.split_whitespace().last().expect("a port's name")).collect();
1711    (text, names)
1712}
1713
1714
1715/// `logos_engine_fast`, a hybrid engine's user-clock side: its streaming unit and the token-row store, the store the
1716/// unit's while the core gives it the job (`own`) and the core's otherwise, read the cycle after the engine cycle's
1717/// middle (`cr`) and written at its step (`ce`) as the core's own memories are; the unit started the cycle after the
1718/// middle of the engine cycle the core gives it the job in (`go`), its busy held from the cycle after each middle.
1719fn fast_verilog(p: &EngineParams, s: &Streaming) -> String {
1720    // A streaming unit whose cores are paired runs their processing elements in clk2, twice the fast side's clock.
1721    let (clk2_in, clk2_pass) = if s.set.pe_pairs { (" input clk2,", ".clk2(clk2), ") } else { ("", "") };
1722    use super::stream::{AW, LW};
1723    let sa = store_address_bits(p);
1724    let j = HybridJob::BITS;
1725    let set = &s.set;
1726    // A token-row's activation words: a row's blocks of the format with the most, an octet's eight blocks one word.
1727    let nb_max = p.formats.iter().map(|&(ty, blocks)| (blocks * ty.block_len()).div_ceil(set.block_len(ty))).max().unwrap_or(1);
1728    let mdw = AW + LW + 1;
1729    let dw = 8 * s.layout.line_bytes;
1730    let text = format!(
1731        r#"// A hybrid engine's user-clock side: the streaming unit and the token-row store, the store the unit's while it owns it
1732// (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and written at its step (ce).
1733module logos_engine_fast
1734  (input clk,{clk2_in} input rst, input ce, input cr, input own, input go, input [{jt}:0] job,
1735   input [{st}:0] e_ra, input e_we, input [{st}:0] e_wa, input [511:0] e_wd, input [63:0] e_wm,
1736   output [511:0] rd, output reg busy_held,
1737   output [3:0] md_valid, input [3:0] md_ready, output [{mdt}:0] md_data,
1738   input [3:0] mr_valid, output [3:0] mr_ready, input [{drt}:0] mr_data);
1739  wire busy, u_we;
1740  wire start = go && cr;
1741  wire [{st}:0] u_ra, u_wa;
1742  wire [511:0] u_wd;
1743  wire [63:0] u_wm;
1744  wire [1023:0] runs = job[{runs_top}:{runs}];
1745  logos_streaming_matvec_{name} #(.N({n}), .T_MAX({t_max}), .NB_MAX({nb_max}), .SA({sa})) unit(.clk(clk), {clk2_pass}.rst(rst), .start(start),
1746    .greedy(job[{greedy}]), {fold}{fmt}.blocks(job[47:32]), .tokens(job[{tokens_top}:{tokens}]), {two}.rows(job[95:64]), .stripes(job[111:96]),
1747    .turns(job[135:128]), .from_base(job[{from_base}]), .from_beats(job[{from_beats}]), .to_base(job[{to_base}]),
1748    .to_beats(job[{to_beats}]), .busy(busy), .st_ra(u_ra), .st_rd(rd), .st_we(u_we), .st_wa(u_wa), .st_wd(u_wd),
1749    .st_wm(u_wm), .tr_count({count}), .tr_line({line}), .tr_lines({lines}), .tr_bytes({bytes}),
1750    .tr_last_lines({last_lines}), .tr_last_bytes({last_bytes}), .tr_row({row}), .tr_row_step({row_step}),
1751    .tensor(job[{tensor_top}:160]), .md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),
1752    .mr_ready(mr_ready), .mr_data(mr_data));
1753  always @(posedge clk)
1754    if (rst) busy_held <= 1'b0;
1755    else if (cr) busy_held <= busy || start;
1756  logos_bram_re_w512 #(.D({store}), .A({sa})) store(.clk(clk), .re(own || cr), .ra(own ? u_ra : e_ra), .rd(rd),
1757    .we(own ? u_we : ce && e_we), .wa(own ? u_wa : e_wa), .wd(own ? u_wd : e_wd), .wm(own ? u_wm : e_wm));
1758endmodule
1759"#,
1760        jt = j - 1,
1761        st = sa - 1,
1762        mdt = 4 * mdw - 1,
1763        drt = 4 * dw - 1,
1764        runs = HybridJob::RUNS,
1765        runs_top = HybridJob::RUNS + 1023,
1766        greedy = HybridJob::GREEDY,
1767        fold = if s.folds > 0 { format!(".fold(job[{}:{}]), ", HybridJob::FOLD + 1, HybridJob::FOLD) } else { String::new() },
1768        name = set.name,
1769        n = s.pairs(),
1770        t_max = p.rows,
1771        fmt = if set.formats.len() > 1 { ".fmt(job[3:0]), " } else { "" },
1772        two = if set.words == Words::Two { format!(".two(job[{}]), ", HybridJob::TWO) } else { String::new() },
1773        tokens = HybridJob::TOKENS,
1774        tokens_top = HybridJob::TOKENS + 7,
1775        from_base = format!("{}:{}", HybridJob::FROM + sa - 1, HybridJob::FROM),
1776        from_beats = format!("{}:{}", HybridJob::FROM + 47, HybridJob::FROM + 32),
1777        to_base = format!("{}:{}", HybridJob::TO + sa - 1, HybridJob::TO),
1778        to_beats = format!("{}:{}", HybridJob::TO + 47, HybridJob::TO + 32),
1779        count = runs_field(0, 32),
1780        line = runs_field(1, AW),
1781        lines = runs_field(2, LW),
1782        bytes = runs_field(3, super::stream::BYW),
1783        last_lines = runs_field(4, LW),
1784        last_bytes = runs_field(5, super::stream::BYW),
1785        row = runs_field(6, super::stream::ROWW),
1786        row_step = runs_field(7, super::stream::ROWW),
1787        tensor_top = 160 + super::stream::TW - 1,
1788        store = p.store_beats,
1789    );
1790    let text = if s.recurrence { fast_recurrence(&text, p, s) } else { text };
1791    let text = if s.vectors { fast_vectors(&text, p) } else { text };
1792    let text = if s.attention { fast_attention(&text, p) } else { text };
1793    let text = if s.recurrence_lanes > 1 { fast_lanes(&text, p, s) } else { text };
1794    if s.contexts > 1 {
1795        fast_contexts(&text, p, s)
1796    } else {
1797        text
1798    }
1799}
1800
1801/// `logos_engine_fast` with its recurrence unit on head lanes ([`Streaming::recurrence_lanes`]), from the text of every
1802/// other unit beside it: `logos_gdn_lanes` for `logos_gdn_side`, each lane's rows through a `logos_fast_port` of its
1803/// own, the shared port (the coordinator's and the vector and attention sides') and the lanes' onto the channels
1804/// through `logos_channel_mux`, the shared port the mux's port 0 and lane k's port 1 + k; the recurrence's fault any
1805/// port's or the lanes' own.
1806fn fast_lanes(text: &str, p: &EngineParams, s: &Streaming) -> String {
1807    use super::stream::{AW, LW};
1808    let (hd, flush) = p.delta.expect("a recurrence unit runs a model's Gated-DeltaNet blocks");
1809    let (t_max, window) = recurrence_log(flush);
1810    let sa = store_address_bits(p);
1811    let (l, dw, mdw) = (s.recurrence_lanes, 8 * s.layout.line_bytes as usize, (AW + LW + 1) as usize);
1812    let lba = (usize::BITS - (s.log_lines.max(2) - 1).leading_zeros()) as usize;
1813    let ports = 4 * (l + 1);
1814    let lanes = format!(
1815        r#"  // The recurrence's head lanes: each lane's rows and flushed rows through a fast port of its own, the lanes' ports and
1816  // the shared one onto the channels at once (port 0 the shared one, port 1 + k lane k's).
1817  wire [{lt}:0] l_rq_valid, l_rq_ready, l_rs_valid, l_wq_valid, l_wq_ready, l_writing, l_fault;
1818  wire [{lt32}:0] l_rq_line, l_wq_line;
1819  wire [{ltd}:0] l_rs_data, l_wq_data;
1820  wire p_fault, lanes_fault;
1821  wire [{pt}:0] c_md_valid, c_md_ready, c_mr_valid, c_mr_ready, c_mw_valid, c_mw_ready, c_mdone_valid, c_mdone_ready;
1822  wire [{pmd}:0] c_md_data;
1823  wire [{pdt}:0] c_mw_data;
1824  genvar lk;
1825  generate
1826    for (lk = 0; lk < {l}; lk = lk + 1) begin : lane_port
1827      logos_fast_port #(.NB(4), .AW({aw}), .LW({lw}), .DL({dw})) port(.clk(clk), .rst(rst), .space(map_space), .base(map_base),
1828        .granule_log2(map_granule_log2), .clear(g_start), .fault(l_fault[lk]), .rq_valid(l_rq_valid[lk]),
1829        .rq_ready(l_rq_ready[lk]), .rq_line(l_rq_line[32*lk +: 32]), .rs_valid(l_rs_valid[lk]),
1830        .rs_data(l_rs_data[{dw}*lk +: {dw}]), .wq_valid(l_wq_valid[lk]), .wq_ready(l_wq_ready[lk]),
1831        .wq_line(l_wq_line[32*lk +: 32]), .wq_data(l_wq_data[{dw}*lk +: {dw}]), .writing(l_writing[lk]),
1832        .md_valid(c_md_valid[4*(lk+1) +: 4]), .md_ready(c_md_ready[4*(lk+1) +: 4]),
1833        .md_data(c_md_data[{mdw4}*(lk+1) +: {mdw4}]), .mr_valid(c_mr_valid[4*(lk+1) +: 4]),
1834        .mr_ready(c_mr_ready[4*(lk+1) +: 4]), .mr_data(mr_data), .mw_valid(c_mw_valid[4*(lk+1) +: 4]),
1835        .mw_ready(c_mw_ready[4*(lk+1) +: 4]), .mw_data(c_mw_data[{dw4}*(lk+1) +: {dw4}]),
1836        .mdone_valid(c_mdone_valid[4*(lk+1) +: 4]), .mdone_ready(c_mdone_ready[4*(lk+1) +: 4]));
1837    end
1838  endgenerate
1839  // The ports straight onto the multiplexer, which holds each port's descriptors and write lines in registers of its own.
1840  logos_channel_mux #(.P({np}), .NB(4), .AW({aw}), .LW({lw}), .DL({dw})) mux(.clk(clk), .rst(rst), .pd_valid(c_md_valid),
1841    .pd_ready(c_md_ready), .pd_data(c_md_data), .pr_valid(c_mr_valid), .pr_ready(c_mr_ready), .pw_valid(c_mw_valid),
1842    .pw_ready(c_mw_ready), .pw_data(c_mw_data), .pdone_valid(c_mdone_valid), .pdone_ready(c_mdone_ready),
1843    .md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data), .mr_valid(mr_valid), .mr_ready(p_mr_ready),
1844    .mw_valid(mw_valid), .mw_ready(mw_ready), .mw_data(mw_data), .mdone_valid(mdone_valid), .mdone_ready(mdone_ready));
1845  assign gdn_fault = p_fault || l_fault != {l}'d0 || lanes_fault;
1846"#,
1847        lt = l - 1,
1848        lt32 = 32 * l - 1,
1849        ltd = dw * l - 1,
1850        pt = ports - 1,
1851        pmd = ports * mdw - 1,
1852        pdt = ports * dw - 1,
1853        mdw4 = 4 * mdw,
1854        dw4 = 4 * dw,
1855        np = l + 1,
1856        aw = AW,
1857        lw = LW,
1858    );
1859    let w = s.recurrence_width;
1860    let fl = s.recurrence_flushes;
1861    assert!(
1862        hd % (fl * w * FADD_LATENCY) == 0,
1863        "a {hd}-wide head's rows split over {fl} flushes, each a whole number of the flush's groups of {} rows",
1864        w * FADD_LATENCY
1865    );
1866    if let Some(window) = window {
1867        assert!(fl == 1, "a trailing flush replays its shares on one flush unit a lane, not {fl}");
1868        assert!(
1869            hd % window == 0 && (hd / window) % (w * FADD_LATENCY) == 0,
1870            "a trailing flush of {window} tokens replays a {hd}-wide head's rows in shares of a whole number of the read-out's groups of {} rows",
1871            w * FADD_LATENCY
1872        );
1873    }
1874    let text = replaced(
1875        text,
1876        &[
1877            (
1878                &format!("  logos_gdn_side #(.HD({hd}), .TMAX({t_max}), .SA({sa}), .W({w})) side("),
1879                &format!(
1880                    "{lanes}  logos_gdn_lanes #(.HD({hd}), .TMAX({t_max}), .SA({sa}), {credit}.W({w}), .L({l}), .LBA({lba}), .LOG_LINES({ll}){flushes}) side(",
1881                    ll = s.log_lines,
1882                    credit = if s.recurrence_credit != super::gdn::LANE_CREDIT { format!(".OUTSTANDING({}), ", s.recurrence_credit) } else { String::new() },
1883                    flushes = if fl > 1 { format!(", .FLUSHES({fl})") } else { String::new() }
1884                ),
1885            ),
1886            (
1887                "\n    .written(!writing));\n",
1888                &format!(
1889                    "\n    .written(!writing), .fault(lanes_fault), .l_rq_valid(l_rq_valid), .l_rq_ready(l_rq_ready),\n    \
1890                     .l_rq_line(l_rq_line), .l_rs_valid(l_rs_valid), .l_rs_data(l_rs_data), .l_wq_valid(l_wq_valid),\n    \
1891                     .l_wq_ready(l_wq_ready), .l_wq_line(l_wq_line), .l_wq_data(l_wq_data), .l_written(~l_writing),\n    \
1892                     .rows(gdn_job[{}:{}]), .row_valid(g_row), .row_ready(g_take));\n",
1893                    GdnJob::ROWS + 15,
1894                    GdnJob::ROWS
1895                ),
1896            ),
1897            (".fault(gdn_fault), .rq_valid(", ".fault(p_fault), .rq_valid("),
1898            (
1899                "  wire g_row, g_take, g_side_busy;\n",
1900                "  wire g_row, g_take;\n",
1901            ),
1902            (
1903                "  reg g_took;\n  always @(posedge clk) g_took <= !rst && g_take;\n  assign g_take = g_row && !g_side_busy && !g_took && !writing;\n  assign g_busy = g_side_busy || g_row;\n",
1904                "",
1905            ),
1906            (".start(g_take), .busy(g_side_busy),", ".start(g_start), .busy(g_busy),"),
1907            (
1908                ".md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data),\n    \
1909                 .mr_valid(mr_valid), .mr_ready(p_mr_ready), .mr_data(mr_data), .mw_valid(mw_valid), .mw_ready(mw_ready),\n    \
1910                 .mw_data(mw_data), .mdone_valid(mdone_valid), .mdone_ready(mdone_ready));\n",
1911                &format!(
1912                    ".md_valid(c_md_valid[3:0]), .md_ready(c_md_ready[3:0]),\n    \
1913                     .md_data(c_md_data[{m}:0]), .mr_valid(c_mr_valid[3:0]), .mr_ready(c_mr_ready[3:0]), .mr_data(mr_data),\n    \
1914                     .mw_valid(c_mw_valid[3:0]), .mw_ready(c_mw_ready[3:0]), .mw_data(c_mw_data[{d}:0]), .mdone_valid(c_mdone_valid[3:0]),\n    \
1915                     .mdone_ready(c_mdone_ready[3:0]));\n",
1916                    m = 4 * mdw - 1,
1917                    d = 4 * dw - 1
1918                ),
1919            ),
1920        ],
1921    );
1922    if !s.delta_norm {
1923        return text;
1924    }
1925    // The job's gated norm: each row's gate beat from the rows' slots, the norm's fields from the job word, to the lanes.
1926    let field = |at: usize, bits: usize| format!("gdn_job[{}:{at}]", at + bits - 1);
1927    replaced(
1928        &text,
1929        &[
1930            (
1931                &format!("  wire [{st}:0] g_q, g_out, g_k, g_v, g_ld, g_gt;\n", st = sa - 1),
1932                &format!("  wire [{st}:0] g_q, g_out, g_k, g_v, g_ld, g_gt, g_z;\n", st = sa - 1),
1933            ),
1934            (".ld_beat(g_ld), .gt_beat(g_gt));\n", &format!(".ld_beat(g_ld), .gt_beat(g_gt), .z_slot({}), .z_beat(g_z));\n", field(GdnJob::Z, 5))),
1935            (
1936                ".gt_beat(g_gt), .st_ra(g_ra),",
1937                &format!(
1938                    ".gt_beat(g_gt), .norm(gdn_job[{}]), .eps({}), .z_beat(g_z),\n    .w_line({}), .st_ra(g_ra),",
1939                    GdnJob::NORM,
1940                    field(GdnJob::EPS, 32),
1941                    field(GdnJob::W_LINE, 32)
1942                ),
1943            ),
1944        ],
1945    )
1946}
1947
1948/// `logos_engine_fast` with the recurrence unit ([`fast_recurrence`]'s `text`) and the vector unit's side beside the
1949/// streaming unit ([`Streaming::vectors`]): `logos_vector_side` started the cycle after the middle of the engine cycle
1950/// the core hands it a vector job in (`vec_go`), its tables written as the core's are, the store and the recurrence
1951/// unit's port its from then to the next matvec or recurrence job's start, and its busy held as the other units' are.
1952fn fast_vectors(text: &str, p: &EngineParams) -> String {
1953    let sa = store_address_bits(p);
1954    let unit = format!(
1955        r#"  // The vector unit's side: a vector job on the fast side's vector unit, started the cycle after the middle of the
1956  // engine cycle the core hands it over in (vec_go); the store and the recurrence unit's port its from then to the next
1957  // matvec or recurrence job's start; its tables written at the core's step, as the core's are.
1958  (* maxfan = 256 *) reg vec;
1959  always @(posedge clk)
1960    if (rst) vec <= 1'b0;
1961    else if (start || g_start) vec <= 1'b0;
1962    else if (v_start) vec <= 1'b1;
1963  wire v_busy, v_we, v_rq_valid, v_wq_valid;
1964  wire [{st}:0] v_ra, v_wa;
1965  wire [511:0] v_wd, v_wq_data;
1966  wire [63:0] v_wm;
1967  wire [31:0] v_rq_line, v_wq_line;
1968  logos_vector_side #(.ROW_MAX({rows}), .VROWS({vrows}), .PROWS({prows}), .FU({fu}), .JOB_WORDS({jobs}), .TENSORS({tensors}),
1969    .LAYERS({layers}), .ROPES({ropes}), .SA({sa}), .FM_ROWS({fm_rows}), .PASSES({passes}), .DEPTH({depth})) vside(.clk(clk), .rst(rst), .start(v_start), .job_pc(vec_job[{pc_top}:{pc}]),
1970    .rows(vec_job[{rows_top}:{rows_at}]), .state_f16(vec_job[{f16}]), .busy(v_busy), .port_fault(gdn_fault), .fault(vec_fault),
1971    .host_we(ce && host_wr), .host_table(host_table), .host_addr(host_addr), .host_wdata(host_wdata), .st_ra(v_ra), .st_rd(rd),
1972    .st_we(v_we), .st_wa(v_wa), .st_wd(v_wd), .st_wm(v_wm), .rq_valid(v_rq_valid), .rq_ready(vec && p_rq_ready),
1973    .rq_line(v_rq_line), .rs_valid(vec && p_rs_valid), .rs_data(rs_data), .wq_valid(v_wq_valid), .wq_ready(vec && p_wq_ready),
1974    .wq_line(v_wq_line), .wq_data(v_wq_data), .written(!writing));
1975  assign rq_ready = !vec && p_rq_ready;
1976  assign rs_valid = !vec && p_rs_valid;
1977  assign wq_ready = !vec && p_wq_ready;
1978"#,
1979        st = sa - 1,
1980        rows = p.rows,
1981        vrows = p.side_rows,
1982        prows = p.pair_rows.min(p.side_rows),
1983        fu = p.function_units,
1984        jobs = p.job_words,
1985        tensors = p.tensors.max(1),
1986        layers = p.layers.max(1),
1987        ropes = p.ropes.max(1),
1988        fm_rows = p.streaming.as_ref().map_or(super::vector_side::FM_ROWS, |s| s.float_rows),
1989        passes = p.streaming.as_ref().map_or(1, |s| s.vector_passes),
1990        depth = p.streaming.as_ref().map_or(1, |s| s.vector_depth),
1991        pc = VectorSideJob::PC,
1992        pc_top = VectorSideJob::PC + 31,
1993        rows_at = VectorSideJob::ROWS,
1994        rows_top = VectorSideJob::ROWS + 15,
1995        f16 = VectorSideJob::F16,
1996    );
1997    replaced(
1998        text,
1999        &[
2000            (
2001                "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit and the token-row store, the store the\n\
2002                 // units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and\n\
2003                 // written at its step (ce).\n",
2004                "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit, the vector unit's side and the token-row\n\
2005                 // store, the store the units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's\n\
2006                 // middle (cr) and written at its step (ce).\n",
2007            ),
2008            (
2009                "   input [31:0] map_space,",
2010                &format!(
2011                    "   input vec_go, input [{v}:0] vec_job, output vec_fault,\n   input [31:0] map_space,",
2012                    v = VectorSideJob::BITS - 1
2013                ),
2014            ),
2015            ("  wire g_start = gdn_go && cr;\n", "  wire g_start = gdn_go && cr;\n  wire v_start = vec_go && cr;\n"),
2016            ("    else if (start) gdn <= 1'b0;\n", "    else if (start || v_start) gdn <= 1'b0;\n"),
2017            ("  wire g_busy, g_we, rq_valid, rq_ready, rs_valid, wq_valid, wq_ready, writing;\n", "  wire g_busy, g_we, rq_valid, rq_ready, rs_valid, wq_valid, wq_ready, writing, p_rq_ready, p_rs_valid, p_wq_ready;\n"),
2018            (
2019                ".clear(g_start), .fault(gdn_fault), .rq_valid(rq_valid), .rq_ready(rq_ready),\n    \
2020                 .rq_line(rq_line), .rs_valid(rs_valid), .rs_data(rs_data), .wq_valid(wq_valid), .wq_ready(wq_ready), .wq_line(wq_line),\n    \
2021                 .wq_data(wq_data), .writing(writing),",
2022                ".clear(g_start || v_start), .fault(gdn_fault), .rq_valid(vec ? v_rq_valid : rq_valid),\n    \
2023                 .rq_ready(p_rq_ready), .rq_line(vec ? v_rq_line : rq_line), .rs_valid(p_rs_valid), .rs_data(rs_data),\n    \
2024                 .wq_valid(vec ? v_wq_valid : wq_valid), .wq_ready(p_wq_ready), .wq_line(vec ? v_wq_line : wq_line),\n    \
2025                 .wq_data(vec ? v_wq_data : wq_data), .writing(writing),",
2026            ),
2027            ("  assign md_valid = gdn ? p_md_valid : u_md_valid;\n", "  assign md_valid = (gdn || vec) ? p_md_valid : u_md_valid;\n"),
2028            ("  assign md_data = gdn ? p_md_data : u_md_data;\n", "  assign md_data = (gdn || vec) ? p_md_data : u_md_data;\n"),
2029            ("  assign mr_ready = gdn ? p_mr_ready : u_mr_ready;\n", "  assign mr_ready = (gdn || vec) ? p_mr_ready : u_mr_ready;\n"),
2030            ("  logos_fast_port #(", &format!("{unit}  logos_fast_port #(")),
2031            (
2032                "    else if (cr) busy_held <= busy || start || g_busy || writing || g_start;\n",
2033                "    else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start;\n",
2034            ),
2035            (
2036                ".re(own || cr), .ra(own ? (gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n    \
2037                 .we(own ? (gdn ? g_we : u_we) : ce && e_we), .wa(own ? (gdn ? g_wa : u_wa) : e_wa),\n    \
2038                 .wd(own ? (gdn ? g_wd : u_wd) : e_wd), .wm(own ? (gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2039                ".re(own || cr), .ra(own ? (vec ? v_ra : gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n    \
2040                 .we(own ? (vec ? v_we : gdn ? g_we : u_we) : ce && e_we), .wa(own ? (vec ? v_wa : gdn ? g_wa : u_wa) : e_wa),\n    \
2041                 .wd(own ? (vec ? v_wd : gdn ? g_wd : u_wd) : e_wd), .wm(own ? (vec ? v_wm : gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2042            ),
2043        ],
2044    )
2045}
2046
2047/// `logos_engine_fast` with the vector unit's side ([`fast_vectors`]'s text) and the attention side beside it
2048/// ([`Streaming::attention`]): `logos_attend_side` on the recurrence unit's port, started the cycle after the middle of
2049/// the engine cycle the core hands it a token-row in (`att_go`); the store and the port its from then to the next
2050/// matvec, recurrence or vector job's start; it only reads the card.
2051fn fast_attention(text: &str, p: &EngineParams) -> String {
2052    let sa = store_address_bits(p);
2053    let (hd, vd, ctx, _) = p.attend.expect("an attention side attends a model's attention blocks");
2054    let field = |at: usize, bits: usize| format!("att_job[{}:{at}]", at + bits - 1);
2055    let unit = format!(
2056        r#"  wire a_busy, a_we, a_rq_valid;
2057  wire [{st}:0] a_ra, a_wa;
2058  wire [511:0] a_wd;
2059  wire [31:0] a_rq_line;
2060  logos_attend_side #(.HD({hd}), .VD({vd}), .P_MAX({ctx}), .SA({sa})) aside(.clk(clk), .rst(rst), .start(a_start), .busy(a_busy),
2061    .region({region}), .keys({keys}), .values({values}), .kscales({kscales}), .vscales({vscales}), .kv_heads({kv}),
2062    .heads({heads}), .positions({positions}), .scale({scale}), .q_beat({q}), .out_beat({o}), .st_ra(a_ra), .st_rd(rd),
2063    .st_we(a_we), .st_wa(a_wa), .st_wd(a_wd), .rq_valid(a_rq_valid), .rq_ready(att && p_rq_ready), .rq_line(a_rq_line),
2064    .rs_valid(att && p_rs_valid), .rs_data(rs_data));
2065"#,
2066        st = sa - 1,
2067        region = field(AttendJob::REGION, 32),
2068        keys = field(AttendJob::KEYS, 32),
2069        values = field(AttendJob::VALUES, 32),
2070        kscales = field(AttendJob::KEY_SCALES, 32),
2071        vscales = field(AttendJob::VALUE_SCALES, 32),
2072        kv = field(AttendJob::KV_HEADS, 32),
2073        heads = field(AttendJob::HEADS, 16),
2074        positions = field(AttendJob::POSITIONS, 32),
2075        scale = field(AttendJob::SCALE, 32),
2076        q = field(AttendJob::QUERY, sa),
2077        o = field(AttendJob::OUTPUT, sa),
2078    );
2079    replaced(
2080        text,
2081        &[
2082            (
2083                "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit, the vector unit's side and the token-row\n\
2084                 // store, the store the units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's\n\
2085                 // middle (cr) and written at its step (ce).\n",
2086                "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit, the vector unit's side, the attention side\n\
2087                 // and the token-row store, the store the units' while they own it (own), the core's otherwise, read the cycle after\n\
2088                 // the engine cycle's middle (cr) and written at its step (ce).\n",
2089            ),
2090            ("   input [31:0] map_space,", &format!("   input att_go, input [{}:0] att_job,\n   input [31:0] map_space,", AttendJob::BITS - 1)),
2091            (
2092                "  wire v_start = vec_go && cr;\n",
2093                "  wire v_start = vec_go && cr;\n  wire a_start = att_go && cr;\n  \
2094                 // The attention side's: a token-row of an attention job, from the cycle after the middle of the engine cycle the\n  \
2095                 // core hands it over in (att_go) to the next unit's start.\n  \
2096                 (* maxfan = 256 *) reg att;\n  \
2097                 always @(posedge clk)\n    \
2098                 if (rst) att <= 1'b0;\n    \
2099                 else if (start || g_start || v_start) att <= 1'b0;\n    \
2100                 else if (a_start) att <= 1'b1;\n",
2101            ),
2102            ("    else if (start || v_start) gdn <= 1'b0;\n", "    else if (start || v_start || a_start) gdn <= 1'b0;\n"),
2103            ("    else if (start || g_start) vec <= 1'b0;\n", "    else if (start || g_start || a_start) vec <= 1'b0;\n"),
2104            ("  assign rq_ready = !vec && p_rq_ready;\n", "  assign rq_ready = !vec && !att && p_rq_ready;\n"),
2105            ("  assign rs_valid = !vec && p_rs_valid;\n", "  assign rs_valid = !vec && !att && p_rs_valid;\n"),
2106            ("  assign wq_ready = !vec && p_wq_ready;\n", "  assign wq_ready = !vec && !att && p_wq_ready;\n"),
2107            (
2108                ".clear(g_start || v_start), .fault(gdn_fault), .rq_valid(vec ? v_rq_valid : rq_valid),\n    \
2109                 .rq_ready(p_rq_ready), .rq_line(vec ? v_rq_line : rq_line), .rs_valid(p_rs_valid), .rs_data(rs_data),\n    \
2110                 .wq_valid(vec ? v_wq_valid : wq_valid),",
2111                ".clear(g_start || v_start || a_start), .fault(gdn_fault), .rq_valid(att ? a_rq_valid : vec ? v_rq_valid : rq_valid),\n    \
2112                 .rq_ready(p_rq_ready), .rq_line(att ? a_rq_line : vec ? v_rq_line : rq_line), .rs_valid(p_rs_valid), .rs_data(rs_data),\n    \
2113                 .wq_valid(!att && (vec ? v_wq_valid : wq_valid)),",
2114            ),
2115            ("  assign md_valid = (gdn || vec) ? p_md_valid : u_md_valid;\n", "  assign md_valid = (gdn || vec || att) ? p_md_valid : u_md_valid;\n"),
2116            ("  assign md_data = (gdn || vec) ? p_md_data : u_md_data;\n", "  assign md_data = (gdn || vec || att) ? p_md_data : u_md_data;\n"),
2117            ("  assign mr_ready = (gdn || vec) ? p_mr_ready : u_mr_ready;\n", "  assign mr_ready = (gdn || vec || att) ? p_mr_ready : u_mr_ready;\n"),
2118            ("  logos_fast_port #(", &format!("{unit}  logos_fast_port #(")),
2119            (
2120                "    else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start;\n",
2121                "    else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start || a_busy || a_start;\n",
2122            ),
2123            (
2124                ".re(own || cr), .ra(own ? (vec ? v_ra : gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n    \
2125                 .we(own ? (vec ? v_we : gdn ? g_we : u_we) : ce && e_we), .wa(own ? (vec ? v_wa : gdn ? g_wa : u_wa) : e_wa),\n    \
2126                 .wd(own ? (vec ? v_wd : gdn ? g_wd : u_wd) : e_wd), .wm(own ? (vec ? v_wm : gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2127                ".re(own || cr), .ra(own ? (att ? a_ra : vec ? v_ra : gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n    \
2128                 .we(own ? (att ? a_we : vec ? v_we : gdn ? g_we : u_we) : ce && e_we),\n    \
2129                 .wa(own ? (att ? a_wa : vec ? v_wa : gdn ? g_wa : u_wa) : e_wa),\n    \
2130                 .wd(own ? (att ? a_wd : vec ? v_wd : gdn ? g_wd : u_wd) : e_wd),\n    \
2131                 .wm(own ? (att ? {64{1'b1}} : vec ? v_wm : gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2132            ),
2133        ],
2134    )
2135}
2136
2137/// `logos_engine_fast` ([`fast_verilog`]'s `text`) with the recurrence unit beside the streaming unit
2138/// ([`Streaming::recurrence`]): `logos_gdn_side` on the channels through `logos_fast_port`, started the cycle after
2139/// the middle of the engine cycle the core gives it a token-row in (`gdn_go`), the store and the channels its from then
2140/// to the next matvec job's start; the unit's busy and the port's writes not yet reported written held as the matvec
2141/// unit's busy is, so the core takes the store and the channels back only once the row's state is on the card.
2142fn fast_recurrence(text: &str, p: &EngineParams, s: &Streaming) -> String {
2143    use super::stream::{AW, LW};
2144    let (hd, flush) = p.delta.expect("a recurrence unit runs a model's Gated-DeltaNet blocks");
2145    let (t_max, window) = recurrence_log(flush);
2146    assert!(window.is_none() || s.recurrence_lanes > 1, "a trailing flush runs on the recurrence's head lanes alone");
2147    let sa = store_address_bits(p);
2148    let (mdt, dw) = (4 * (AW + LW + 1) - 1, 8 * s.layout.line_bytes);
2149    let field = |at: usize, bits: usize| format!("gdn_job[{}:{at}]", at + bits - 1);
2150    let unit = format!(
2151        r#"  // The recurrence unit: a lazily flushed memory's token-rows on the channels through the fast port, the job started the
2152  // cycle after the middle of the engine cycle the core gives it the job in (gdn_go); the store and the channels the
2153  // unit's from then to the next matvec job's start. The job's rows given on in order (logos_gdn_rows), each laid from
2154  // the rows and slots tables, the side taking the next once it is idle and the last row's writes are on the card (the
2155  // next row may be the same sequence's); the unit busy while a row is left.
2156  wire g_start = gdn_go && cr;
2157  reg gdn;
2158  always @(posedge clk)
2159    if (rst) gdn <= 1'b0;
2160    else if (start) gdn <= 1'b0;
2161    else if (g_start) gdn <= 1'b1;
2162  wire g_busy, g_we, rq_valid, rq_ready, rs_valid, wq_valid, wq_ready, writing;
2163  wire [{st}:0] g_ra, g_wa;
2164  wire [511:0] g_wd;
2165  wire [{dt}:0] rs_data, wq_data;
2166  wire [31:0] rq_line, wq_line;
2167  wire [3:0] p_md_valid, p_mr_ready;
2168  wire [{mdt}:0] p_md_data;
2169  wire g_row, g_take, g_side_busy;
2170  wire [31:0] g_region;
2171  wire [{st}:0] g_q, g_out, g_k, g_v, g_ld, g_gt;
2172  logos_gdn_rows #(.SA({sa}), .ROW_MAX({row_max})) rows_of(.clk(clk), .rst(rst), .start(g_start), .rows({rows}),
2173    .q_slot({q_slot}), .out_slot({out_slot}), .k_slot({k_slot}), .v_slot({v_slot}), .v_at({v_at}), .ld_slot({ld_slot}),
2174    .gt_slot({gt_slot}), .host_we(ce && host_wr), .host_table(host_table), .host_addr(host_addr), .host_wdata(host_wdata),
2175    .row_valid(g_row), .row_ready(g_take), .region(g_region), .q_beat(g_q), .out_beat(g_out), .k_beat(g_k), .v_beat(g_v),
2176    .ld_beat(g_ld), .gt_beat(g_gt));
2177  reg g_took;
2178  always @(posedge clk) g_took <= !rst && g_take;
2179  assign g_take = g_row && !g_side_busy && !g_took && !writing;
2180  assign g_busy = g_side_busy || g_row;
2181  logos_gdn_side #(.HD({hd}), .TMAX({t_max}), .SA({sa}), .W({w})) side(.clk(clk), .rst(rst), .start(g_take), .busy(g_side_busy),
2182    .region(g_region), .mem_off({memory}), .log_off({log}), .entry({entry}), .nk({nk}), .nv({nv}), .fval({fval}),
2183    .fdec({fdec}), .fgate({fgate}), .scale({scale}), .tokens({tokens}), .f16(gdn_job[{f16}]), .q_beat(g_q),
2184    .out_beat(g_out), .k_beat(g_k), .v_beat(g_v), .ld_beat(g_ld), .gt_beat(g_gt), .st_ra(g_ra),
2185    .st_rd(rd), .st_we(g_we), .st_wa(g_wa), .st_wd(g_wd), .rq_valid(rq_valid), .rq_ready(rq_ready), .rq_line(rq_line),
2186    .rs_valid(rs_valid), .rs_data(rs_data), .wq_valid(wq_valid), .wq_ready(wq_ready), .wq_line(wq_line), .wq_data(wq_data),
2187    .written(!writing));
2188  logos_fast_port #(.NB(4), .AW({aw}), .LW({lw}), .DL({dw})) port(.clk(clk), .rst(rst), .space(map_space), .base(map_base),
2189    .granule_log2(map_granule_log2), .clear(g_start), .fault(gdn_fault), .rq_valid(rq_valid), .rq_ready(rq_ready),
2190    .rq_line(rq_line), .rs_valid(rs_valid), .rs_data(rs_data), .wq_valid(wq_valid), .wq_ready(wq_ready), .wq_line(wq_line),
2191    .wq_data(wq_data), .writing(writing), .md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data),
2192    .mr_valid(mr_valid), .mr_ready(p_mr_ready), .mr_data(mr_data), .mw_valid(mw_valid), .mw_ready(mw_ready),
2193    .mw_data(mw_data), .mdone_valid(mdone_valid), .mdone_ready(mdone_ready));
2194  assign md_valid = gdn ? p_md_valid : u_md_valid;
2195  assign md_data = gdn ? p_md_data : u_md_data;
2196  assign mr_ready = gdn ? p_mr_ready : u_mr_ready;
2197"#,
2198        st = sa - 1,
2199        dt = dw - 1,
2200        w = s.recurrence_width,
2201        aw = AW,
2202        lw = LW,
2203        rows = field(GdnJob::ROWS, 16),
2204        row_max = p.rows,
2205        memory = field(GdnJob::MEMORY, 32),
2206        log = field(GdnJob::LOG, 32),
2207        entry = field(GdnJob::ENTRY, 32),
2208        nk = field(GdnJob::KEY_HEADS, 32),
2209        nv = field(GdnJob::VALUE_HEADS, 32),
2210        fval = field(GdnJob::VALUES, 32),
2211        fdec = field(GdnJob::DECAYS, 32),
2212        fgate = field(GdnJob::GATES, 32),
2213        scale = field(GdnJob::SCALE, 32),
2214        q_slot = field(GdnJob::QUERY, 5),
2215        out_slot = field(GdnJob::OUTPUT, 5),
2216        k_slot = field(GdnJob::KEY, 5),
2217        v_slot = field(GdnJob::VALUE, 5),
2218        v_at = field(GdnJob::VALUE_AT, 20),
2219        ld_slot = field(GdnJob::LOG_DECAY, 5),
2220        gt_slot = field(GdnJob::GATE, 5),
2221        tokens = field(GdnJob::TOKENS, 8),
2222        f16 = GdnJob::F16,
2223    );
2224    replaced(
2225        text,
2226        &[
2227            (
2228                "// A hybrid engine's user-clock side: the streaming unit and the token-row store, the store the unit's while it owns it\n\
2229                 // (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and written at its step (ce).\n",
2230                "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit and the token-row store, the store the\n\
2231                 // units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and\n\
2232                 // written at its step (ce).\n",
2233            ),
2234            (
2235                &format!("   input [3:0] mr_valid, output [3:0] mr_ready, input [{}:0] mr_data);\n", 4 * dw - 1),
2236                &format!(
2237                    "   input [3:0] mr_valid, output [3:0] mr_ready, input [{d}:0] mr_data,\n   \
2238                     output [3:0] mw_valid, input [3:0] mw_ready, output [{d}:0] mw_data, input [3:0] mdone_valid, output [3:0] mdone_ready,\n   \
2239                     input gdn_go, input [{g}:0] gdn_job, output gdn_fault,\n   \
2240                     input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,\n   \
2241                     input [31:0] map_space, input [{a}:0] map_base, input [4:0] map_granule_log2);\n",
2242                    d = 4 * dw - 1,
2243                    g = GdnJob::bits(s) - 1,
2244                    a = AW - 1
2245                ),
2246            ),
2247            (
2248                "  wire busy, u_we;\n",
2249                &format!("  wire busy, u_we;\n  wire [3:0] u_md_valid, u_mr_ready;\n  wire [{mdt}:0] u_md_data;\n"),
2250            ),
2251            (
2252                ".md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),\n    .mr_ready(mr_ready), .mr_data(mr_data));\n",
2253                &format!(
2254                    ".md_valid(u_md_valid), .md_ready(md_ready), .md_data(u_md_data), .mr_valid(mr_valid),\n    \
2255                     .mr_ready(u_mr_ready), .mr_data(mr_data));\n{unit}"
2256                ),
2257            ),
2258            ("    else if (cr) busy_held <= busy || start;\n", "    else if (cr) busy_held <= busy || start || g_busy || writing || g_start;\n"),
2259            (
2260                ".re(own || cr), .ra(own ? u_ra : e_ra), .rd(rd),\n    .we(own ? u_we : ce && e_we), .wa(own ? u_wa : e_wa), .wd(own ? u_wd : e_wd), .wm(own ? u_wm : e_wm));\n",
2261                ".re(own || cr), .ra(own ? (gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n    \
2262                 .we(own ? (gdn ? g_we : u_we) : ce && e_we), .wa(own ? (gdn ? g_wa : u_wa) : e_wa),\n    \
2263                 .wd(own ? (gdn ? g_wd : u_wd) : e_wd), .wm(own ? (gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2264            ),
2265        ],
2266    )
2267}
2268
2269/// `logos_engine_fast` ([`fast_verilog`]'s `text`, every unit on the fast side) for an engine of
2270/// [`Streaming::contexts`] contexts: each context's core hands its jobs over as one context's does, and each class of
2271/// units serves one context at a time — the matvec class (the streaming unit) and the row class (the recurrence, vector
2272/// and attention sides, which share the fast port) — so one context's weights stream while another's rows run. A core's
2273/// hand-off at `cr` is its context's request of the job's class; the class grants a requesting context, the one after
2274/// the context it served last, at a `cr` it is idle on and no longer held: a class holds the context it serves until
2275/// that context's core takes its store bank back (`own`), which it does only once its busy has fallen. Each context's
2276/// busy holds while its request waits and while the class serving it is busy. The store is a bank a context, each
2277/// laid by the same layout over that context's token-rows: the matvec unit's while it serves the bank's context, the row
2278/// class's likewise, the context's core's otherwise. The streaming unit and the row class's ports share the channels a
2279/// descriptor at a time (`logos_channel_mux`, the row class port 0), the unit through a `logos_read_credit` a channel:
2280/// its channels' feeds step together, so it takes a channel's lines only as its other channels let it, and asking only
2281/// for lines a credit can hold it takes each line as it comes, as the multiplexer's order needs; the per-row units' mode
2282/// is set and cleared only by their own starts; the vector side reads the rows table of the context it serves; the row class's faults are held
2283/// for the context they arose in.
2284fn fast_contexts(text: &str, p: &EngineParams, s: &Streaming) -> String {
2285    // A streaming unit whose cores are paired runs their processing elements in clk2, twice the fast side's clock.
2286    let clk2_in = if s.set.pe_pairs { " input clk2," } else { "" };
2287    use super::stream::{AW, LW};
2288    let c = s.contexts;
2289    let cb = if c > 1 { (usize::BITS - (c - 1).leading_zeros()) as usize } else { 1 };
2290    let sa = store_address_bits(p);
2291    let (j, g, v, a) = (HybridJob::BITS, GdnJob::bits(s), VectorSideJob::BITS, AttendJob::BITS);
2292    let (mdw, dw) = (AW as usize + LW as usize + 1, 8 * s.layout.line_bytes as usize);
2293    // A context's job word among every context's: a choice among constant slices, where a slice at a stride of the
2294    // word's width would be a multiply and a barrel shift.
2295    let pick = |words: &str, width: usize, at: &str| {
2296        (0..c - 1).rev().fold(format!("{words}[{} +: {width}]", width * (c - 1)), |rest, k| format!("({at} == {cb}'d{k}) ? {words}[{} +: {width}] : {rest}", width * k))
2297    };
2298    let head = text.find("module logos_engine_fast\n").expect("the fast side's module");
2299    let body_at = head + text[head..].find(");\n").expect("the fast side's ports") + 3;
2300    let body = replaced(
2301        &text[body_at..],
2302        &[
2303            ("  wire start = go && cr;\n", "@CONTEXT_WIRES@"),
2304            ("  wire g_start = gdn_go && cr;\n", ""),
2305            ("  wire v_start = vec_go && cr;\n", ""),
2306            ("  wire a_start = att_go && cr;\n", ""),
2307            ("    else if (start || g_start || v_start) att <= 1'b0;\n", "    else if (g_start || v_start) att <= 1'b0;\n"),
2308            ("    else if (start || v_start || a_start) gdn <= 1'b0;\n", "    else if (v_start || a_start) gdn <= 1'b0;\n"),
2309            ("    else if (start || g_start || a_start) vec <= 1'b0;\n", "    else if (g_start || a_start) vec <= 1'b0;\n"),
2310            ("  assign md_valid = (gdn || vec || att) ? p_md_valid : u_md_valid;\n", ""),
2311            ("  assign md_data = (gdn || vec || att) ? p_md_data : u_md_data;\n", ""),
2312            ("  assign mr_ready = (gdn || vec || att) ? p_mr_ready : u_mr_ready;\n", ""),
2313            (
2314                "  always @(posedge clk)\n    if (rst) busy_held <= 1'b0;\n    \
2315                 else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start || a_busy || a_start;\n",
2316                "",
2317            ),
2318        ],
2319    );
2320    let body = spliced(&body, "  logos_bram_re_w512 #(.D(", "endmodule\n", "@CONTEXT_GRANTS@endmodule\n");
2321    let mut body = body;
2322    for name in ["md_valid", "md_ready", "md_data", "mr_valid", "mr_ready", "mr_data", "mw_valid", "mw_ready", "mw_data", "mdone_valid", "mdone_ready"] {
2323        body = renamed(&body, name, &format!("x_{name}"));
2324    }
2325    for (from, to) in [("job", "mjob"), ("gdn_job", "gjob"), ("vec_job", "vjob"), ("att_job", "ajob"), ("gdn_fault", "g_fault"), ("vec_fault", "v_fault"), ("rd", "w_rd")] {
2326        body = renamed(&body, from, to);
2327    }
2328    let unit = body.find(" unit(.clk(clk)").expect("the streaming unit's instance");
2329    let unit_end = unit + body[unit..].find(");\n").expect("its end");
2330    let instance = replaced(
2331        &body[unit..unit_end],
2332        &[
2333            (".md_ready(x_md_ready)", ".md_ready(u_md_ready)"),
2334            (".mr_valid(x_mr_valid)", ".mr_valid(u_mr_valid)"),
2335            (".mr_data(x_mr_data)", ".mr_data(u_mr_data)"),
2336            (".st_rd(w_rd)", ".st_rd(u_rd)"),
2337        ],
2338    );
2339    let body = format!("{}{instance}{}", &body[..unit], &body[unit_end..]);
2340    let body = replaced(
2341        &body,
2342        &[
2343            ("  logos_vector_side #(", &format!("  logos_vector_side #(.CTX({c}), .CB({cb}), ")),
2344            (".port_fault(g_fault), .fault(v_fault),", ".port_fault(g_fault), .fault(v_fault), .ctx(rw_at),"),
2345            ("  logos_gdn_rows #(", &format!("  logos_gdn_rows #(.CTX({c}), .CB({cb}), ")),
2346            (".rst(rst), .start(g_start), .rows(", ".rst(rst), .ctx(rw_at), .start(g_start), .rows("),
2347        ],
2348    );
2349    let wires = format!(
2350        r#"  // The units' starts and the jobs they take, from the contexts' grants below.
2351  wire start, g_start, v_start, a_start;
2352  wire [{jt}:0] mjob;
2353  wire [{gt}:0] gjob;
2354  wire [{vt}:0] vjob;
2355  wire [{at}:0] ajob;
2356  wire [511:0] u_rd, w_rd;
2357  wire [{cbt}:0] rw_at;
2358  wire g_fault, v_fault;
2359  wire [3:0] u_md_ready, u_mr_valid, x_md_ready, x_mr_valid, x_mw_valid, x_mw_ready, x_mdone_valid, x_mdone_ready;
2360  wire [{dt}:0] x_mr_data, x_mw_data, u_mr_data;
2361  wire [3:0] k_md_valid, k_md_ready, k_mr_valid, k_mr_ready, pw_ready_unused, pdone_valid_unused;
2362  wire [4*({aw}+{lw}+1)-1:0] k_md_data;
2363"#,
2364        jt = j - 1,
2365        gt = g - 1,
2366        vt = v - 1,
2367        at = a - 1,
2368        cbt = cb - 1,
2369        dt = 4 * dw - 1,
2370        aw = AW,
2371        lw = LW,
2372    );
2373    // The channels a descriptor at a time, every port straight onto one multiplexer: with head lanes their multiplexer,
2374    // the streaming unit's credits its last port; else the row class's port 0 and the credits' port 1.
2375    let lanes_mux = "  logos_channel_mux #(.P(";
2376    let lanes = body.find(lanes_mux).map(|at| (at, at + body[at..].find(");\n").expect("the lanes' multiplexer's end") + 3));
2377    let (body, channels) = match lanes {
2378        Some((at, end)) => {
2379            let ports: usize = body[at + lanes_mux.len()..].split(')').next().and_then(|n| n.parse().ok()).expect("the lanes' multiplexer's ports");
2380            let flat = format!(
2381                "  logos_channel_mux #(.P({}), .NB(4), .AW({AW}), .LW({LW}), .DL({dw})) mux(.clk(clk), .rst(rst),\n    \
2382                 .pd_valid({{k_md_valid, c_md_valid}}), .pd_ready({{k_md_ready, c_md_ready}}), .pd_data({{k_md_data, c_md_data}}),\n    \
2383                 .pr_valid({{k_mr_valid, c_mr_valid}}), .pr_ready({{k_mr_ready, c_mr_ready}}), .pw_valid({{4'd0, c_mw_valid}}),\n    \
2384                 .pw_ready({{pw_ready_unused, c_mw_ready}}), .pw_data({{{}'d0, c_mw_data}}), .pdone_valid({{pdone_valid_unused, c_mdone_valid}}),\n    \
2385                 .pdone_ready({{4'hf, c_mdone_ready}}), .md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),\n    \
2386                 .mr_ready(mr_ready), .mw_valid(mw_valid), .mw_ready(mw_ready), .mw_data(mw_data), .mdone_valid(mdone_valid),\n    \
2387                 .mdone_ready(mdone_ready));\n",
2388                ports + 1,
2389                4 * dw
2390            );
2391            (format!("{}{flat}{}", &body[..at], &body[end..]), String::new())
2392        }
2393        None => (
2394            body,
2395            format!(
2396                "  logos_channel_mux #(.P(2), .NB(4), .AW({AW}), .LW({LW}), .DL({dw})) cmux(.clk(clk), .rst(rst),\n    \
2397                 .pd_valid({{k_md_valid, p_md_valid}}), .pd_ready({{k_md_ready, x_md_ready}}), .pd_data({{k_md_data, p_md_data}}),\n    \
2398                 .pr_valid({{k_mr_valid, x_mr_valid}}), .pr_ready({{k_mr_ready, p_mr_ready}}), .pw_valid({{4'd0, x_mw_valid}}),\n    \
2399                 .pw_ready({{pw_ready_unused, x_mw_ready}}), .pw_data({{{}'d0, x_mw_data}}), .pdone_valid({{pdone_valid_unused, x_mdone_valid}}),\n    \
2400                 .pdone_ready({{4'hf, x_mdone_ready}}), .md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),\n    \
2401                 .mr_ready(mr_ready), .mw_valid(mw_valid), .mw_ready(mw_ready), .mw_data(mw_data), .mdone_valid(mdone_valid),\n    \
2402                 .mdone_ready(mdone_ready));\n",
2403                4 * dw
2404            ),
2405        ),
2406    };
2407    let grants = format!(
2408        r#"  // The contexts' grants: a class grants the first requesting context after the one it served last.
2409  function [{cb}:0] next_of(input [{ct}:0] want, input [{cbt}:0] last);
2410    integer i, k;
2411    reg found;
2412    reg [{cbt}:0] at;
2413    begin
2414      found = 1'b0;
2415      at = last;
2416      for (i = 1; i <= {c}; i = i + 1) begin
2417        k = (last + i) % {c};
2418        if (!found && want[k]) begin found = 1'b1; at = k[{cbt}:0]; end
2419      end
2420      next_of = {{found, at}};
2421    end
2422  endfunction
2423  // The matvec class.
2424  reg [{ct}:0] mv_pend;
2425  reg [{cbt}:0] mv_ctx;
2426  reg mv_valid;
2427  wire [{ct}:0] mv_ask = go & {{{c}{{cr}}}};
2428  wire [{cb}:0] mv_next = next_of(mv_pend | mv_ask, mv_ctx);
2429  wire mv_grant = cr && mv_next[{cb}] && !busy && !(mv_valid && own[mv_ctx]);
2430  wire [{cbt}:0] mv_at = mv_grant ? mv_next[{cbt}:0] : mv_ctx;
2431  assign start = mv_grant;
2432  assign mjob = {mjob_sel};
2433  always @(posedge clk)
2434    if (rst) begin mv_pend <= {{{c}{{1'b0}}}}; mv_ctx <= {{{cb}{{1'b0}}}}; mv_valid <= 1'b0; end
2435    else begin
2436      mv_pend <= (mv_pend | mv_ask) & ~({{{{{cm}{{1'b0}}}}, mv_grant}} << mv_next[{cbt}:0]);
2437      if (mv_grant) begin mv_ctx <= mv_next[{cbt}:0]; mv_valid <= 1'b1; end
2438      else if (mv_valid && !own[mv_ctx]) mv_valid <= 1'b0;
2439    end
2440  // The row class: a request's kind, 0 a recurrence row, 1 a vector job, 2 an attention row.
2441  reg [{ct}:0] rw_pend;
2442  reg [{kt}:0] rw_kinds;
2443  (* maxfan = 256 *) reg [{cbt}:0] rw_ctx;
2444  reg rw_valid;
2445  reg [1:0] rw_kind;
2446  wire [{ct}:0] rw_ask = (gdn_go | vec_go | att_go) & {{{c}{{cr}}}};
2447  wire [{cb}:0] rw_next = next_of(rw_pend | rw_ask, rw_ctx);
2448  wire [{cbt}:0] rw_to = rw_next[{cbt}:0];
2449  wire rw_busy = g_busy || writing || v_busy || a_busy;
2450  wire rw_grant = cr && rw_next[{cb}] && !rw_busy && !(rw_valid && own[rw_ctx]);
2451  wire [1:0] rw_new = gdn_go[rw_to] ? 2'd0 : vec_go[rw_to] ? 2'd1 : 2'd2;
2452  wire [1:0] rw_k = rw_ask[rw_to] ? rw_new : rw_kinds[2*rw_to +: 2];
2453  assign g_start = rw_grant && rw_k == 2'd0;
2454  assign v_start = rw_grant && rw_k == 2'd1;
2455  assign a_start = rw_grant && rw_k == 2'd2;
2456  assign rw_at = rw_grant ? rw_to : rw_ctx;
2457  assign gjob = {gjob_sel};
2458  assign vjob = {vjob_sel};
2459  assign ajob = {ajob_sel};
2460  integer rk;
2461  always @(posedge clk)
2462    if (rst) begin rw_pend <= {{{c}{{1'b0}}}}; rw_kinds <= {{{k2}{{1'b0}}}}; rw_ctx <= {{{cb}{{1'b0}}}}; rw_valid <= 1'b0; rw_kind <= 2'd0; end
2463    else begin
2464      for (rk = 0; rk < {c}; rk = rk + 1)
2465        if (rw_ask[rk]) rw_kinds[2*rk +: 2] <= gdn_go[rk] ? 2'd0 : vec_go[rk] ? 2'd1 : 2'd2;
2466      rw_pend <= (rw_pend | rw_ask) & ~({{{{{cm}{{1'b0}}}}, rw_grant}} << rw_to);
2467      if (rw_grant) begin rw_ctx <= rw_to; rw_valid <= 1'b1; rw_kind <= rw_k; end
2468      else if (rw_valid && !own[rw_ctx]) rw_valid <= 1'b0;
2469    end
2470  // Each context's busy: its request waiting, or the class serving it busy.
2471  integer bk;
2472  always @(posedge clk)
2473    if (rst) busy_held <= {{{c}{{1'b0}}}};
2474    else if (cr)
2475      for (bk = 0; bk < {c}; bk = bk + 1)
2476        busy_held[bk] <= mv_pend[bk] || mv_ask[bk] || rw_pend[bk] || rw_ask[bk] || (mv_valid && mv_ctx == bk[{cbt}:0] && busy)
2477          || (rw_valid && rw_ctx == bk[{cbt}:0] && rw_busy);
2478  // The row class's faults, held for the context each arose in.
2479  reg [{ct}:0] gf, vf;
2480  always @(posedge clk)
2481    if (rst) begin gf <= {{{c}{{1'b0}}}}; vf <= {{{c}{{1'b0}}}}; end
2482    else if (rw_valid) begin
2483      if (rw_kind == 2'd0) gf[rw_ctx] <= g_fault;
2484      if (rw_kind == 2'd1) vf[rw_ctx] <= v_fault;
2485    end
2486  assign gdn_fault = gf;
2487  assign vec_fault = vf;
2488  // The store, a bank a context: the matvec unit's while it serves the bank's context, the row class's likewise, the
2489  // context's core's while the core holds it.
2490  wire [{st}:0] w_ra = att ? a_ra : vec ? v_ra : g_ra;
2491  wire w_we = att ? a_we : vec ? v_we : g_we;
2492  wire [{st}:0] w_wa = att ? a_wa : vec ? v_wa : g_wa;
2493  wire [511:0] w_wd = att ? a_wd : vec ? v_wd : g_wd;
2494  wire [63:0] w_wm = att ? {{64{{1'b1}}}} : vec ? v_wm : {{64{{1'b1}}}};
2495  wire [{bt}:0] banked;
2496  genvar bank_at;
2497  generate for (bank_at = 0; bank_at < {c}; bank_at = bank_at + 1) begin : bank
2498    wire mv_here = own[bank_at] && (mv_grant ? mv_next[{cbt}:0] == bank_at : mv_valid && mv_ctx == bank_at);
2499    wire rw_here = own[bank_at] && (rw_grant ? rw_to == bank_at : rw_valid && rw_ctx == bank_at);
2500    logos_bram_re_w512 #(.D({store}), .A({sa})) store(.clk(clk), .re(own[bank_at] || cr),
2501      .ra(own[bank_at] ? (mv_here ? u_ra : w_ra) : e_ra[{sa}*bank_at +: {sa}]), .rd(banked[512*bank_at +: 512]),
2502      .we(own[bank_at] ? (mv_here ? u_we : rw_here && w_we) : ce && e_we[bank_at]),
2503      .wa(own[bank_at] ? (mv_here ? u_wa : w_wa) : e_wa[{sa}*bank_at +: {sa}]),
2504      .wd(own[bank_at] ? (mv_here ? u_wd : w_wd) : e_wd[512*bank_at +: 512]),
2505      .wm(own[bank_at] ? (mv_here ? u_wm : w_wm) : e_wm[64*bank_at +: 64]));
2506  end endgenerate
2507  assign rd = banked;
2508  assign u_rd = banked[512*mv_ctx +: 512];
2509  assign w_rd = banked[512*rw_ctx +: 512];
2510  // The streaming unit's reads through a credit a channel: its runs asked only as the credit's buffer can hold them,
2511  // every line taken from the multiplexer as it comes, the unit's next line a register; each line on to the unit
2512  // through a pipe, so the unit's take and the credit's pop each end at a register.
2513  genvar kc;
2514  generate for (kc = 0; kc < 4; kc = kc + 1) begin : credit
2515    wire c_valid, c_ready;
2516    wire [{dw}-1:0] c_data;
2517    logos_read_credit #(.AW({aw}), .LW({lw}), .DL({dw})) k(.clk(clk), .rst(rst), .in_valid(u_md_valid[kc]),
2518      .in_ready(u_md_ready[kc]), .in_data(u_md_data[({aw}+{lw}+1)*kc +: {aw}+{lw}+1]), .out_valid(c_valid),
2519      .out_ready(c_ready), .out_data(c_data), .md_valid(k_md_valid[kc]),
2520      .md_ready(k_md_ready[kc]), .md_data(k_md_data[({aw}+{lw}+1)*kc +: {aw}+{lw}+1]), .mr_valid(k_mr_valid[kc]),
2521      .mr_ready(k_mr_ready[kc]), .mr_data(x_mr_data[{dw}*kc +: {dw}]));
2522    logos_pipe #(.W({dw})) to_unit(.clk(clk), .rst_n(!rst), .in_valid(c_valid), .in_ready(c_ready), .in_data(c_data),
2523      .out_valid(u_mr_valid[kc]), .out_ready(u_mr_ready[kc]), .out_data(u_mr_data[{dw}*kc +: {dw}]));
2524  end endgenerate
2525{channels}  assign x_mr_data = mr_data;
2526"#,
2527        mjob_sel = pick("job", j, "mv_at"),
2528        gjob_sel = pick("gdn_job", g, "rw_at"),
2529        vjob_sel = pick("vec_job", v, "rw_at"),
2530        ajob_sel = pick("att_job", a, "rw_at"),
2531        c = c,
2532        cb = cb,
2533        ct = c - 1,
2534        cbt = cb - 1,
2535        cm = c - 1,
2536        kt = 2 * c - 1,
2537        k2 = 2 * c,
2538        st = sa - 1,
2539        sa = sa,
2540        bt = 512 * c - 1,
2541        store = p.store_beats,
2542        aw = AW,
2543        lw = LW,
2544        dw = dw,
2545    );
2546    let header = format!(
2547        r#"// A hybrid engine's user-clock side for {c} contexts: the streaming unit, the recurrence unit, the vector unit's side, the
2548// attention side and the token-row store a bank a context, the matvec class serving one context while the row class
2549// serves another (each a context's from its grant until the context's core takes its bank back), the core's bank its own
2550// otherwise, read the cycle after the engine cycle's middle (cr) and written at its step (ce).
2551module logos_engine_fast
2552  (input clk,{clk2_in} input rst, input ce, input cr, input [{ct}:0] own, input [{ct}:0] go, input [{cj}:0] job,
2553   input [{cs}:0] e_ra, input [{ct}:0] e_we, input [{cs}:0] e_wa, input [{cd}:0] e_wd, input [{cm}:0] e_wm,
2554   output [{cd}:0] rd, output reg [{ct}:0] busy_held,
2555   output [3:0] md_valid, input [3:0] md_ready, output [{mdt}:0] md_data,
2556   input [3:0] mr_valid, output [3:0] mr_ready, input [{drt}:0] mr_data,
2557   output [3:0] mw_valid, input [3:0] mw_ready, output [{drt}:0] mw_data, input [3:0] mdone_valid, output [3:0] mdone_ready,
2558   input [{ct}:0] gdn_go, input [{cg}:0] gdn_job, output [{ct}:0] gdn_fault,
2559   input [{ct}:0] vec_go, input [{cv}:0] vec_job, output [{ct}:0] vec_fault,
2560   input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,
2561   input [{ct}:0] att_go, input [{ca}:0] att_job,
2562   input [31:0] map_space, input [{awt}:0] map_base, input [4:0] map_granule_log2);
2563"#,
2564        c = c,
2565        ct = c - 1,
2566        cj = c * j - 1,
2567        cs = c * sa - 1,
2568        cd = 512 * c - 1,
2569        cm = 64 * c - 1,
2570        mdt = 4 * mdw - 1,
2571        drt = 4 * dw - 1,
2572        cg = c * g - 1,
2573        cv = c * v - 1,
2574        ca = c * a - 1,
2575        awt = AW - 1,
2576    );
2577    let body = replaced(&body, &[("@CONTEXT_WIRES@", wires.as_str()), ("@CONTEXT_GRANTS@", grants.as_str())]);
2578    format!("{}{header}{body}", &text[..text[..head].rfind("// A hybrid engine's user-clock side").expect("the fast side's comment")])
2579}
2580
2581/// `logos_vector_side` for an engine of contexts ([`fast_contexts`]): a rows table a context, the context a host write
2582/// names in its address's top four bits, and the job's rows read from the table of the context it runs for (`ctx`).
2583fn vector_side_contexts(text: &str) -> String {
2584    // A convolution path's rows from the table of the context the job runs for too.
2585    let text = text.replace("  wire [127:0] cv_row_e = rowt[cv_row_k];\n", "  wire [127:0] cv_row_e = rowt[ctx * ROW_MAX + cv_row_k];\n");
2586    replaced(
2587        &text,
2588        &[
2589            ("module logos_vector_side #(parameter ROW_MAX = 1,", "module logos_vector_side #(parameter CTX = 1, CB = 1, ROW_MAX = 1,"),
2590            ("   input start, input [31:0] job_pc,", "   input [CB-1:0] ctx, input start, input [31:0] job_pc,"),
2591            ("  `LOGOS_RAMSTYLE_ASYNC(ROW_MAX) reg [127:0] rowt [0:ROW_MAX-1];\n", "  `LOGOS_RAMSTYLE_ASYNC(CTX*ROW_MAX) reg [127:0] rowt [0:CTX*ROW_MAX-1];\n"),
2592            ("      3'd1: rowt[host_addr] <= host_wdata[127:0];\n", "      3'd1: rowt[host_addr[31:28] * ROW_MAX + host_addr[27:0]] <= host_wdata[127:0];\n"),
2593            ("          row_e <= rowt[k]; slot_e <= slott[l_what[4:0]];", "          row_e <= rowt[ctx * ROW_MAX + k]; slot_e <= slott[l_what[4:0]];"),
2594            ("          row_e <= rowt[k]; slot_e <= slott[p_what[4:0]];", "          row_e <= rowt[ctx * ROW_MAX + k]; slot_e <= slott[p_what[4:0]];"),
2595            ("  always @(posedge clk) row_region <= rowt[k][31:0];\n", "  always @(posedge clk) row_region <= rowt[ctx * ROW_MAX + k][31:0];\n"),
2596        ],
2597    )
2598}
2599
2600/// The vector unit's side `text` ([`super::vector_side::vector_side_verilog`]'s) with a convolution job's path of its own
2601/// ([`Streaming::with_conv_path`]): a job whose descriptors are the machine's conv job's (three ring loads, oldest first,
2602/// x from a slot, the kernel's four taps from one tensor at `j·C`, the output into a slot and x into the ring, the conv
2603/// micro-program word for word; its channels a multiple of 16, of 32 for a binary16 ring, no more than `beats` beats)
2604/// reads its kernel's and its layer's table words, then hands every row to `logos_conv_path` (`lanes` lanes), which
2605/// takes the side's store ports, its memory requests and its line queue while it runs, and the job ends as any does
2606/// once every write is on the card. Every other job runs on the vector unit, of [`CONV_PATH_VREGS`] registers: a job
2607/// whose loads, stores or instructions name a register past them faults at its first pass and runs no further.
2608fn vector_side_conv(text: &str, lanes: usize, beats: usize) -> String {
2609    use super::fp32::F32_TO_F16_LATENCY;
2610    use super::vector::RNE_P_LATENCY;
2611    let narrow = F32_TO_F16_LATENCY.max(RNE_P_LATENCY);
2612    let prog: Vec<u64> = super::super::vector::conv(&[0, 1, 2], 3, &[4, 5, 6, 7], 8, 9).iter().map(super::simd::encode).collect();
2613    assert_eq!(prog.len(), 6, "the conv micro-program's six instructions");
2614    let prog_ok: String = prog.iter().enumerate().map(|(i, w)| format!(" && prog[{i}] == 64'h{w:016x}")).collect();
2615    let text = renamed(&renamed(text, "rq_valid", "rq_valid_s"), "rq_line", "rq_line_s");
2616    replaced(
2617        &text,
2618        &[
2619            (
2620                "   output reg rq_valid_s, input rq_ready, output reg [31:0] rq_line_s, input rs_valid,",
2621                "   output rq_valid, input rq_ready, output [31:0] rq_line, input rs_valid,",
2622            ),
2623            (
2624                "  localparam JA = (JOB_WORDS > 1) ? $clog2(JOB_WORDS) : 1,",
2625                "  // The side's own memory requests, the path's while it runs.\n  reg rq_valid_s;\n  reg [31:0] rq_line_s;\n  localparam JA = (JOB_WORDS > 1) ? $clog2(JOB_WORDS) : 1,",
2626            ),
2627            ("FM_T = 6'd39, GNEXT = 6'd40;", "FM_T = 6'd39, GNEXT = 6'd40, CV_T = 6'd41, CV_GO = 6'd42, CV_RUN = 6'd43;"),
2628            (
2629                "  always @* begin\n    j_ra = pc[JA-1:0];",
2630                &format!(
2631                    r#"  // A convolution job (`cv`): the machine's conv job, its descriptors' shape (cv_shape), every row on logos_conv_path,
2632  // which drives the store's ports, the memory requests and the line queue while the side waits in CV_RUN. The shape is
2633  // a register set every clock: the descriptors are written only in DESC, the last of them the clock before DESC
2634  // chooses PASS, so it holds the job's own shape from PASS's first clock on.
2635  wire [31:0] cv_c = ld_a[0][63:32];
2636  reg cv_shape;
2637  always @(posedge clk) cv_shape <= !heads_first && heads == 32'd1 && nload == 8'd8 && nput == 8'd2 && nprog == 16'd6 &&
2638    ld_a[0][7:0] == 8'h30 && ld_a[1][7:0] == 8'h31 && ld_a[2][7:0] == 8'h32 &&
2639    ld_a[0][31:24] == 8'd0 && ld_a[1][31:24] == 8'd1 && ld_a[2][31:24] == 8'd2 &&
2640    ld_a[1][23:8] == ld_a[0][23:8] && ld_a[2][23:8] == ld_a[0][23:8] && ld_a[3][7:0] == 8'h03 && ld_b[3][31:0] == 32'd0 &&
2641    ld_a[4][7:0] == 8'h14 && ld_a[5][7:0] == 8'h15 && ld_a[6][7:0] == 8'h16 && ld_a[7][7:0] == 8'h17 &&
2642    ld_a[5][23:8] == ld_a[4][23:8] && ld_a[6][23:8] == ld_a[4][23:8] && ld_a[7][23:8] == ld_a[4][23:8] &&
2643    ld_b[4][31:0] == 32'd0 && ld_b[5][31:0] == cv_c && ld_b[6][31:0] == cv_c + cv_c && ld_b[7][31:0] == cv_c + cv_c + cv_c &&
2644    ld_a[1][63:32] == cv_c && ld_a[2][63:32] == cv_c && ld_a[3][63:32] == cv_c && ld_a[4][63:32] == cv_c &&
2645    ld_a[5][63:32] == cv_c && ld_a[6][63:32] == cv_c && ld_a[7][63:32] == cv_c &&
2646    pt_a[0][7:0] == 8'h09 && pt_b[0][31:0] == 32'd0 && pt_a[1][7:0] == 8'h23 && pt_a[1][23:8] == ld_a[0][23:8] &&
2647    pt_a[0][63:32] == cv_c && pt_a[1][63:32] == cv_c{prog_ok} &&
2648    cv_c[3:0] == 4'd0 && (!f16 || !cv_c[4]) && (cv_c >> 4) <= 32'd{beats};
2649  reg cv_start;
2650  // Whether a load, a store or an instruction of the job names a register past the vector unit's: each descriptor word
2651  // read (wd_q) and whether it is a load's or store's register word (wd_reg) or an instruction (wd_prog), checked the
2652  // clock after.
2653  reg wide, wd_reg, wd_prog;
2654  reg [63:0] wd_q;
2655  wire cv_on = state == CV_RUN;
2656  wire cv_busy, cv_st_we, cv_rq_valid, cv_q_v, cv_flush;
2657  wire [15:0] cv_row_k;
2658  wire [SA-1:0] cv_st_ra, cv_st_wa;
2659  wire [511:0] cv_st_wd, cv_q_data;
2660  wire [63:0] cv_q_mask;
2661  wire [31:0] cv_rq_line, cv_q_line;
2662  wire [127:0] cv_row_e = rowt[cv_row_k];
2663  reg [31:0] cv_kline, cv_beats, cv_xb, cv_xs, cv_ob, cv_os, cv_roff, cv_rbytes;
2664  reg [7:0] cv_taps;
2665  logos_conv_path #(.SA(SA), .CVB({beats}), .LANES({lanes}), .SRL(SRL)) cvp(.clk(clk), .rst(rst), .start(cv_start), .rows(nrows),
2666    .f16(f16), .k_line(cv_kline), .beats(cv_beats), .x_base(cv_xb), .x_stride(cv_xs), .o_base(cv_ob), .o_stride(cv_os),
2667    .ring_off(cv_roff), .ring_taps(cv_taps), .row_bytes(cv_rbytes), .row_k(cv_row_k), .row_e(cv_row_e), .settled(settled),
2668    .busy(cv_busy), .st_ra(cv_st_ra), .st_rd(st_rd), .st_we(cv_st_we), .st_wa(cv_st_wa), .st_wd(cv_st_wd),
2669    .rq_valid(cv_rq_valid), .rq_ready(rq_ready), .rq_line(cv_rq_line), .rs_valid(rs_valid), .rs_data(rs_data), .q_v(cv_q_v),
2670    .q_data(cv_q_data), .q_mask(cv_q_mask), .q_line(cv_q_line), .q_n(q_n), .flush(cv_flush));
2671  assign rq_valid = cv_on ? cv_rq_valid : rq_valid_s;
2672  assign rq_line = cv_on ? cv_rq_line : rq_line_s;
2673
2674  always @* begin
2675    j_ra = pc[JA-1:0];"#
2676                ),
2677            ),
2678            (
2679                "    if (state == FM_ACT) st_ra = fm_from[SA-1:0] + fm_ar[SA-1:0] * fm_from[SA-1+32:32] + fm_at[SA-1:0];\n",
2680                "    if (state == FM_ACT) st_ra = fm_from[SA-1:0] + fm_ar[SA-1:0] * fm_from[SA-1+32:32] + fm_at[SA-1:0];\n    if (cv_on) st_ra = cv_st_ra;\n",
2681            ),
2682            (
2683                "    s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; st_we <= 1'b0; cb_flush <= 1'b0;\n",
2684                &format!(
2685                    "    s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; st_we <= 1'b0; cb_flush <= 1'b0; cv_start <= 1'b0;\n    \
2686                     if (cv_on && cv_st_we) begin st_we <= 1'b1; st_wa <= cv_st_wa; st_wd <= cv_st_wd; st_wm <= {{64{{1'b1}}}}; end\n    \
2687                     if (cv_flush) cb_flush <= 1'b1;\n    \
2688                     wd_q <= jw; wd_reg <= 1'b0; wd_prog <= 1'b0;\n    \
2689                     if ((wd_reg && wd_q[3:0] >= 4'd{v}) || (wd_prog && (wd_q[11:8] >= 4'd{v} || wd_q[15:12] >= 4'd{v} || wd_q[19:16] >= 4'd{v} || wd_q[23:20] >= 4'd{v}))) wide <= 1'b1;\n",
2690                    v = CONV_PATH_VREGS
2691                ),
2692            ),
2693            (
2694                "          else if (fm_shape) begin",
2695                "          else if (cv_shape) begin t_ra <= ld_a[4][8+TA-1:8]; b_ra <= ld_a[0][8+BA-1:8]; state <= CV_T; end\n          else if (fm_shape) begin",
2696            ),
2697            (
2698                "        // A load: its fields and tables;",
2699                "        // A convolution: its kernel tensor's and its layer's table words read, then every row on the path.\n        \
2700                 CV_T: state <= CV_GO;\n        CV_GO: begin\n          \
2701                 cv_kline <= tw[31:0] + (ld_b[4][31:0] >> 4); cv_beats <= cv_c >> 4;\n          \
2702                 cv_xb <= slott[ld_a[3][12:8]][31:0] + (ld_b[3][31:0] >> 4); cv_xs <= slott[ld_a[3][12:8]][63:32];\n          \
2703                 cv_ob <= slott[pt_a[0][12:8]][31:0] + (pt_b[0][31:0] >> 4); cv_os <= slott[pt_a[0][12:8]][63:32];\n          \
2704                 cv_roff <= bw[31:0]; cv_taps <= bw[71:64]; cv_rbytes <= f16 ? bw[63:32] << 1 : bw[63:32] << 2;\n          \
2705                 cv_start <= 1'b1; state <= CV_RUN;\n        end\n        \
2706                 CV_RUN: if (!cv_start && !cv_busy) state <= DRAIN;\n        // A load: its fields and tables;",
2707            ),
2708            (
2709                &format!("      if (d_v[{narrow}]) begin q_data[q_wr] <= o_data; q_mask[q_wr] <= o_wmask; q_line[q_wr] <= o_byte[37:6]; q_wr <= q_wr + 4'd1; end\n"),
2710                &format!(
2711                    "      if (d_v[{narrow}]) begin q_data[q_wr] <= o_data; q_mask[q_wr] <= o_wmask; q_line[q_wr] <= o_byte[37:6]; q_wr <= q_wr + 4'd1; end\n      \
2712                     if (cv_q_v) begin q_data[q_wr] <= cv_q_data; q_mask[q_wr] <= cv_q_mask; q_line[q_wr] <= cv_q_line; q_wr <= q_wr + 4'd1; end\n"
2713                ),
2714            ),
2715            (
2716                &format!("      q_n <= q_n + {{4'd0, d_v[{narrow}]}} - {{4'd0, cb_pop}};\n"),
2717                &format!("      q_n <= q_n + {{4'd0, d_v[{narrow}]}} + {{4'd0, cv_q_v}} - {{4'd0, cb_pop}};\n"),
2718            ),
2719            (
2720                ", .VREGS(16), .SREGS(8), .FU(FU), .PAIRS(PROWS), .PASSES(PASSES)) vu(",
2721                &format!(", .VREGS({CONV_PATH_VREGS}), .SREGS(8), .FU(FU), .PAIRS(PROWS), .PASSES(PASSES)) vu("),
2722            ),
2723            (
2724                "ring_open <= 1'b0;\n          state <= HDR0;\n",
2725                "ring_open <= 1'b0; wide <= 1'b0;\n          state <= HDR0;\n",
2726            ),
2727            (
2728                "              if (dt[0] == 1'b0) ld_a[dt >> 1] <= jw; else ld_b[dt >> 1] <= jw;\n",
2729                "              if (dt[0] == 1'b0) begin ld_a[dt >> 1] <= jw; wd_reg <= 1'b1; end\n              else ld_b[dt >> 1] <= jw;\n",
2730            ),
2731            (
2732                "              if (dt[0] == 1'b0) pt_a[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw; else pt_b[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw;\n",
2733                "              if (dt[0] == 1'b0) begin pt_a[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw; wd_reg <= 1'b1; end\n              \
2734                 else pt_b[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw;\n",
2735            ),
2736            (
2737                "            end else prog[dt - {7'd0, nload, 1'b0} - {7'd0, nput, 1'b0}] <= jw;\n",
2738                "            end else begin\n              prog[dt - {7'd0, nload, 1'b0} - {7'd0, nput, 1'b0}] <= jw;\n              wd_prog <= 1'b1;\n            end\n",
2739            ),
2740            (
2741                "          else begin li <= 8'd0; gi <= 8'd0; goff <= 16'd0; gk0 <= k; gh0 <= h; state <= LOAD; end\n",
2742                "          else if (wide) begin fault <= 1'b1; state <= DRAIN; end\n          else begin li <= 8'd0; gi <= 8'd0; goff <= 16'd0; gk0 <= k; gh0 <= h; state <= LOAD; end\n",
2743            ),
2744        ],
2745    )
2746}
2747
2748/// `logos_gdn_rows` for an engine of contexts ([`fast_contexts`]): a rows table a context, the context a host write names
2749/// in its address's top four bits, and the job's rows read from the table of the context it runs for (`ctx`).
2750fn gdn_rows_contexts(text: &str) -> String {
2751    replaced(
2752        text,
2753        &[
2754            ("module logos_gdn_rows #(parameter SA = 10, ROW_MAX = 1)", "module logos_gdn_rows #(parameter SA = 10, ROW_MAX = 1, CTX = 1, CB = 1)"),
2755            ("  (input clk, input rst, input start, input [15:0] rows,", "  (input clk, input rst, input [CB-1:0] ctx, input start, input [15:0] rows,"),
2756            ("  `LOGOS_RAMSTYLE_ASYNC(ROW_MAX) reg [31:0] rowt [0:ROW_MAX-1];\n", "  localparam TB = $clog2(CTX * ROW_MAX);\n  `LOGOS_RAMSTYLE_ASYNC(CTX*ROW_MAX) reg [31:0] rowt [0:CTX*ROW_MAX-1];\n"),
2757            ("      3'd1: rowt[host_addr[RB-1:0]] <= host_wdata[31:0];\n", "      3'd1: rowt[host_addr[31:28] * ROW_MAX + host_addr[27:0]] <= host_wdata[31:0];\n"),
2758            ("  reg [RB-1:0] r_at;\n", "  reg [TB-1:0] r_at;\n"),
2759            ("      r_at <= {RB{1'b0}};\n", "      r_at <= ctx * ROW_MAX;\n"),
2760        ],
2761    )
2762}
2763
2764/// `text` with every use of the identifier `from` in its code — never a port's name in a connection, `.from(`, nor a
2765/// word of a comment — renamed `to`.
2766fn renamed(text: &str, from: &str, to: &str) -> String {
2767    let word = |c: u8| c.is_ascii_alphanumeric() || c == b'_' || c == b'$';
2768    let mut out = String::with_capacity(text.len());
2769    for line in text.split_inclusive('\n') {
2770        let (code, comment) = line.split_at(line.find("//").unwrap_or(line.len()));
2771        let bytes = code.as_bytes();
2772        let mut at = 0;
2773        while let Some(k) = code[at..].find(from) {
2774            let start = at + k;
2775            let end = start + from.len();
2776            let lone = (start == 0 || !(word(bytes[start - 1]) || bytes[start - 1] == b'.')) && bytes.get(end).is_none_or(|&c| !word(c));
2777            out.push_str(&code[at..start]);
2778            out.push_str(if lone { to } else { from });
2779            at = end;
2780        }
2781        out.push_str(&code[at..]);
2782        out.push_str(comment);
2783    }
2784    out
2785}
2786
2787/// `text` with every `(from, to)` of `edits` made, each `from` there exactly once.
2788fn replaced(text: &str, edits: &[(&str, &str)]) -> String {
2789    let mut out = text.to_string();
2790    for (from, to) in edits {
2791        assert_eq!(out.matches(from).count(), 1, "`{from}`");
2792        out = out.replace(from, to);
2793    }
2794    out
2795}
2796
2797/// `text` with what runs from `from` up to `to` — each there exactly once, `from` first — replaced by `with`.
2798fn spliced(text: &str, from: &str, to: &str, with: &str) -> String {
2799    assert_eq!(text.matches(from).count(), 1, "`{from}`");
2800    assert_eq!(text.matches(to).count(), 1, "`{to}`");
2801    let (a, b) = (text.find(from).unwrap(), text.find(to).unwrap());
2802    assert!(a < b, "`{from}` before `{to}`");
2803    format!("{}{with}{}", &text[..a], &text[b..])
2804}
2805
2806/// Fast clock cycles an engine clock cycle must span at least: the crossing takes the shell's values two cycles after
2807/// the engine clock's rising edge, the core reads its memories the cycle after the falling edge, and the crossing holds
2808/// what the engine gives the shell from the cycle after that, all before the next rising edge.
2809pub const ENGINE_MIN_DIVIDE: u32 = 6;
2810
2811/// Word `word` of each channel's tile run in the job's runs, `bits` of it, channel 3's highest.
2812fn runs_field(word: usize, bits: u32) -> String {
2813    let parts: Vec<String> = (0..4).rev().map(|c| format!("runs[{}:{}]", 256 * c + 32 * word + bits as usize - 1, 256 * c + 32 * word)).collect();
2814    format!("{{{}}}", parts.join(", "))
2815}
2816
2817const ENGINE_VERILOG: &str = r#"// The engine: the sequencer running a step's jobs over the token-row store and the card's memory.
2818module logos_engine #(parameter ROW_MAX = @ROWS@, VROWS = @VROWS@, STORE_BEATS = @STORE@, JOB_WORDS = @JOBS@,
2819                        TENSORS = @TENSORS@, LAYERS = @LAYERS@, ROPES = @ROPES@, MAXL = 16, MAXP = 16, MAXI = 64)
2820  (input clk, input rst,
2821   input start, input [31:0] job_at, input [31:0] job_end, input [15:0] rows, input state_f16, input cache_int8, input two_words, input per_block,
2822   output busy, output reg fault, output reg [31:0] pc,
2823   input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,
2824   input [31:0] host_raddr, output [511:0] host_rdata,
2825   output mem_rd, output [31:0] mem_rd_line, input mem_rd_valid, input [511:0] mem_rd_data,
2826   output mem_wr, output [31:0] mem_wr_line, output [511:0] mem_wr_data, output [63:0] mem_wr_mask, input mem_wr_ready@PORTS@);
2827
2828  `LOGOS_RAMSTYLE_ASYNC(ROW_MAX) reg [127:0] rowt   [0:ROW_MAX-1];
2829  `LOGOS_RAMSTYLE_ASYNC(32) reg [63:0]  slott  [0:31];
2830  reg [63:0]  ropet  [0:ROPES-1];
2831  // The job words, the tensor and block tables and the token-row store in block RAM, each read where its address is
2832  // set (`rtl::ram`): the job word at the program counter; a tensor's entry, a load's or the job's; a block's entry, a
2833  // load's, a store's or the job's layer's; the store's beat the state reads, or the host's while the engine is idle.
2834  localparam JA = (JOB_WORDS > 1) ? $clog2(JOB_WORDS) : 1, TA = (TENSORS > 1) ? $clog2(TENSORS) : 1,
2835             BA = (LAYERS > 1) ? $clog2(LAYERS) : 1, SA = (STORE_BEATS > 1) ? $clog2(STORE_BEATS) : 1;
2836  wire [63:0] jw;
2837  wire [127:0] tw;
2838  wire [511:0] bw, st_rd;
2839  reg [31:0] t_ra, b_ra, st_ra;
2840  reg st_we;
2841  reg [31:0] st_wa;
2842  reg [511:0] st_wd;
2843  reg [63:0] st_wm;
2844  reg [6:0] state;
2845  logos_ram_w64 #(.D(JOB_WORDS), .A(JA)) jobs_ram(.clk(clk), .ra(pc[JA-1:0]), .rd(jw),
2846    .we(host_wr && host_table == 3'd0), .wa(host_addr[JA-1:0]), .wd(host_wdata[63:0]), .wm(8'hff));
2847  logos_ram_w128 #(.D(TENSORS), .A(TA)) tens_ram(.clk(clk), .ra(t_ra[TA-1:0]), .rd(tw),
2848    .we(host_wr && host_table == 3'd3), .wa(host_addr[TA-1:0]), .wd(host_wdata[127:0]), .wm(16'hffff));
2849  logos_ram_w512 #(.D(LAYERS), .A(BA)) block_ram(.clk(clk), .ra(b_ra[BA-1:0]), .rd(bw),
2850    .we(host_wr && host_table == 3'd5), .wa(host_addr[BA-1:0]), .wd(host_wdata), .wm({64{1'b1}}));
2851@STORE_RAM@  assign host_rdata = st_rd;
2852
2853  // The card's memory: writes queue in block RAM and leave as the card takes them (mem_wr_ready); a read goes out only
2854  // once every write before it has left, so no read passes a write. A write the full queue cannot take faults the step.
2855  localparam [5:0] WQ = 6'd32;
2856  reg rq_v, wq_v;
2857  reg [31:0] rq_line, wq_line;
2858  reg [511:0] wq_data;
2859  reg [63:0] wq_mask;
2860  reg [5:0] wf_count;
2861  reg [4:0] wf_head, wf_tail;
2862  wire [607:0] wf_word;
2863  logos_ram_w608 #(.D(WQ), .A(5)) write_queue(.clk(clk), .ra(wf_head), .rd(wf_word), .we(wq_v), .wa(wf_tail),
2864    .wd({wq_mask, wq_line, wq_data}), .wm({76{1'b1}}));
2865  assign mem_wr = wf_count != 6'd0;
2866  assign {mem_wr_mask, mem_wr_line, mem_wr_data} = wf_word;
2867  wire wf_pop = mem_wr && mem_wr_ready;
2868  // Room for a state's write: the queue less the write in flight and a spare.
2869  wire wq_room = ({26'd0, wf_count} + {31'd0, wq_v}) < WQ - 2;
2870  reg rd_pend;
2871  reg [31:0] rd_pend_line;
2872  wire wq_empty = (wf_count == 6'd0) && !wq_v;
2873  assign mem_rd = (rq_v || rd_pend) && wq_empty;
2874  assign mem_rd_line = rd_pend ? rd_pend_line : rq_line;
2875  always @(posedge clk) begin
2876    if (rst) begin
2877      wf_count <= 6'd0; wf_head <= 5'd0; wf_tail <= 5'd0; rd_pend <= 1'b0;
2878    end else begin
2879      if (wq_v) wf_tail <= wf_tail + 5'd1;
2880      if (wf_pop) wf_head <= wf_head + 5'd1;
2881      wf_count <= wf_count + {5'd0, wq_v} - {5'd0, wf_pop};
2882      if (rq_v && !wq_empty) begin rd_pend <= 1'b1; rd_pend_line <= rq_line; end
2883      else if (mem_rd) rd_pend <= 1'b0;
2884    end
2885  end
2886
2887  // The vector unit and its ports.
2888  reg s_issue, s_wr, s_len_wr;
2889  reg [63:0] s_instr;
2890  reg [3:0] s_wr_reg, s_rd_reg, s_rd_s;
2891  reg [15:0] s_wr_row, s_rd_row;
2892  reg [511:0] s_wr_data;
2893  reg [19:0] s_len_val;
2894  wire s_busy;
2895  wire [511:0] s_rd_data;
2896  wire [31:0] s_rd_s_val;
2897  logos_simd #(.ROWS(VROWS), .VREGS(16), .SREGS(8), .FU(@FU@)) vu(.clk(clk), .rst(rst), .issue(s_issue), .instr(s_instr), .busy(s_busy),
2898    .wr(s_wr), .wr_reg(s_wr_reg), .wr_row(s_wr_row), .wr_data(s_wr_data), .len_wr(s_len_wr), .len_val(s_len_val),
2899    .rd_reg(s_rd_reg), .rd_row(s_rd_row), .rd_data(s_rd_data), .rd_s(s_rd_s), .rd_s_val(s_rd_s_val));
2900
2901  localparam IDLE = 7'd0, HDR = 7'd1, HEADS = 7'd2, FRESH = 7'd3, DESC = 7'd5, PASS = 7'd6, LOAD = 7'd7,
2902             LOAD_WAIT = 7'd8, LOAD_LEN = 7'd9, RUN = 7'd10, RUN_GAP = 7'd11, RUN_WAIT = 7'd12, PUT = 7'd13, NEXT = 7'd14,
2903@MATVEC_STATES@             DL_HDR = 7'd30, DL_ROW = 7'd31, DL_KQ_K = 7'd32, DL_KQ_Q = 7'd33, DL_KQ_L0 = 7'd34, DL_KQ_L1 = 7'd35, DL_KQ_DOT = 7'd36,
2904             DL_KQ_GAP = 7'd37, DL_KQ_WAIT = 7'd38, DL_E_RD = 7'd39, DL_E_RW = 7'd40, DL_E_OUT = 7'd41, DL_L_OUT = 7'd42,
2905             DL_CNT = 7'd43, DL_CNT_W = 7'd44, DL_APP = 7'd45, DL_FQ = 7'd46, DL_FZ = 7'd47, DL_FL0 = 7'd48, DL_FL1 = 7'd49,
2906             DL_FK = 7'd50, DL_FKW = 7'd51, DL_FV = 7'd52, DL_FVW = 7'd53, DL_FDEC = 7'd54, DL_FDECW = 7'd55, DL_FDEC_GAP = 7'd56,
2907             DL_FDEC_WAIT = 7'd57, DL_FEXP_GAP = 7'd58, DL_FEXP_WAIT = 7'd59, DL_FBETA = 7'd60, DL_FBETAW = 7'd61,
2908             DL_FB_GAP = 7'd62, DL_FB_WAIT = 7'd63, DL_FL3 = 7'd64, DL_FRUN = 7'd65, DL_FR_GAP = 7'd66, DL_FR_WAIT = 7'd67,
2909             DL_FLOG = 7'd68, DL_FY = 7'd69, DL_FC = 7'd70, DL_L_RD = 7'd71, DL_L_RW = 7'd72, DL_CNT_WR = 7'd73,
2910             AT_HDR = 7'd80, AT_ROW = 7'd81, AT_HEAD = 7'd82, AT_Q = 7'd83, AT_Q_GAP = 7'd84, AT_RUN = 7'd85, AT_SCALE = 7'd86,
2911             AT_SCALE_W = 7'd87, AT_FETCH = 7'd88, AT_FETCH_W = 7'd89, AT_KS = 7'd90, AT_GIVE = 7'd91, AT_TAKEN = 7'd92,
2912             DL_KQ_BETA = 7'd93;
2913  // A job done, the sequencer reads the next one's header, until the step's last word. Busy until then and until every
2914  // write the step made is on the card (the card port takes a write once its channel has written it): the shell takes
2915  // its channels back once the engine is idle, so a step that ends in writes leaves none of them behind.
2916  wire [6:0] next_job = (pc < job_end) ? HDR : IDLE;
2917  assign busy = state != IDLE || !wq_empty;
2918
2919  // The job's header and descriptors.
2920  reg [63:0] hdr;
2921  wire [3:0] unit = hdr[3:0];
2922  wire heads_first = hdr[4];
2923  wire [7:0] nfresh = hdr[20:13], nload = hdr[28:21], nput = hdr[36:29];
2924  wire [15:0] nprog = hdr[52:37];
2925  reg [31:0] heads;
2926  `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_a [0:MAXL-1];
2927  `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_b [0:MAXL-1];
2928  `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_a [0:MAXP-1];
2929  `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_b [0:MAXP-1];
2930  reg [63:0] prog [0:MAXI-1];
2931  reg [15:0] dc;
2932
2933
2934  // The pass: its row and head, the load, store or instruction it is at, and the beat.
2935  reg [15:0] k;
2936  reg [31:0] h;
2937  reg [7:0] li, pi;
2938  reg [15:0] ii;
2939  reg [31:0] lb, pb;
2940
2941  // The row's table entry.
2942  wire [127:0] rw = rowt[k];
2943  wire [31:0] region = rw[31:0], position = rw[63:32];
2944  wire [7:0] ring = rw[71:64];
2945
2946  // The load at hand.
2947  wire [63:0] la = ld_a[li], lbw = ld_b[li];
2948  wire [3:0] l_reg = la[3:0], l_kind = la[7:4];
2949  wire [15:0] l_what = la[23:8];
2950  wire [7:0] l_extra = la[31:24];
2951  wire [31:0] l_len = la[63:32], l_at = lbw[31:0], l_stride = lbw[63:32];
2952  wire [31:0] l_elem = l_at + h * l_stride;
2953  wire [31:0] l_beats = (l_len + 32'd15) >> 4;
2954  wire [63:0] l_slot = slott[l_what[4:0]];
2955  wire [31:0] l_store_beat = l_slot[31:0] + {16'd0, k} * l_slot[63:32] + (l_elem >> 4) + lb;
2956  wire [63:0] l_rope = ropet[l_what];
2957  wire [511:0] l_block = bw;
2958  // A ring row's index: the row's ring index plus the age, less the ring's rows once it passes them.
2959  wire [31:0] l_ring_sum = {24'd0, ring} + {24'd0, l_extra};
2960  wire [31:0] l_ring_idx = (l_ring_sum >= l_block[95:64]) ? l_ring_sum - l_block[95:64] : l_ring_sum;
2961  wire [31:0] st_bytes = state_f16 ? 32'd2 : 32'd4;
2962  wire [63:0] l_ring_byte = {32'd0, region} * 64 + l_block[31:0] + l_ring_idx * l_block[63:32] * st_bytes + lb * (state_f16 ? 32 : 64);
2963  reg [31:0] l_line;
2964  reg l_half;
2965  always @* begin
2966    l_half = 1'b0;
2967    case (l_kind)
2968      4'd1: l_line = tw[31:0] + (l_elem >> 4) + lb;
2969      4'd2: l_line = l_rope[31:0] + (((position * 2 + {31'd0, l_extra[0]}) * l_rope[63:32]) >> 4) + lb;
2970      default: begin l_line = l_ring_byte[37:6]; l_half = l_ring_byte[5]; end
2971    endcase
2972  end
2973  wire l_from_f16 = (l_kind == 4'd3) && state_f16;
2974  wire [511:0] widened;
2975  genvar g;
2976  generate
2977    for (g = 0; g < 16; g = g + 1) begin : widen
2978      logos_f16_to_f32 wv(.h(l_half ? mem_rd_data[256 + 16*g +: 16] : mem_rd_data[16*g +: 16]), .x(widened[32*g +: 32]));
2979    end
2980  endgenerate
2981
2982  // The store at hand.
2983  wire [63:0] pa = pt_a[pi], pbw = pt_b[pi];
2984  wire [3:0] p_reg = pa[3:0];
2985  wire p_scalar = pa[4];
2986  wire [2:0] p_kind = pa[7:5];
2987  wire [15:0] p_what = pa[23:8];
2988  wire p_values = pa[24];
2989  wire [1:0] p_part = pa[26:25];
2990  wire [31:0] p_len = pa[63:32], p_at = pbw[31:0], p_stride = pbw[63:32];
2991  wire [31:0] p_elem = p_at + h * p_stride;
2992  wire [31:0] p_beats = p_scalar ? 32'd1 : (p_len + 32'd15) >> 4;
2993  wire [31:0] p_lanes = p_scalar ? 32'd1 : ((p_len - (pb << 4)) >= 32'd16 ? 32'd16 : p_len - (pb << 4));
2994  wire [63:0] p_slot = slott[p_what[4:0]];
2995  wire [31:0] p_store_beat = p_slot[31:0] + {16'd0, k} * p_slot[63:32] + (p_elem >> 4) + pb;
2996  wire [511:0] p_block = bw;
2997  // The value stored: a vector register's beat, or a scalar register in lane 0.
2998  wire [511:0] p_data = p_scalar ? {480'd0, s_rd_s_val} : s_rd_data;
2999  wire [255:0] halves;
3000  wire [127:0] codes;
3001  generate
3002    for (g = 0; g < 16; g = g + 1) begin : narrow
3003      logos_f32_to_f16 nh(.x(p_data[32*g +: 32]), .h(halves[16*g +: 16]));
3004      wire signed [31:0] c;
3005      logos_rne nc(.x(p_data[32*g +: 32]), .n(c));
3006      assign codes[8*g +: 8] = c[7:0];
3007    end
3008  endgenerate
3009  // A cached row's first byte: its position's row of its head, keys or values.
3010  wire [31:0] p_cache_off = p_values ? p_block[63:32] : p_block[31:0];
3011  wire [31:0] p_cache_w = p_values ? p_block[223:192] : p_block[191:160];
3012  wire [31:0] p_scale_off = p_values ? p_block[127:96] : p_block[95:64];
3013  wire [31:0] p_cached = position * p_block[159:128] + h;
3014  reg [63:0] p_byte;
3015  always @* begin
3016    case (p_kind)
3017      3'd1: p_byte = {32'd0, region} * 64 + p_block[31:0] + {24'd0, ring} * p_block[63:32] * st_bytes + pb * (state_f16 ? 32 : 64);
3018      default: case (p_part)
3019        2'd0: p_byte = {32'd0, region} * 64 + p_cache_off + p_cached * p_cache_w * 2 + pb * 32;
3020        2'd1: p_byte = {32'd0, region} * 64 + p_cache_off + p_cached * p_cache_w + pb * 16;
3021        default: p_byte = {32'd0, region} * 64 + p_scale_off + p_cached * 2;
3022      endcase
3023    endcase
3024  end
3025  // Lane masks: the lanes below `p_lanes`.
3026  wire [15:0] lane_mask = (p_lanes >= 32'd16) ? 16'hffff : ((16'd1 << p_lanes[4:0]) - 16'd1);
3027  wire [5:0] p_off = p_byte[5:0];
3028
3029@MATVEC_DECLS@
3030@DELTA_DECLS@
3031@ATTEND_DECLS@
3032@QUANTIZER_FEEDS@
3033  integer i, wi;
3034  always @(posedge clk) begin
3035    s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; rq_v <= 1'b0; wq_v <= 1'b0;@MATVEC_PULSES@
3036    @DELTA_PULSES@
3037    // The host writes the tables and the store between steps.
3038    if (host_wr) case (host_table)
3039      3'd1: rowt[host_addr] <= host_wdata[127:0];
3040      3'd2: slott[host_addr] <= host_wdata[63:0];
3041      3'd4: ropet[host_addr] <= host_wdata[63:0];
3042      default: ;
3043    endcase
3044    if (rst) begin
3045      state <= IDLE; fault <= 1'b0;
3046    end else if (wq_v && wf_count == WQ && !wf_pop) begin
3047      fault <= 1'b1; state <= IDLE;
3048    end else case (state)
3049      IDLE: if (start) begin pc <= job_at; fault <= 1'b0; state <= HDR; end
3050      HDR: begin
3051        hdr <= jw; pc <= pc + 32'd1;
3052        if (jw[3:0] == 4'd2) state <= HEADS;
3053        else if (jw[3:0] == 4'd1) state <= MV_HDR;
3054        else if (jw[3:0] == 4'd@DELTA_UNIT@) state <= DL_HDR;
3055        else if (jw[3:0] == 4'd@ATTEND_UNIT@) state <= AT_HDR;
3056        // The step's advance moves the host's positions; the card's rows keep theirs to the step's end.
3057        else if (jw[3:0] == 4'd5) state <= (pc + 32'd1 < job_end) ? HDR : IDLE;
3058        else begin fault <= 1'b1; state <= IDLE; end
3059      end
3060@MATVEC_CASES@@DELTA_CASES@
3061@ATTEND_CASES@
3062      HEADS: begin heads <= jw[31:0]; pc <= pc + 32'd1; state <= FRESH; end
3063      // A fresh slot's width is the vector the job writes; the lanes past it are no vector's, so it needs no clearing.
3064      FRESH: begin pc <= pc + {24'd0, nfresh}; dc <= 16'd0; state <= DESC; end
3065      DESC: begin
3066        // The loads' and stores' words, two each, then the instructions, a word a cycle.
3067        if (dc == 2 * nload + 2 * nput + nprog) begin k <= 16'd0; h <= 32'd0; state <= PASS; end
3068        else begin
3069          if (dc < 2 * nload) begin
3070            if (dc[0] == 1'b0) ld_a[dc >> 1] <= jw; else ld_b[dc >> 1] <= jw;
3071          end else if (dc < 2 * nload + 2 * nput) begin
3072            if (dc[0] == 1'b0) pt_a[(dc - 2 * nload) >> 1] <= jw; else pt_b[(dc - 2 * nload) >> 1] <= jw;
3073          end else prog[dc - 2 * nload - 2 * nput] <= jw;
3074          pc <= pc + 32'd1; dc <= dc + 16'd1;
3075        end
3076      end
3077      PASS: begin
3078        if ((heads_first && h == heads) || (!heads_first && k == rows) || heads == 32'd0 || rows == 16'd0) state <= next_job;
3079        else begin li <= 8'd0; lb <= 32'd0; state <= LOAD; end
3080      end
3081      LOAD: begin
3082        if (li == nload) begin ii <= 16'd0; state <= RUN; end
3083        else if (lb == l_beats) state <= LOAD_LEN;
3084        else if (l_kind == 4'd0) begin
3085          s_wr <= 1'b1; s_wr_reg <= l_reg; s_wr_row <= lb[15:0]; s_wr_data <= st_rd;
3086          lb <= lb + 32'd1;
3087        end else begin
3088          rq_v <= 1'b1; rq_line <= l_line; state <= LOAD_WAIT;
3089        end
3090      end
3091      LOAD_WAIT: if (mem_rd_valid) begin
3092        s_wr <= 1'b1; s_wr_reg <= l_reg; s_wr_row <= lb[15:0]; s_wr_data <= l_from_f16 ? widened : mem_rd_data;
3093        lb <= lb + 32'd1; state <= LOAD;
3094      end
3095      LOAD_LEN: begin
3096        s_len_wr <= 1'b1; s_wr_reg <= l_reg; s_len_val <= l_len[19:0];
3097        li <= li + 8'd1; lb <= 32'd0; state <= LOAD;
3098      end
3099      RUN: begin
3100        if (ii == nprog) begin pi <= 8'd0; pb <= 32'd0; state <= PUT; end
3101        else begin s_issue <= 1'b1; s_instr <= prog[ii]; ii <= ii + 16'd1; state <= RUN_GAP; end
3102      end
3103      RUN_GAP: state <= RUN_WAIT;
3104      RUN_WAIT: if (!s_busy) state <= RUN;
3105      PUT: begin
3106        // The register's beat is read as the beat is chosen; the write lands a cycle later.
3107        if (pi == nput) state <= NEXT;
3108        else if (pb == p_beats) begin pi <= pi + 8'd1; pb <= 32'd0; end
3109        else if (p_kind == 3'd0 || wq_room) begin
3110          // A store into the token-row store goes through its write port; one into the card's memory, here.
3111          if (p_kind != 3'd0) begin
3112            wq_v <= 1'b1; wq_line <= p_byte[37:6];
3113            wq_data <= 512'd0; wq_mask <= 64'd0;
3114            if (p_kind == 3'd1 && !state_f16) begin
3115              wq_data <= p_data;
3116              for (i = 0; i < 16; i = i + 1) if (lane_mask[i]) wq_mask[4*i +: 4] <= 4'hf;
3117            end else if (p_kind == 3'd1 || p_part == 2'd0) begin
3118              wq_data <= {256'd0, halves} << (8 * p_off);
3119              for (i = 0; i < 16; i = i + 1) if (lane_mask[i]) wq_mask[p_off + 2*i +: 2] <= 2'b11;
3120            end else if (p_part == 2'd1) begin
3121              wq_data <= {384'd0, codes} << (8 * p_off);
3122              for (i = 0; i < 16; i = i + 1) if (lane_mask[i]) wq_mask[p_off + i] <= 1'b1;
3123            end else begin
3124              wq_data <= {496'd0, halves[15:0]} << (8 * p_off);
3125              wq_mask[p_off +: 2] <= 2'b11;
3126            end
3127          end
3128          pb <= pb + 32'd1;
3129        end
3130      end
3131      NEXT: begin
3132        if (heads_first) begin
3133          if (k + 16'd1 == rows) begin k <= 16'd0; h <= h + 32'd1; end else k <= k + 16'd1;
3134        end else begin
3135          if (h + 32'd1 == heads) begin h <= 32'd0; k <= k + 16'd1; end else h <= h + 32'd1;
3136        end
3137        state <= PASS;
3138      end
3139      default: state <= IDLE;
3140    endcase
3141  end
3142
3143  // The store's beat each state reads, the host's while the engine is idle; a load's or the job's tensor; a load's, a
3144  // store's or the job's layer's block.
3145  always @* begin
3146    case (state)
3147@MATVEC_READS@      LOAD: st_ra = l_store_beat;
3148@STORE_READS@      default: st_ra = host_raddr;
3149    endcase
3150    t_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : {12'd0, mv_tensor};
3151    b_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : (state == PUT) ? {16'd0, p_what} : {24'd0, hdr[12:5]};
3152  end
3153
3154@STORE_WRITE_NOTE@  always @* begin
3155    st_we = 1'b0; st_wa = 32'd0; st_wd = 512'd0; st_wm = 64'd0;
3156    if (host_wr && host_table >= 3'd6) begin
3157      st_we = 1'b1; st_wa = host_addr; st_wd = host_wdata; st_wm = {64{1'b1}};
3158@MATVEC_WRITES@    end else if (!rst && state == PUT && pi != nput && pb != p_beats && p_kind == 3'd0) begin
3159      st_we = 1'b1; st_wa = p_store_beat;
3160      if (p_scalar) begin st_wd[32 * p_elem[3:0] +: 32] = s_rd_s_val; st_wm[4 * p_elem[3:0] +: 4] = 4'hf; end
3161      else for (wi = 0; wi < 16; wi = wi + 1)
3162        if (lane_mask[wi]) begin st_wd[32*wi +: 32] = s_rd_data[32*wi +: 32]; st_wm[4*wi +: 4] = 4'hf; end
3163    end
3164@STORE_WRITES@  end
3165
3166  // The register a store reads, chosen by the store at hand.
3167  always @* begin
3168    s_rd_reg = p_reg; s_rd_row = pb[15:0]; s_rd_s = p_reg;
3169    @DELTA_READS@
3170  end
3171endmodule
3172"#;
3173
3174/// How the engine runs its matvec jobs, as fragments its template takes: its ports past the card's memory port, the
3175/// token-row store's memory, the job's declarations, states, the pulses its states raise for a cycle, its case items,
3176/// the store beats its states read (case items of the store's read address), the note on the store's write port and
3177/// its writes into the store (branches of the store's write port).
3178struct MatvecText {
3179    ports: String,
3180    store_ram: String,
3181    decls: String,
3182    states: String,
3183    pulses: String,
3184    cases: String,
3185    reads: String,
3186    write_note: String,
3187    writes: String,
3188}
3189
3190impl MatvecText {
3191    /// The reference engine's: each job's matrix read a line at a time through the card's memory port into the array's
3192    /// column of its format, the store in the engine's clock.
3193    fn reference() -> MatvecText {
3194        MatvecText {
3195            ports: String::new(),
3196            store_ram: REFERENCE_STORE_RAM.to_string(),
3197            decls: REFERENCE_MATVEC_DECLS.to_string(),
3198            states: REFERENCE_MATVEC_STATES.to_string(),
3199            pulses: REFERENCE_MATVEC_PULSES.to_string(),
3200            cases: REFERENCE_MATVEC_CASES.to_string(),
3201            reads: REFERENCE_MATVEC_READS.to_string(),
3202            write_note: REFERENCE_STORE_WRITE_NOTE.to_string(),
3203            writes: REFERENCE_MATVEC_WRITES.to_string(),
3204        }
3205    }
3206}
3207
3208impl MatvecText {
3209    /// The hybrid engine's core ([`hybrid_verilog`]): each job handed to the streaming unit in the fast clock, the store
3210    /// the fast clock's, both through the crossing between the clocks; with the recurrence unit beside the streaming unit,
3211    /// a lazily flushed memory's token-row handed to it the same way ([`delta_on_the_fast_side`]). The job's tensor's entry addresses its stream
3212    /// entry, which the core reads a line at a time through its memory port, and gives the unit with the job's slots,
3213    /// token-rows and words as one word (`sm_job`, [`HybridJob`]); the unit owns the store and the channels from the edge
3214    /// the core gives it the job until the unit's busy, as the crossing holds it, falls.
3215    fn hybrid(s: &Streaming) -> MatvecText {
3216        let job = HybridJob::BITS;
3217        MatvecText {
3218            ports: format!(
3219                ",\n   output reg sm_own, output reg sm_go, output [{}:0] sm_job, input sm_busy,\n   output [31:0] e_ra, output e_we, \
3220                 output [31:0] e_wa, output [511:0] e_wd, output [63:0] e_wm, input [511:0] e_rd{}{}",
3221                job - 1,
3222                if s.recurrence {
3223                    format!(",\n   output reg sm_gdn_go, output [{}:0] sm_gdn_job, input sm_gdn_fault", GdnJob::bits(s) - 1)
3224                } else {
3225                    String::new()
3226                },
3227                if s.vectors {
3228                    format!(",\n   output reg sm_vec_go, output [{}:0] sm_vec_job, input sm_vec_fault", VectorSideJob::BITS - 1)
3229                } else {
3230                    String::new()
3231                }
3232            ) + &if s.attention { format!(",\n   output reg sm_att_go, output [{}:0] sm_att_job", AttendJob::BITS - 1) } else { String::new() },
3233            store_ram: String::new(),
3234            decls: format!(
3235                r#"  // The matvec job, handed to the streaming unit in the fast clock: the stream entry the job's tensor's entry addresses,
3236  // with the job's slots, token-rows and words.
3237  reg [19:0] mv_tensor;
3238  reg [4:0] mv_from, mv_to;
3239  wire [127:0] mv_entry = tw;
3240  reg [511:0] se0, se1, se2;
3241  reg [1:0] se_i;
3242  assign sm_job = {{two_words, rows[7:0], slott[mv_to], slott[mv_from], se2, se1, se0[{entry_top}:0]}};
3243  // The store, the fast clock's through the crossing.
3244  assign st_rd = e_rd;
3245  assign e_ra = st_ra;
3246  assign e_we = st_we;
3247  assign e_wa = st_wa;
3248  assign e_wd = st_wd;
3249  assign e_wm = st_wm;
3250{vector_job}@QUANTIZERS@"#,
3251                entry_top = HybridJob::ENTRY - 1,
3252                vector_job = if s.vectors { VECTOR_JOB } else { "" },
3253            ),
3254            states: format!(
3255                "             MV_HDR = 7'd15, MV_SL = 7'd16, MV_SLW = 7'd17, MV_RUN = 7'd18,{}\n",
3256                if s.vectors { " V_SG = 7'd76, V_SIDE = 7'd77," } else { "" }
3257            ),
3258            pulses: format!(" sm_go <= 1'b0; if (rst) sm_own <= 1'b0;{}", if s.vectors { " sm_vec_go <= 1'b0;" } else { "" }),
3259            cases: format!(
3260                r#"      MV_HDR: begin
3261        mv_tensor <= jw[19:0]; mv_from <= jw[24:20]; mv_to <= jw[29:25]; pc <= pc + 32'd1; se_i <= 2'd0;
3262        if (!per_block{one_word}) begin fault <= 1'b1; state <= IDLE; end else state <= MV_SL;
3263      end
3264      // The stream entry a line a read, then the store and the channels the unit's, and its start.
3265      MV_SL: begin
3266        if (se_i == 2'd3) begin sm_own <= 1'b1; sm_go <= 1'b1; state <= MV_RUN; end
3267        else begin rq_v <= 1'b1; rq_line <= mv_entry[31:0] + {{30'd0, se_i}}; state <= MV_SLW; end
3268      end
3269      MV_SLW: if (mem_rd_valid) begin
3270        case (se_i)
3271          2'd0: se0 <= mem_rd_data;
3272          2'd1: se1 <= mem_rd_data;
3273          default: se2 <= mem_rd_data;
3274        endcase
3275        se_i <= se_i + 2'd1; state <= MV_SL;
3276      end
3277      MV_RUN: if (!sm_go && !sm_busy) begin sm_own <= 1'b0; state <= next_job; end
3278{vector_cases}"#,
3279                one_word = if s.set.words == Words::Two { "" } else { " || two_words" },
3280                vector_cases = if s.vectors { VECTOR_CASES } else { "" },
3281            ),
3282            reads: String::new(),
3283            write_note: "  // The store's write port: the host's beat; a store's lanes; the units' writes.\n".to_string(),
3284            writes: String::new(),
3285        }
3286    }
3287}
3288
3289/// The word a hybrid engine's core gives its streaming unit with a matvec job, its fields from the lowest bit: the
3290/// stream entry's first line's seven words ([`super::streaming::StreamEntry`]: the seventh its greedy flag and, from bit
3291/// 1, its fold), its four tile runs (its other two lines), the input slot's and the output slot's table entries, the
3292/// step's token-rows and whether it runs two words.
3293pub struct HybridJob;
3294
3295impl HybridJob {
3296    pub const GREEDY: usize = 192;
3297    pub const FOLD: usize = Self::GREEDY + 1;
3298    pub const ENTRY: usize = 224;
3299    pub const RUNS: usize = Self::ENTRY;
3300    pub const FROM: usize = Self::RUNS + 1024;
3301    pub const TO: usize = Self::FROM + 64;
3302    pub const TOKENS: usize = Self::TO + 64;
3303    pub const TWO: usize = Self::TOKENS + 8;
3304    pub const BITS: usize = Self::TWO + 1;
3305}
3306
3307/// The word a hybrid engine's core gives its attention side with a token-row of an attention job
3308/// ([`Streaming::attention`]), its fields from the lowest bit, each 32 bits but the last: the row's region (its first
3309/// line), its block's keys', values', key scales' and value scales' offsets in it, the cached heads, the row's positions
3310/// (its own and every one before), the score scale's bits, the store beats of the row's query and of its output, and
3311/// the query heads (16 bits).
3312pub struct AttendJob;
3313
3314impl AttendJob {
3315    pub const REGION: usize = 0;
3316    pub const KEYS: usize = 32;
3317    pub const VALUES: usize = 64;
3318    pub const KEY_SCALES: usize = 96;
3319    pub const VALUE_SCALES: usize = 128;
3320    pub const KV_HEADS: usize = 160;
3321    pub const POSITIONS: usize = 192;
3322    pub const SCALE: usize = 224;
3323    pub const QUERY: usize = 256;
3324    pub const OUTPUT: usize = 288;
3325    pub const HEADS: usize = 320;
3326    pub const BITS: usize = 336;
3327}
3328
3329/// The word a hybrid engine's core gives its vector unit's side with a vector job ([`Streaming::vectors`]), its fields
3330/// from the lowest bit: the place of the job's header in the job words, the step's token-rows (16 bits), and whether the
3331/// state stores binary16.
3332pub struct VectorSideJob;
3333
3334impl VectorSideJob {
3335    pub const PC: usize = 0;
3336    pub const ROWS: usize = 32;
3337    pub const F16: usize = 48;
3338    pub const BITS: usize = 49;
3339}
3340
3341/// The core of a hybrid engine whose vector jobs run on the fast side ([`Streaming::vectors`]): the engine's template with a
3342/// vector job's header going to the hand-off ([`VECTOR_CASES`]) and the core's own vector-job states, their descriptors,
3343/// their loads' and stores' places and their store writes gone; the vector unit stays for the recurrence unit's `k·q`
3344/// of a memory rewritten every token, and with `handed_off` (every memory lazily flushed, its token-rows the recurrence
3345/// unit's) it goes too: its registers, its instance, its pulses and its read port.
3346fn vector_jobs_on_the_fast_side(template: &str, handed_off: bool) -> String {
3347    let text = replaced(
3348        template,
3349        &[
3350            ("        if (jw[3:0] == 4'd2) state <= HEADS;\n", "        if (jw[3:0] == 4'd2) state <= V_SG;\n"),
3351            (
3352                "  reg [31:0] heads;\n  `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_a [0:MAXL-1];\n  `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_b [0:MAXL-1];\n  `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_a [0:MAXP-1];\n  `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_b [0:MAXP-1];\n  reg [63:0] prog [0:MAXI-1];\n  reg [15:0] dc;\n",
3353                "",
3354            ),
3355            (
3356                "  // The pass: its row and head, the load, store or instruction it is at, and the beat.\n  reg [15:0] k;\n  reg [31:0] h;\n  \
3357                 reg [7:0] li, pi;\n  reg [15:0] ii;\n  reg [31:0] lb, pb;\n",
3358                "  // The row and the head a unit's job is at.\n  reg [15:0] k;\n  reg [31:0] h;\n",
3359            ),
3360            ("  wire [7:0] ring = rw[71:64];\n", ""),
3361            ("@MATVEC_READS@      LOAD: st_ra = l_store_beat;\n", "@MATVEC_READS@"),
3362            (
3363                "    t_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : {12'd0, mv_tensor};\n    \
3364                 b_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : (state == PUT) ? {16'd0, p_what} : {24'd0, hdr[12:5]};\n",
3365                "    t_ra = {12'd0, mv_tensor};\n    b_ra = {24'd0, hdr[12:5]};\n",
3366            ),
3367            ("    s_rd_reg = p_reg; s_rd_row = pb[15:0]; s_rd_s = p_reg;\n", "    s_rd_reg = 4'd0; s_rd_row = 16'd0; s_rd_s = 4'd0;\n"),
3368        ],
3369    );
3370    let text = spliced(&text, "  // The load at hand.\n", "@MATVEC_DECLS@", "  wire [31:0] st_bytes = state_f16 ? 32'd2 : 32'd4;\n  genvar g;\n\n");
3371    let text = spliced(&text, "      HEADS: begin heads <= jw[31:0];", "      default: state <= IDLE;\n    endcase", "");
3372    let text = spliced(&text, "    end else if (!rst && state == PUT && pi != nput", "@STORE_WRITES@", "    end\n");
3373    for gone in [
3374        "ld_a", "pt_b", "prog[", "l_store_beat", "p_store_beat", "l_ring_byte", "p_cached", "lane_mask", "p_byte", "l_line", "HEADS:",
3375        "LOAD_WAIT:", "PUT:", "NEXT:", "state <= HEADS",
3376    ] {
3377        assert!(!text.contains(gone), "a core whose vector jobs run on the fast side keeps no `{gone}`");
3378    }
3379    if !handed_off {
3380        return text;
3381    }
3382    let text = spliced(&text, "  // The vector unit and its ports.\n", "  localparam IDLE = 7'd0,", "");
3383    let text = replaced(
3384        &text,
3385        &[
3386            ("    s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; rq_v <= 1'b0; wq_v <= 1'b0;", "    rq_v <= 1'b0; wq_v <= 1'b0;"),
3387            (
3388                "  // The register a store reads, chosen by the store at hand.\n  always @* begin\n    s_rd_reg = 4'd0; s_rd_row = 16'd0; s_rd_s = 4'd0;\n    @DELTA_READS@\n  end\n",
3389                "",
3390            ),
3391        ],
3392    );
3393    for gone in ["logos_simd", "s_issue", "s_rd_reg", "s_wr_data", "s_busy"] {
3394        assert!(!text.contains(gone), "a core that hands every token-row and vector job to the fast side keeps no `{gone}`");
3395    }
3396    text
3397}
3398
3399/// The recurrence unit of a vectored hybrid engine's core whose memories are all lazily flushed ([`Streaming::vectors`]
3400/// with a log of more than one token): the job's slots and its block's places read, and each token-row handed whole to
3401/// the fast side's recurrence unit ([`GdnJob`], [`GDN_CASES`]); a block rewritten every token (a log of none) faults, the
3402/// row engine that would update it absent with the vector unit its `k·q` needs.
3403fn delta_handed_off(hd: usize, t_max: usize, normed: bool) -> UnitVerilog {
3404    let decls = format!(
3405        r#"  // The recurrence unit: the job's slots and its block's place in a region, each token-row the fast side's.
3406  localparam HD = {hd}, TMAX = {t_max};
3407  localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75;
3408  reg [4:0] dl_qs, dl_ks, dl_vs, dl_lds, dl_dcs, dl_gts, dl_tos;
3409  reg [19:0] dl_vat;
3410  wire [511:0] dlb = bw;
3411  wire [31:0] dl_mem = dlb[127:96], dl_log = dlb[159:128], dl_entry = dlb[191:160];
3412  wire [31:0] dl_nk = dlb[223:192], dl_nv = dlb[255:224], dl_hd = dlb[287:256];
3413  wire [31:0] dl_fval = dlb[319:288], dl_fdec = dlb[351:320], dl_fgate = dlb[383:352], dl_T = dlb[415:384], dl_scale = dlb[447:416];
3414  // A slot's beat holding element `e` of the row at hand; the element is the beat's lane `e mod 16`.
3415  function [31:0] element_beat(input [63:0] slot, input [31:0] e);
3416    element_beat = slot[31:0] + {{16'd0, k}} * slot[63:32] + (e >> 4);
3417  endfunction
3418{GDN_JOB}"#
3419    );
3420    let cases = format!(
3421        r#"      DL_HDR: begin
3422        dl_qs <= jw[4:0]; dl_ks <= jw[9:5]; dl_vs <= jw[14:10]; dl_lds <= jw[19:15];
3423        dl_dcs <= jw[24:20]; dl_gts <= jw[29:25]; dl_tos <= jw[34:30]; dl_vat <= jw[54:35];
3424        pc <= pc + 32'd1; k <= 16'd0; state <= DL_ROW;
3425      end
3426      DL_ROW: begin
3427        if (k == rows) state <= next_job;
3428        else if (dl_T == 32'd0) begin fault <= 1'b1; state <= IDLE; end
3429        else state <= DL_SG;
3430      end
3431{GDN_CASES}"#
3432    );
3433    let unit = UnitVerilog { decls, cases, reads: String::new(), writes: String::new() };
3434    let unit = if normed { delta_normed_core(unit) } else { unit };
3435    for gone in ["dre", "dlr", "dl_kv", "dl_qv", "DL_KQ", "DL_E_", "dl_vals", "s_issue", "s_wr"] {
3436        for text in [&unit.decls, &unit.cases] {
3437            assert!(!text.contains(gone), "a recurrence unit that hands every token-row to the fast side keeps no `{gone}`");
3438        }
3439    }
3440    unit
3441}
3442
3443/// The attention unit of a hybrid engine whose attention runs on the fast side ([`Streaming::attention`]): each
3444/// token-row of an attention job handed whole to the attention side ([`AttendJob`]) once every write before it has
3445/// left, the core waiting for it; no attention unit, no quantizer, no cache read of the core's own.
3446fn attend_handed_off() -> UnitVerilog {
3447    let decls = r#"  // The attention unit: the job's slots, heads and score scale, its block's cache in a region, each token-row the
3448  // fast side's.
3449  localparam [6:0] AT_SG = 7'd94, AT_SIDE = 7'd95;
3450  reg [4:0] at_qs, at_tos;
3451  reg [15:0] at_heads;
3452  reg [31:0] at_scale;
3453  wire [511:0] atb = bw;
3454  wire [31:0] at_keys = atb[31:0], at_vals = atb[63:32], at_kscales = atb[95:64], at_vscales = atb[127:96];
3455  wire [31:0] at_kvh = atb[159:128];
3456  wire [63:0] at_qslot = slott[at_qs], at_oslot = slott[at_tos];
3457  wire [31:0] at_qbeat = at_qslot[31:0] + {16'd0, k} * at_qslot[63:32], at_obeat = at_oslot[31:0] + {16'd0, k} * at_oslot[63:32];
3458  assign sm_att_job = {at_heads, at_obeat, at_qbeat, at_scale, position + 32'd1, at_kvh, at_vscales, at_kscales, at_vals, at_keys,
3459    region};
3460"#
3461    .to_string();
3462    let cases = r#"      AT_HDR: begin
3463        at_qs <= jw[4:0]; at_tos <= jw[9:5]; at_heads <= jw[25:10]; at_scale <= jw[57:26];
3464        pc <= pc + 32'd1; k <= 16'd0; state <= AT_ROW;
3465      end
3466      AT_ROW: if (k == rows) state <= next_job; else state <= AT_SG;
3467      // A token-row on the fast side's attention side, once every write before it has left.
3468      AT_SG: if (wq_empty) begin sm_own <= 1'b1; sm_att_go <= 1'b1; state <= AT_SIDE; end
3469      AT_SIDE: if (!sm_att_go && !sm_busy) begin sm_own <= 1'b0; k <= k + 16'd1; state <= AT_ROW; end
3470"#
3471    .to_string();
3472    UnitVerilog { decls, cases, reads: String::new(), writes: String::new() }
3473}
3474
3475/// The core's vector job for the fast side ([`VectorSideJob`]).
3476const VECTOR_JOB: &str = r#"  // A vector job as the fast side's vector unit takes it (`VectorSideJob`): its header's place, the step's token-rows,
3477  // whether the state stores binary16.
3478  reg [31:0] vs_pc;
3479  assign sm_vec_job = {state_f16, rows, vs_pc};
3480"#;
3481
3482/// The core's hand-off of a vector job to the fast side and its taking the store and the channels back, the program
3483/// counter past the job's words.
3484const VECTOR_CASES: &str = r#"      // A vector job on the fast side's vector unit — its loads, its micro-program and its stores, each pass of a row and a
3485      // head — once every write before it has left; the job's words passed.
3486      V_SG: if (wq_empty) begin
3487        vs_pc <= pc - 32'd1;
3488        pc <= pc + 32'd1 + {24'd0, nfresh} + {23'd0, nload, 1'b0} + {23'd0, nput, 1'b0} + {16'd0, nprog};
3489        sm_own <= 1'b1; sm_vec_go <= 1'b1; state <= V_SIDE;
3490      end
3491      V_SIDE: if (!sm_vec_go && !sm_busy) begin
3492        sm_own <= 1'b0;
3493        if (sm_vec_fault) begin fault <= 1'b1; state <= IDLE; end else state <= next_job;
3494      end
3495"#;
3496
3497/// The word a hybrid engine's core gives its recurrence unit with a job of a lazily flushed memory's token-rows
3498/// ([`Streaming::recurrence`]), its fields from the lowest bit: the job's rows (16 bits); its block's memory's and log's
3499/// offsets in a row's region and a log entry's bytes, its key and value heads, a log entry's values', decay exponents' and
3500/// gates' first elements, the read-out's scale's bits (32 bits each); the store slots of the rows' query, output, key
3501/// heads, value heads, decay exponents and gates (5 bits each) and the values' first element in theirs (20 bits); the
3502/// tokens a flush takes (8 bits), and whether the state stores binary16. The fast side lays each row's region and store
3503/// beats from its copies of the rows and slots tables (`logos_gdn_rows`).
3504pub struct GdnJob;
3505
3506impl GdnJob {
3507    pub const ROWS: usize = 0;
3508    pub const MEMORY: usize = 16;
3509    pub const LOG: usize = 48;
3510    pub const ENTRY: usize = 80;
3511    pub const KEY_HEADS: usize = 112;
3512    pub const VALUE_HEADS: usize = 144;
3513    pub const VALUES: usize = 176;
3514    pub const DECAYS: usize = 208;
3515    pub const GATES: usize = 240;
3516    pub const SCALE: usize = 272;
3517    pub const QUERY: usize = 304;
3518    pub const OUTPUT: usize = 309;
3519    pub const KEY: usize = 314;
3520    pub const VALUE: usize = 319;
3521    pub const LOG_DECAY: usize = 324;
3522    pub const GATE: usize = 329;
3523    pub const VALUE_AT: usize = 334;
3524    pub const TOKENS: usize = 354;
3525    pub const F16: usize = 362;
3526    pub const BITS: usize = 363;
3527    /// A build whose recurrence makes its gated norms ([`Streaming::delta_norm`]): whether the job carries one, its
3528    /// gate's slot, the norm's epsilon and its weight's first line, past the fields every build takes.
3529    pub const NORM: usize = 363;
3530    pub const Z: usize = 364;
3531    pub const EPS: usize = 369;
3532    pub const W_LINE: usize = 401;
3533    pub const NORMED_BITS: usize = 433;
3534
3535    /// The bits of the job word of the build `s`.
3536    pub fn bits(s: &Streaming) -> usize {
3537        if s.delta_norm { GdnJob::NORMED_BITS } else { GdnJob::BITS }
3538    }
3539}
3540
3541/// The recurrence unit of a hybrid engine whose lazily flushed memories run on the fast side
3542/// ([`Streaming::recurrence`]): `core`'s ([`delta_verilog`]'s) with every token's update kept and a lazily flushed
3543/// memory's token-row handed whole to the fast side's recurrence unit ([`GdnJob`]) — the log's count read, the row's
3544/// entry appended, the fold, the read-out, the flush and the count written back there — once every write before it has
3545/// left; the core's lazy row engine, its log's append and count, and their registers and states gone.
3546fn delta_on_the_fast_side(core: UnitVerilog) -> UnitVerilog {
3547    let decls = replaced(
3548        &core.decls,
3549        &[
3550            ("  reg [19:0] dl_vat;\n", "  reg [19:0] dl_vat;\n  localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75;\n"),
3551            (
3552                "  reg [31:0] dg, dr, dj, de, dl_count, dob;\n  reg [1:0] dfld;\n  reg [2:0] dl_ii;\n  reg dl_flush;\n",
3553                "  reg [31:0] dg, dr, dj, dob;\n",
3554            ),
3555            (
3556                "  reg [32*HD-1:0] dl_kv, dl_qv, dl_y, dl_c;\n  `LOGOS_RAMSTYLE_ASYNC(TMAX) reg [32*HD-1:0] dl_vals [0:TMAX-1];\n  \
3557                 reg [31:0] dl_kq, dl_decay, dl_beta, dl_dec_e, dl_beta_e;\n  reg [3:0] dl_rd_reg, dl_rd_s;\n  reg [15:0] dl_rd_row;\n  // The vector unit's read port is the fold's while its folded query and carried values are read out: a function of\n  // the state, so no power-up value of a register of its own can hand it to the fold outside those states.\n  wire dl_rd = state == DL_FY || state == DL_FC;\n",
3558                "  reg [32*HD-1:0] dl_kv, dl_qv;\n  reg [31:0] dl_kq, dl_decay, dl_beta;\n",
3559            ),
3560            ("  wire [255:0] dl_halves, dl_app_halves;\n", "  wire [255:0] dl_halves;\n"),
3561            ("      logos_f32_to_f16 na(.x(dl_app_beat[32*dgv +: 32]), .h(dl_app_halves[16*dgv +: 16]));\n", ""),
3562            (
3563                "  // The row engines: every token's update, and a lazily flushed memory's read-out and replay.\n  \
3564                 reg dre_v, dre_first, dlr_v, dlr_first, dlr_log_wr;\n  reg [511:0] dre_data;\n  wire dre_ov, dre_ol, dlr_ov, dlr_ol, dlr_busy;\n  \
3565                 wire [511:0] dre_od, dlr_od;\n  wire [31:0] dre_or, dlr_or;\n",
3566                "  // The row engine: every token's update.\n  reg dre_v, dre_first;\n  reg [511:0] dre_data;\n  wire dre_ov, dre_ol;\n  \
3567                 wire [511:0] dre_od;\n  wire [31:0] dre_or;\n",
3568            ),
3569            (
3570                "  wire dl_out_v = dl_lazy ? dlr_ov : dre_ov, dl_out_l = dl_lazy ? dlr_ol : dre_ol;\n  \
3571                 assign dl_out_beat_data = dl_lazy ? dlr_od : dre_od;\n  wire [31:0] dl_out_read = dl_lazy ? dlr_or : dre_or;\n",
3572                "  wire dl_out_v = dre_ov, dl_out_l = dre_ol;\n  assign dl_out_beat_data = dre_od;\n  wire [31:0] dl_out_read = dre_or;\n",
3573            ),
3574        ],
3575    );
3576    let decls = spliced(&decls, "  // The log: its count's line, the entry at hand, a field's beat.\n", "  // A state beat as the engine reads it:", "");
3577    let decls = spliced(&decls, "  // An entry's key or value beat at hand during the fold", "  // The row engine: every token's update.\n", "");
3578    let decls = spliced(&decls, "  logos_gdn_lazy_row #(", "  wire dl_lazy = dl_T != 32'd0;\n", "");
3579    let decls = spliced(&decls, "  // A fold's instruction.\n", "  // The read-out's element of the job's output.\n", GDN_JOB);
3580    let cases = replaced(
3581        &core.cases,
3582        &[
3583            (
3584                "        else begin h <= 32'd0; dg <= 32'd0; dj <= 32'd0; state <= dl_lazy ? DL_CNT : DL_KQ_K; end\n",
3585                "        else begin h <= 32'd0; dg <= 32'd0; dj <= 32'd0; state <= dl_lazy ? DL_SG : DL_KQ_K; end\n",
3586            ),
3587            (
3588                "      DL_E_OUT, DL_L_OUT: begin\n        if (dl_out_v && !(dl_lazy && !dl_flush)) begin\n",
3589                "      DL_E_OUT: begin\n        if (dl_out_v) begin\n",
3590            ),
3591            (
3592                "            if (h + 32'd1 == dl_nv) begin\n              if (dl_lazy) state <= DL_CNT_WR;\n              \
3593                 else begin k <= k + 16'd1; state <= DL_ROW; end\n            end else begin\n",
3594                "            if (h + 32'd1 == dl_nv) begin k <= k + 16'd1; state <= DL_ROW; end\n            else begin\n",
3595            ),
3596            ("              state <= dl_lazy ? DL_FQ : DL_KQ_K;\n", "              state <= DL_KQ_K;\n"),
3597            (
3598                "          end else begin dr <= dr + 32'd1; state <= dl_lazy ? DL_L_RD : DL_E_RD; end\n",
3599                "          end else begin dr <= dr + 32'd1; state <= DL_E_RD; end\n",
3600            ),
3601        ],
3602    );
3603    let lazy = cases.find("      // A lazily flushed memory: the row's token logged after the count,").expect("the lazily flushed memory's states");
3604    assert!(cases[lazy..].trim_end().ends_with("end") && cases[lazy..].matches("DL_CNT_WR: if (wq_room) begin").count() == 1, "the lazy states end the unit's cases");
3605    let cases = format!("{}{GDN_CASES}", &cases[..lazy]);
3606    let reads = replaced(
3607        &core.reads,
3608        &[("      DL_KQ_Q, DL_FQ: st_ra = dl_qs_beat;\n", "      DL_KQ_Q: st_ra = dl_qs_beat;\n"), ("      DL_APP: st_ra = dl_app_src;\n", "")],
3609    );
3610    let writes = replaced(
3611        &core.writes,
3612        &[("    else if (!rst && (state == DL_E_OUT || state == DL_L_OUT) && dl_out_l) begin\n", "    else if (!rst && state == DL_E_OUT && dl_out_l) begin\n")],
3613    );
3614    let unit = UnitVerilog { decls, cases, reads, writes };
3615    for gone in [
3616        "dlr", "dl_vals", "dl_y", "dl_c[", "dl_ii", "dl_rd", "dl_word", "DL_F", "DL_L_", "DL_CNT", "DL_APP", "dl_app", "dl_count", "dfld",
3617        "dl_flush", "dl_log_byte", "dl_entry_byte",
3618    ] {
3619        for text in [&unit.decls, &unit.cases, &unit.reads, &unit.writes] {
3620            assert!(!text.contains(gone), "the fast side's recurrence leaves the core no `{gone}`");
3621        }
3622    }
3623    unit
3624}
3625
3626/// [`delta_handed_off`]'s unit for a build whose recurrence makes its gated norms ([`Streaming::delta_norm`]): a job that
3627/// carries one (its slots word's bit 55, its gate's slot in bits 56 to 60) reads its next word — the norm weight's
3628/// tensor and the norm's epsilon — and a step later the weight's first line from the tensor table, as a matrix job reads
3629/// its entry, and hands the fast side all four with the job ([`GdnJob::NORM`] on).
3630fn delta_normed_core(unit: UnitVerilog) -> UnitVerilog {
3631    let decls = replaced(
3632        &unit.decls,
3633        &[
3634            (
3635                "  localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75;\n",
3636                "  localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75, DL_NORM = 7'd78, DL_WLINE = 7'd79;\n  \
3637                 // The job's gated norm: whether it carries one, its gate's slot, its epsilon, its weight's first line.\n  \
3638                 reg dl_norm;\n  reg [4:0] dl_zs;\n  reg [31:0] dl_eps, dl_wline;\n",
3639            ),
3640            ("  assign sm_gdn_job = {state_f16,", "  assign sm_gdn_job = {dl_wline, dl_eps, dl_zs, dl_norm, state_f16,"),
3641        ],
3642    );
3643    let cases = replaced(
3644        &unit.cases,
3645        &[(
3646            "        pc <= pc + 32'd1; k <= 16'd0; state <= DL_ROW;\n      end\n",
3647            "        dl_norm <= jw[55]; dl_zs <= jw[60:56];\n        pc <= pc + 32'd1; k <= 16'd0; state <= jw[55] ? DL_NORM : DL_ROW;\n      end\n      \
3648             // A gated norm's word: its weight's tensor, whose first line the tensor table gives a step later, and its\n      \
3649             // epsilon.\n      \
3650             DL_NORM: begin mv_tensor <= jw[19:0]; dl_eps <= jw[51:20]; pc <= pc + 32'd1; state <= DL_WLINE; end\n      \
3651             DL_WLINE: begin dl_wline <= mv_entry[31:0]; state <= DL_ROW; end\n",
3652        )],
3653    );
3654    UnitVerilog { decls, cases, ..unit }
3655}
3656
3657/// The core's recurrence job for the fast side ([`GdnJob`]).
3658const GDN_JOB: &str = r#"  // A lazily flushed memory's token-rows as the fast side's recurrence unit takes them (`GdnJob`): the step's rows, the
3659  // block's places, the read-out's scale, the slots of the rows' query, output, key heads, value heads, decay exponents
3660  // and gates and the values' first element, the tokens a flush takes, whether the state stores binary16.
3661  assign sm_gdn_job = {state_f16, dl_T[7:0], dl_vat, dl_gts, dl_lds, dl_vs, dl_ks, dl_tos, dl_qs, dl_scale, dl_fgate, dl_fdec,
3662    dl_fval, dl_nv, dl_nk, dl_entry, dl_log, dl_mem, rows};
3663"#;
3664
3665/// The core's hand-off of a lazily flushed memory's token-rows to the fast side and its taking the store and the
3666/// channels back: a block whose heads are not the unit's width, or whose log holds more tokens than the unit's, faults.
3667const GDN_CASES: &str = r#"      // A lazily flushed memory's token-rows on the fast side's recurrence unit, all of the job's at once — each row's token
3668      // logged after its log's count, the query folded back through the log for each head, each value row read through the
3669      // folded query and, once the log is full, replayed and stored, the count written back — once every write before it
3670      // has left.
3671      DL_SG: if (wq_empty) begin
3672        if (dl_hd != $unsigned(HD) || dl_T > $unsigned(TMAX)) begin fault <= 1'b1; state <= IDLE; end
3673        else begin sm_own <= 1'b1; sm_gdn_go <= 1'b1; state <= DL_SIDE; end
3674      end
3675      DL_SIDE: if (!sm_gdn_go && !sm_busy) begin
3676        sm_own <= 1'b0;
3677        if (sm_gdn_fault) begin fault <= 1'b1; state <= IDLE; end else begin k <= rows; state <= DL_ROW; end
3678      end
3679"#;
3680
3681const REFERENCE_STORE_RAM: &str = r#"  logos_ram_w512 #(.D(STORE_BEATS), .A(SA)) store_ram(.clk(clk), .ra(st_ra[SA-1:0]), .rd(st_rd),
3682    .we(st_we), .wa(st_wa[SA-1:0]), .wd(st_wd), .wm(st_wm));
3683"#;
3684
3685const REFERENCE_MATVEC_DECLS: &str = r#"  // The array's job: its tensor's format, rows and blocks, the token-rows' activations a weight block at a time, and
3686  // the weight stream two rows' blocks a beat, each block gathered from the lines it spans.
3687  reg [19:0] mv_tensor;
3688  reg [4:0] mv_from, mv_to;
3689  wire [127:0] mv_entry = tw;
3690  wire [7:0] mv_type = mv_entry[39:32];
3691  wire [31:0] mv_in = {8'd0, mv_entry[63:40]}, mv_out = {8'd0, mv_entry[87:64]};
3692  reg mv_known;
3693  reg [3:0] mv_fmt;
3694  reg [15:0] mv_blen, mv_bbytes;
3695  always @* begin
3696    mv_known = 1'b0; mv_fmt = 4'd0; mv_blen = 16'd32; mv_bbytes = 16'd0;
3697    case (mv_type)
3698@FORMAT_CASES@      default: ;
3699    endcase
3700  end
3701  wire [31:0] mv_blocks = mv_in / {16'd0, mv_blen};
3702  wire [31:0] mv_pairs = (mv_out + 32'd1) >> 1;
3703  wire [31:0] mv_row_bytes = mv_blocks * {16'd0, mv_bbytes};
3704  reg mv_start, act_wr;
3705  wire w_valid = (state == MV_PRESENT);
3706  reg [7:0] act_t;
3707  reg [15:0] act_b;
3708  reg [31:0] mv_p, mv_b, aj;
3709@QUANTIZERS@
3710  reg [@WIDEST@:0] w_blk1, w_blk2;
3711  reg [2559:0] gbuf;
3712  reg [63:0] g_byte;
3713  reg [3:0] g_i, g_n;
3714  reg g_two;
3715  // The lines a block of the row at hand spans from its byte in the line.
3716  wire [15:0] g_lines = (g_byte[5:0] + mv_bbytes + 16'd63) >> 6;
3717@COLUMNS@
3718  wire mv_ready = @READY@;
3719  wire mv_col_busy = @COLUMN_BUSY@;
3720  reg o_v;
3721  reg [15:0] o_p;
3722  reg [7:0] o_t;
3723  reg [31:0] o_1, o_2;
3724  always @* begin
3725    o_v = 1'b0; o_p = 16'd0; o_t = 8'd0; o_1 = 32'd0; o_2 = 32'd0;
3726@OUT_MUX@  end
3727  wire [63:0] mv_to_slot = slott[mv_to];
3728  wire [31:0] o_e = {15'd0, o_p, 1'b0};
3729  wire [31:0] o_beat = mv_to_slot[31:0] + {24'd0, o_t} * mv_to_slot[63:32] + (o_e >> 4);
3730  wire [63:0] mv_from_slot = slott[mv_from];
3731  // A gathered block: the lines it spans, from its first byte.
3732  wire [2559:0] g_shifted = gbuf >> (8 * g_byte[5:0]);
3733  wire [63:0] g_row_byte = {32'd0, mv_entry[31:0]} * 64 + mv_b * mv_bbytes;
3734"#;
3735
3736const REFERENCE_MATVEC_STATES: &str = r#"             MV_HDR = 7'd15, MV_ACT = 7'd16, MV_ACT_WR = 7'd17, MV_ACT_GAP = 7'd18, MV_GO = 7'd19, MV_NEXT = 7'd20,
3737             MV_G = 7'd21, MV_GW = 7'd22, MV_PRESENT = 7'd23, MV_DRAIN = 7'd24,
3738"#;
3739
3740const REFERENCE_MATVEC_CASES: &str = r#"      MV_HDR: begin
3741        mv_tensor <= jw[19:0]; mv_from <= jw[24:20]; mv_to <= jw[29:25]; pc <= pc + 32'd1;
3742        act_t <= 8'd0; act_b <= 16'd0; aj <= 32'd0;
3743        if (!per_block) begin fault <= 1'b1; state <= IDLE; end else state <= MV_ACT;
3744      end
3745      MV_ACT: begin
3746        // A token-row's weight block of activations, a beat a cycle into the quantizer, then its codes and scale into
3747        // the column.
3748        if (!mv_known) begin fault <= 1'b1; state <= IDLE; end
3749        else if ({8'd0, act_t} == {8'd0, rows}) state <= MV_GO;
3750        else if (aj < {16'd0, mv_blen} >> 4) aj <= aj + 32'd1;
3751        else state <= MV_ACT_WR;
3752      end
3753      MV_ACT_WR: if (!aq_busy) begin act_wr <= 1'b1; state <= MV_ACT_GAP; end
3754      MV_ACT_GAP: begin
3755        aj <= 32'd0;
3756        if ({16'd0, act_b} + 32'd1 == mv_blocks) begin act_b <= 16'd0; act_t <= act_t + 8'd1; end else act_b <= act_b + 16'd1;
3757        state <= MV_ACT;
3758      end
3759      MV_GO: begin mv_start <= 1'b1; mv_p <= 32'd0; mv_b <= 32'd0; state <= MV_NEXT; end
3760      MV_NEXT: begin
3761        if (mv_p == mv_pairs) state <= MV_DRAIN;
3762        else begin
3763          g_two <= 1'b0; g_i <= 4'd0;
3764          g_byte <= g_row_byte + {32'd0, mv_p} * 2 * mv_row_bytes;
3765          state <= MV_G;
3766        end
3767      end
3768      MV_G: begin
3769        g_n <= g_lines[3:0];
3770        if (g_i != 4'd0 && g_i == g_n) begin
3771          if (!g_two) begin
3772            w_blk1 <= g_shifted[@WIDEST@:0];
3773            g_two <= 1'b1; g_i <= 4'd0;
3774            // The pair's second row, or its first again past the matrix's last row.
3775            g_byte <= g_row_byte + ((2 * mv_p + 1 < mv_out) ? ({32'd0, mv_p} * 2 + 1) : ({32'd0, mv_p} * 2)) * mv_row_bytes;
3776          end else begin
3777            w_blk2 <= g_shifted[@WIDEST@:0];
3778            state <= MV_PRESENT;
3779          end
3780        end else begin
3781          rq_v <= 1'b1; rq_line <= g_byte[37:6] + g_i; state <= MV_GW;
3782        end
3783      end
3784      MV_GW: if (mem_rd_valid) begin
3785        gbuf[512 * g_i +: 512] <= mem_rd_data;
3786        g_i <= g_i + 4'd1;
3787        g_n <= g_lines[3:0];
3788        state <= MV_G;
3789      end
3790      MV_PRESENT: if (mv_ready) begin
3791        if (mv_b + 32'd1 == mv_blocks) begin mv_b <= 32'd0; mv_p <= mv_p + 32'd1; end else mv_b <= mv_b + 32'd1;
3792        state <= MV_NEXT;
3793      end
3794      MV_DRAIN: if (!mv_col_busy && !mv_start) state <= next_job;
3795"#;
3796
3797const REFERENCE_MATVEC_READS: &str = r#"      MV_ACT: st_ra = mv_from_slot[31:0] + {24'd0, act_t} * mv_from_slot[63:32] + {16'd0, act_b} * ({16'd0, mv_blen} >> 4) + aj;
3798"#;
3799
3800const REFERENCE_MATVEC_WRITES: &str = r#"    end else if (o_v) begin
3801      st_we = 1'b1; st_wa = o_beat;
3802      st_wd[32 * o_e[3:0] +: 32] = o_1; st_wm[4 * o_e[3:0] +: 4] = 4'hf;
3803      if (o_e + 32'd1 < mv_out) begin st_wd[32 * (o_e[3:0] + 4'd1) +: 32] = o_2; st_wm[4 * (o_e[3:0] + 4'd1) +: 4] = 4'hf; end
3804"#;
3805
3806const REFERENCE_STORE_WRITE_NOTE: &str = r#"  // The store's write port: the host's beat; a column's two outputs under their lanes, the second past the matrix's
3807  // last row kept out; a store's lanes; the units' writes.
3808"#;
3809
3810const REFERENCE_MATVEC_PULSES: &str = r#" mv_start <= 1'b0; act_wr <= 1'b0;"#;
3811
3812/// The recurrence unit for heads of `hd` elements and logs of `t_max` tokens: its declarations, row engines and
3813/// wiring, and its states, each a Verilog fragment the engine's template takes. The vector unit gives each head's
3814/// `k·q`, and folds a lazily flushed head's query back through its log, an entry's instructions at a time, newest first.
3815/// A unit's Verilog for the engine's template: its declarations, its states, the store beat each of its states reads
3816/// (case items of the store's read address) and its writes into the store (branches of the store's write port).
3817#[derive(Default)]
3818struct UnitVerilog {
3819    decls: String,
3820    cases: String,
3821    reads: String,
3822    writes: String,
3823}
3824
3825fn delta_verilog(hd: usize, t_max: usize) -> UnitVerilog {
3826    let w = |op: Op| format!("64'h{:016x}", super::simd::encode(&op));
3827    let fold = [
3828        Op::Dot { a: 2, b: 0, d: 3 },
3829        Op::Scalar { op: Bin::Mul, a: 2, b: 3, d: 3 },
3830        Op::VecScalar { op: Bin::Mul, a: 3, s: 3, d: 4 },
3831        Op::Vec { op: Bin::Add, a: 1, b: 4, d: 1 },
3832        Op::VecScalar { op: Bin::Mul, a: 2, s: 3, d: 5 },
3833        Op::Vec { op: Bin::Sub, a: 0, b: 5, d: 6 },
3834        Op::VecScalar { op: Bin::Mul, a: 6, s: 1, d: 0 },
3835    ];
3836    let mut fold_case = String::new();
3837    for (i, op) in fold.iter().enumerate() {
3838        writeln!(fold_case, "        3'd{i}: dl_word = {};", w(*op)).unwrap();
3839    }
3840    let in_values = (0..t_max).rev().map(|e| format!("dl_vals[{e}][32*dr +: 32]")).collect::<Vec<_>>().join(", ");
3841    let decls = format!(
3842        r#"  // The recurrence unit: the job's slots, its block's place in a region, the head at hand and its log.
3843  localparam HD = {hd}, TMAX = {t_max};
3844  reg [4:0] dl_qs, dl_ks, dl_vs, dl_lds, dl_dcs, dl_gts, dl_tos;
3845  reg [19:0] dl_vat;
3846  wire [511:0] dlb = bw;
3847  wire [31:0] dl_mem = dlb[127:96], dl_log = dlb[159:128], dl_entry = dlb[191:160];
3848  wire [31:0] dl_nk = dlb[223:192], dl_nv = dlb[255:224], dl_hd = dlb[287:256];
3849  wire [31:0] dl_fval = dlb[319:288], dl_fdec = dlb[351:320], dl_fgate = dlb[383:352], dl_T = dlb[415:384], dl_scale = dlb[447:416];
3850  wire [31:0] dl_beats = dl_hd >> 4;
3851  reg [31:0] dg, dr, dj, de, dl_count, dob;
3852  reg [1:0] dfld;
3853  reg [2:0] dl_ii;
3854  reg dl_flush;
3855  reg [32*HD-1:0] dl_kv, dl_qv, dl_y, dl_c;
3856  `LOGOS_RAMSTYLE_ASYNC(TMAX) reg [32*HD-1:0] dl_vals [0:TMAX-1];
3857  reg [31:0] dl_kq, dl_decay, dl_beta, dl_dec_e, dl_beta_e;
3858  reg [3:0] dl_rd_reg, dl_rd_s;
3859  reg [15:0] dl_rd_row;
3860  // The vector unit's read port is the fold's while its folded query and carried values are read out: a function of
3861  // the state, so no power-up value of a register of its own can hand it to the fold outside those states.
3862  wire dl_rd = state == DL_FY || state == DL_FC;
3863  // A slot's beat holding element `e` of the row at hand; the element is the beat's lane `e mod 16`.
3864  function [31:0] element_beat(input [63:0] slot, input [31:0] e);
3865    element_beat = slot[31:0] + {{16'd0, k}} * slot[63:32] + (e >> 4);
3866  endfunction
3867  wire [31:0] dl_v_e = {{12'd0, dl_vat}} + h * dl_hd + dr;
3868  wire [31:0] dl_v_elem = st_rd[32 * dl_v_e[3:0] +: 32];
3869  wire [31:0] dl_h_elem = st_rd[32 * h[3:0] +: 32];
3870  // A head's key or query beat: the group's head of the row at hand.
3871  wire [31:0] dl_ks_beat = slott[dl_ks][31:0] + {{16'd0, k}} * slott[dl_ks][63:32] + ((dg * dl_hd) >> 4) + dj;
3872  wire [31:0] dl_qs_beat = slott[dl_qs][31:0] + {{16'd0, k}} * slott[dl_qs][63:32] + ((dg * dl_hd) >> 4) + dj;
3873  // The state row at hand, and the line of its beat `dj`.
3874  wire [63:0] dl_row_byte = {{32'd0, region}} * 64 + dl_mem + ({{32'd0, h}} * dl_hd + dr) * dl_hd * st_bytes;
3875  wire [63:0] dl_beat_byte = dl_row_byte + dj * 16 * st_bytes;
3876  wire [63:0] dl_out_byte = dl_row_byte + dob * 16 * st_bytes;
3877  // The log: its count's line, the entry at hand, a field's beat.
3878  wire [63:0] dl_log_byte = {{32'd0, region}} * 64 + dl_log;
3879  wire [63:0] dl_entry_byte = dl_log_byte + 64 + de * dl_entry;
3880  reg [31:0] dl_src_slot_e, dl_dst_e, dl_len;
3881  reg [4:0] dl_src_slot;
3882  always @* begin
3883    case (dfld)
3884      2'd0: begin dl_src_slot = dl_ks; dl_src_slot_e = 32'd0; dl_dst_e = 32'd0; dl_len = dl_nk * dl_hd; end
3885      2'd1: begin dl_src_slot = dl_vs; dl_src_slot_e = {{12'd0, dl_vat}}; dl_dst_e = dl_fval; dl_len = dl_nv * dl_hd; end
3886      2'd2: begin dl_src_slot = dl_lds; dl_src_slot_e = 32'd0; dl_dst_e = dl_fdec; dl_len = dl_nv; end
3887      default: begin dl_src_slot = dl_gts; dl_src_slot_e = 32'd0; dl_dst_e = dl_fgate; dl_len = dl_nv; end
3888    endcase
3889  end
3890  wire [63:0] dl_app_byte = dl_log_byte + 64 + dl_count * dl_entry + (dl_dst_e + 16 * dj) * st_bytes;
3891  wire [31:0] dl_app_lanes = (dl_len - 16 * dj >= 32'd16) ? 32'd16 : dl_len - 16 * dj;
3892  wire [15:0] dl_app_mask = (dl_app_lanes >= 32'd16) ? 16'hffff : ((16'd1 << dl_app_lanes[4:0]) - 16'd1);
3893  wire [31:0] dl_app_src = slott[dl_src_slot][31:0] + {{16'd0, k}} * slott[dl_src_slot][63:32] + (dl_src_slot_e >> 4) + dj;
3894  wire [511:0] dl_app_beat = st_rd;
3895  // A state beat as the engine reads it: f32, or the line's half of binary16 widened.
3896  wire [511:0] dl_widened;
3897  wire [255:0] dl_halves, dl_app_halves;
3898  wire [511:0] dl_out_beat_data;
3899  genvar dgv;
3900  generate
3901    for (dgv = 0; dgv < 16; dgv = dgv + 1) begin : dl_lanes
3902      logos_f16_to_f32 wv(.h(dl_beat_byte[5] ? mem_rd_data[256 + 16*dgv +: 16] : mem_rd_data[16*dgv +: 16]), .x(dl_widened[32*dgv +: 32]));
3903      logos_f32_to_f16 nv(.x(dl_out_beat_data[32*dgv +: 32]), .h(dl_halves[16*dgv +: 16]));
3904      logos_f32_to_f16 na(.x(dl_app_beat[32*dgv +: 32]), .h(dl_app_halves[16*dgv +: 16]));
3905    end
3906  endgenerate
3907  // An entry's key or value beat at hand during the fold, and its line's half widened.
3908  wire [63:0] dl_fk_byte = dl_entry_byte + (dg * dl_hd + 16 * dj) * st_bytes;
3909  wire [63:0] dl_fv_byte = dl_entry_byte + (dl_fval + h * dl_hd + 16 * dj) * st_bytes;
3910  wire [63:0] dl_fbyte = (state == DL_FV || state == DL_FVW) ? dl_fv_byte : dl_fk_byte;
3911  wire [511:0] dl_fwide;
3912  generate
3913    for (dgv = 0; dgv < 16; dgv = dgv + 1) begin : dl_flanes
3914      logos_f16_to_f32 wf(.h(dl_fbyte[5] ? mem_rd_data[256 + 16*dgv +: 16] : mem_rd_data[16*dgv +: 16]), .x(dl_fwide[32*dgv +: 32]));
3915    end
3916  endgenerate
3917  // One element of a log entry's field, from its line: f32, or binary16 widened.
3918  wire [63:0] dl_elem_byte = dl_entry_byte + ((dfld == 2'd2 ? dl_fdec : dl_fgate) + h) * st_bytes;
3919  wire [31:0] dl_elem_f16;
3920  logos_f16_to_f32 we(.h(mem_rd_data[8 * dl_elem_byte[5:0] +: 16]), .x(dl_elem_f16));
3921  wire [31:0] dl_elem = state_f16 ? dl_elem_f16 : mem_rd_data[8 * dl_elem_byte[5:0] +: 32];
3922  // The row engines: every token's update, and a lazily flushed memory's read-out and replay.
3923  reg dre_v, dre_first, dlr_v, dlr_first, dlr_log_wr;
3924  reg [511:0] dre_data;
3925  wire dre_ov, dre_ol, dlr_ov, dlr_ol, dlr_busy;
3926  wire [511:0] dre_od, dlr_od;
3927  wire [31:0] dre_or, dlr_or;
3928  logos_gdn_row_engine #(.N(HD)) dre(.clk(clk), .rst(rst), .k(dl_kv), .q(dl_qv), .decay(dl_decay), .beta(dl_beta), .kq(dl_kq),
3929    .scale(dl_scale), .f16(state_f16), .in_valid(dre_v), .in_first(dre_first), .in_data(dre_data), .in_v(dl_v_elem),
3930    .out_valid(dre_ov), .out_last(dre_ol), .out_data(dre_od), .out_read(dre_or));
3931  logos_gdn_lazy_row #(.N(HD), .T_MAX(TMAX)) dlr(.clk(clk), .rst(rst), .y(dl_y), .scale(dl_scale), .f16(state_f16),
3932    .entries(dl_count[7:0]), .flush(dl_flush), .log_wr(dlr_log_wr), .log_at(de[7:0]), .log_k(dl_kv), .log_decay(dl_dec_e),
3933    .log_beta(dl_beta_e), .in_valid(dlr_v), .in_first(dlr_first), .in_data(dre_data), .in_carried(dl_c[32*dr +: 32]),
3934    .in_values({{{in_values}}}), .out_valid(dlr_ov), .out_last(dlr_ol), .out_data(dlr_od), .out_read(dlr_or), .busy(dlr_busy));
3935  wire dl_lazy = dl_T != 32'd0;
3936  wire dl_out_v = dl_lazy ? dlr_ov : dre_ov, dl_out_l = dl_lazy ? dlr_ol : dre_ol;
3937  assign dl_out_beat_data = dl_lazy ? dlr_od : dre_od;
3938  wire [31:0] dl_out_read = dl_lazy ? dlr_or : dre_or;
3939  // A fold's instruction.
3940  reg [63:0] dl_word;
3941  always @* begin
3942    case (dl_ii)
3943{fold_case}        default: dl_word = 64'd0;
3944    endcase
3945  end
3946  // The read-out's element of the job's output.
3947  wire [63:0] dl_to_slot = slott[dl_tos];
3948  wire [31:0] dl_to_e = h * dl_hd + dr;
3949  wire [31:0] dl_to_beat = dl_to_slot[31:0] + {{16'd0, k}} * dl_to_slot[63:32] + (dl_to_e >> 4);
3950"#
3951    );
3952    let cases = format!(
3953        r#"      DL_HDR: begin
3954        dl_qs <= jw[4:0]; dl_ks <= jw[9:5]; dl_vs <= jw[14:10]; dl_lds <= jw[19:15];
3955        dl_dcs <= jw[24:20]; dl_gts <= jw[29:25]; dl_tos <= jw[34:30]; dl_vat <= jw[54:35];
3956        pc <= pc + 32'd1; k <= 16'd0; state <= DL_ROW;
3957      end
3958      DL_ROW: begin
3959        if (k == rows) state <= next_job;
3960        else begin h <= 32'd0; dg <= 32'd0; dj <= 32'd0; state <= dl_lazy ? DL_CNT : DL_KQ_K; end
3961      end
3962      // Every token's update: the head's k·q on the vector unit, then each value row through the row engine.
3963      DL_KQ_K: begin
3964        if (dj == dl_beats) begin dj <= 32'd0; state <= DL_KQ_Q; end
3965        else begin
3966          s_wr <= 1'b1; s_wr_reg <= 4'd0; s_wr_row <= dj[15:0];
3967          s_wr_data <= st_rd;
3968          dl_kv[512 * dj +: 512] <= st_rd;
3969          dj <= dj + 32'd1;
3970        end
3971      end
3972      DL_KQ_Q: begin
3973        if (dj == dl_beats) begin dj <= 32'd0; state <= DL_KQ_L0; end
3974        else begin
3975          s_wr <= 1'b1; s_wr_reg <= 4'd1; s_wr_row <= dj[15:0];
3976          s_wr_data <= st_rd;
3977          dl_qv[512 * dj +: 512] <= st_rd;
3978          dj <= dj + 32'd1;
3979        end
3980      end
3981      DL_KQ_L0: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd0; s_len_val <= dl_hd[19:0]; state <= DL_KQ_L1; end
3982      DL_KQ_L1: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd1; s_len_val <= dl_hd[19:0]; state <= DL_KQ_DOT; end
3983      DL_KQ_DOT: begin s_issue <= 1'b1; s_instr <= {dot}; state <= DL_KQ_GAP; end
3984      DL_KQ_GAP: state <= DL_KQ_WAIT;
3985      DL_KQ_WAIT: if (!s_busy) begin
3986        dl_kq <= s_rd_s_val; dl_decay <= dl_h_elem; state <= DL_KQ_BETA;
3987      end
3988      DL_KQ_BETA: begin dl_beta <= dl_h_elem; dr <= 32'd0; dj <= 32'd0; state <= DL_E_RD; end
3989      DL_E_RD: begin
3990        if (dj == dl_beats) begin dob <= 32'd0; state <= DL_E_OUT; end
3991        else begin rq_v <= 1'b1; rq_line <= dl_beat_byte[37:6]; state <= DL_E_RW; end
3992      end
3993      DL_E_RW: if (mem_rd_valid) begin
3994        dre_v <= 1'b1; dre_first <= (dj == 32'd0); dre_data <= state_f16 ? dl_widened : mem_rd_data;
3995        dj <= dj + 32'd1; state <= DL_E_RD;
3996      end
3997      DL_E_OUT, DL_L_OUT: begin
3998        if (dl_out_v && !(dl_lazy && !dl_flush)) begin
3999          // An updated beat back where it was read, as the state stores it.
4000          wq_v <= 1'b1; wq_line <= dl_out_byte[37:6];
4001          wq_data <= 512'd0; wq_mask <= 64'd0;
4002          if (state_f16) begin
4003            wq_data <= {{256'd0, dl_halves}} << (8 * dl_out_byte[5:0]);
4004            wq_mask <= {{32'd0, 32'hffff_ffff}} << dl_out_byte[5:0];
4005          end else begin wq_data <= dl_out_beat_data; wq_mask <= 64'hffff_ffff_ffff_ffff; end
4006          dob <= dob + 32'd1;
4007        end
4008        if (dl_out_l) begin
4009          dj <= 32'd0;
4010          if (dr + 32'd1 == dl_hd) begin
4011            dr <= 32'd0;
4012            if (h + 32'd1 == dl_nv) begin
4013              if (dl_lazy) state <= DL_CNT_WR;
4014              else begin k <= k + 16'd1; state <= DL_ROW; end
4015            end else begin
4016              h <= h + 32'd1; dg <= (dg + 32'd1 == dl_nk) ? 32'd0 : dg + 32'd1;
4017              state <= dl_lazy ? DL_FQ : DL_KQ_K;
4018            end
4019          end else begin dr <= dr + 32'd1; state <= dl_lazy ? DL_L_RD : DL_E_RD; end
4020        end
4021      end
4022      // A lazily flushed memory: the row's token logged after the count, the query folded back through the log
4023      // for each head, each value row read through the folded query and, once the log is full, replayed and stored.
4024      DL_CNT: begin rq_v <= 1'b1; rq_line <= dl_log_byte[37:6]; state <= DL_CNT_W; end
4025      DL_CNT_W: if (mem_rd_valid) begin dl_count <= mem_rd_data[31:0]; dfld <= 2'd0; dj <= 32'd0; state <= DL_APP; end
4026      DL_APP: begin
4027        if (16 * dj >= dl_len) begin
4028          dj <= 32'd0;
4029          if (dfld == 2'd3) begin
4030            dl_count <= dl_count + 32'd1; dl_flush <= (dl_count + 32'd1 >= dl_T); h <= 32'd0; dg <= 32'd0; state <= DL_FQ;
4031          end else dfld <= dfld + 2'd1;
4032        end else if (wq_room) begin
4033          wq_v <= 1'b1; wq_line <= dl_app_byte[37:6];
4034          wq_data <= 512'd0; wq_mask <= 64'd0;
4035          if (state_f16) begin
4036            wq_data <= {{256'd0, dl_app_halves}} << (8 * dl_app_byte[5:0]);
4037            for (i = 0; i < 16; i = i + 1) if (dl_app_mask[i]) wq_mask[dl_app_byte[5:0] + 2*i +: 2] <= 2'b11;
4038          end else begin
4039            wq_data <= dl_app_beat;
4040            for (i = 0; i < 16; i = i + 1) if (dl_app_mask[i]) wq_mask[4*i +: 4] <= 4'hf;
4041          end
4042          dj <= dj + 32'd1;
4043        end
4044      end
4045      DL_FQ: begin
4046        // The fold starts from the head's group's query and no values carried.
4047        if (dj == dl_beats) begin dj <= 32'd0; state <= DL_FZ; end
4048        else begin
4049          s_wr <= 1'b1; s_wr_reg <= 4'd0; s_wr_row <= dj[15:0];
4050          s_wr_data <= st_rd;
4051          dj <= dj + 32'd1;
4052        end
4053      end
4054      DL_FZ: begin
4055        if (dj == dl_beats) begin dj <= 32'd0; state <= DL_FL0; end
4056        else begin s_wr <= 1'b1; s_wr_reg <= 4'd1; s_wr_row <= dj[15:0]; s_wr_data <= 512'd0; dj <= dj + 32'd1; end
4057      end
4058      DL_FL0: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd0; s_len_val <= dl_hd[19:0]; state <= DL_FL1; end
4059      DL_FL1: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd1; s_len_val <= dl_hd[19:0]; de <= dl_count - 32'd1; dj <= 32'd0; state <= DL_FK; end
4060      DL_FK: begin
4061        if (dj == dl_beats) begin dj <= 32'd0; state <= DL_FV; end
4062        else begin rq_v <= 1'b1; rq_line <= dl_fk_byte[37:6]; state <= DL_FKW; end
4063      end
4064      DL_FKW: if (mem_rd_valid) begin
4065        s_wr <= 1'b1; s_wr_reg <= 4'd2; s_wr_row <= dj[15:0];
4066        s_wr_data <= state_f16 ? dl_fwide : mem_rd_data; dl_kv[512 * dj +: 512] <= state_f16 ? dl_fwide : mem_rd_data;
4067        dj <= dj + 32'd1; state <= DL_FK;
4068      end
4069      DL_FV: begin
4070        if (dj == dl_beats) begin dj <= 32'd0; dfld <= 2'd2; state <= DL_FDEC; end
4071        else begin rq_v <= 1'b1; rq_line <= dl_fv_byte[37:6]; state <= DL_FVW; end
4072      end
4073      DL_FVW: if (mem_rd_valid) begin
4074        s_wr <= 1'b1; s_wr_reg <= 4'd3; s_wr_row <= dj[15:0];
4075        s_wr_data <= state_f16 ? dl_fwide : mem_rd_data; dl_vals[de][512 * dj +: 512] <= state_f16 ? dl_fwide : mem_rd_data;
4076        dj <= dj + 32'd1; state <= DL_FV;
4077      end
4078      DL_FDEC: begin rq_v <= 1'b1; rq_line <= dl_elem_byte[37:6]; state <= DL_FDECW; end
4079      DL_FDECW: if (mem_rd_valid) begin
4080        // The decay exponent as a constant, its exp the entry's decay; then the gate the same way.
4081        s_issue <= 1'b1; s_instr <= {{dl_elem, 32'h0001_0008}}; state <= DL_FDEC_GAP;
4082      end
4083      DL_FDEC_GAP: state <= DL_FDEC_WAIT;
4084      DL_FDEC_WAIT: if (!s_busy) begin s_issue <= 1'b1; s_instr <= {exp}; state <= DL_FEXP_GAP; end
4085      DL_FEXP_GAP: state <= DL_FEXP_WAIT;
4086      DL_FEXP_WAIT: if (!s_busy) begin dl_dec_e <= s_rd_s_val; dfld <= 2'd3; state <= DL_FBETA; end
4087      DL_FBETA: begin rq_v <= 1'b1; rq_line <= dl_elem_byte[37:6]; state <= DL_FBETAW; end
4088      DL_FBETAW: if (mem_rd_valid) begin
4089        dl_beta_e <= dl_elem; s_issue <= 1'b1; s_instr <= {{dl_elem, 32'h0002_0008}}; state <= DL_FB_GAP;
4090      end
4091      DL_FB_GAP: state <= DL_FB_WAIT;
4092      DL_FB_WAIT: if (!s_busy) begin s_len_wr <= 1'b1; s_wr_reg <= 4'd2; s_len_val <= dl_hd[19:0]; state <= DL_FL3; end
4093      DL_FL3: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd3; s_len_val <= dl_hd[19:0]; dl_ii <= 3'd0; state <= DL_FRUN; end
4094      DL_FRUN: begin
4095        if (dl_ii == 3'd7) begin dlr_log_wr <= 1'b1; state <= DL_FLOG; end
4096        else begin s_issue <= 1'b1; s_instr <= dl_word; dl_ii <= dl_ii + 3'd1; state <= DL_FR_GAP; end
4097      end
4098      DL_FR_GAP: state <= DL_FR_WAIT;
4099      DL_FR_WAIT: if (!s_busy) state <= DL_FRUN;
4100      DL_FLOG: begin
4101        if (de == 32'd0) begin dj <= 32'd0; dl_rd_reg <= 4'd0; dl_rd_row <= 16'd0; state <= DL_FY; end
4102        else begin de <= de - 32'd1; dj <= 32'd0; state <= DL_FK; end
4103      end
4104      DL_FY: begin
4105        // The folded query and the values carried, read out of the vector unit a beat a cycle.
4106        if (dj == dl_beats) begin dj <= 32'd0; dl_rd_reg <= 4'd1; dl_rd_row <= 16'd0; state <= DL_FC; end
4107        else begin dl_y[512 * dj +: 512] <= s_rd_data; dj <= dj + 32'd1; dl_rd_row <= dl_rd_row + 16'd1; end
4108      end
4109      DL_FC: begin
4110        if (dj == dl_beats) begin dj <= 32'd0; dr <= 32'd0; state <= DL_L_RD; end
4111        else begin dl_c[512 * dj +: 512] <= s_rd_data; dj <= dj + 32'd1; dl_rd_row <= dl_rd_row + 16'd1; end
4112      end
4113      DL_L_RD: begin
4114        if (dj == dl_beats) begin dob <= 32'd0; state <= DL_L_OUT; end
4115        else begin rq_v <= 1'b1; rq_line <= dl_beat_byte[37:6]; state <= DL_L_RW; end
4116      end
4117      DL_L_RW: if (mem_rd_valid) begin
4118        dlr_v <= 1'b1; dlr_first <= (dj == 32'd0); dre_data <= state_f16 ? dl_widened : mem_rd_data;
4119        dj <= dj + 32'd1; state <= DL_L_RD;
4120      end
4121      DL_CNT_WR: if (wq_room) begin
4122        wq_v <= 1'b1; wq_line <= dl_log_byte[37:6];
4123        wq_data <= {{480'd0, dl_flush ? 32'd0 : dl_count}}; wq_mask <= 64'h0000_0000_0000_000f;
4124        k <= k + 16'd1; state <= DL_ROW;
4125      end
4126"#,
4127        dot = w(Op::Dot { a: 0, b: 1, d: 0 }),
4128        exp = w(Op::ScalarMap { f: Func::Exp, a: 1, d: 1 }),
4129    );
4130    let reads = r#"      DL_KQ_K: st_ra = dl_ks_beat;
4131      DL_KQ_Q, DL_FQ: st_ra = dl_qs_beat;
4132      DL_KQ_WAIT: st_ra = element_beat(slott[dl_dcs], h);
4133      DL_KQ_BETA: st_ra = element_beat(slott[dl_gts], h);
4134      DL_E_RD, DL_E_RW, DL_E_OUT: st_ra = element_beat(slott[dl_vs], dl_v_e);
4135      DL_APP: st_ra = dl_app_src;
4136"#
4137    .to_string();
4138    let writes = r#"    else if (!rst && (state == DL_E_OUT || state == DL_L_OUT) && dl_out_l) begin
4139      st_we = 1'b1; st_wa = dl_to_beat; st_wd[32 * dl_to_e[3:0] +: 32] = dl_out_read; st_wm[4 * dl_to_e[3:0] +: 4] = 4'hf;
4140    end
4141"#
4142    .to_string();
4143    UnitVerilog { decls, cases, reads, writes }
4144}
4145
4146/// The attention unit for query heads of `hd` elements and value heads of `vd` over up to `p_max` positions, its math
4147/// on the array over an int8 cache when `array`, on the vector units otherwise: its declarations and its states, each
4148/// a Verilog fragment the engine's template takes. A query head's cached rows stream from the row's sequence's region
4149/// as the unit asks for them: binary16 elements widened, or an int8 row's codes and its scale — the codes times the
4150/// scale for the vector units, as they are for the array.
4151fn attend_unit_verilog(hd: usize, vd: usize, p_max: usize, array: bool) -> UnitVerilog {
4152    let unit = if array {
4153        format!(
4154            r#"  logos_attend_int8 #(.HD(AH), .VD(AV), .P_MAX(AP)) atu(.clk(clk), .rst(rst), .start(at_start), .positions(at_n[15:0]),
4155    .q_codes(aq_codes[16*AH-1:0]), .q_m(aq_m), .q_e(aq_e), .scale(at_scale), .busy(at_busy),
4156    .k_valid(at_kv), .k_codes(at_krow[8*AH-1:0]), .k_scale(at_kscale), .k_ready(at_kr),
4157    .s_valid(at_sv), .v_scale(at_vscale), .s_ready(at_sr),
4158    .v_valid(at_vv), .v_codes(at_vrow[8*AV-1:0]), .v_ready(at_vr),
4159    .o_valid(at_ov), .o_beat(at_ob), .o_last(at_ol));"#
4160        )
4161    } else {
4162        r#"  assign at_sr = 1'b0;
4163  logos_attend_vector #(.HD(AH), .VD(AV), .P_MAX(AP)) atu(.clk(clk), .rst(rst), .start(at_start), .positions(at_n[15:0]),
4164    .q(at_q), .scale(at_scale), .busy(at_busy),
4165    .k_valid(at_kv), .k_beat(at_beat), .k_ready(at_kr),
4166    .v_valid(at_vv), .v_beat(at_beat), .v_ready(at_vr),
4167    .o_valid(at_ov), .o_beat(at_ob), .o_last(at_ol));"#
4168            .to_string()
4169    };
4170    let decls = format!(
4171        r#"  // The attention unit: the job's slots, its block's cache, the row and head at hand, and the cache rows it streams.
4172  localparam AH = {hd}, AV = {vd}, AP = {p_max};
4173  // A head's beats, sixteen elements a beat, and the lines its int8 codes take.
4174  localparam [31:0] AH_BEATS = AH / 16, AV_BEATS = AV / 16, AH_LINES = (AH + 63) / 64, AV_LINES = (AV + 63) / 64;
4175  reg [4:0] at_qs, at_tos;
4176  reg [15:0] at_heads;
4177  reg [31:0] at_scale, at_g, at_n, at_kj, at_kb, at_sj, at_vj, at_vb, at_ob_i, at_lj;
4178  reg [1:0] at_what;
4179  reg [32*AH-1:0] at_q;
4180  reg at_start, at_kv, at_sv, at_vv;
4181  reg [511:0] at_line, at_beat;
4182  reg [8*AH-1:0] at_krow;
4183  reg [8*AV-1:0] at_vrow;
4184  reg [15:0] at_kscale, at_vscale, at_rowscale;
4185  wire at_busy, at_kr, at_sr, at_vr, at_ov, at_ol;
4186  wire [511:0] at_ob;
4187  wire [511:0] atb = bw;
4188  wire [31:0] at_keys = atb[31:0], at_vals = atb[63:32], at_kscales = atb[95:64], at_vscales = atb[127:96];
4189  wire [31:0] at_kvh = atb[159:128];
4190  // A cached head row's first byte, keys or values, and its scale's: position j's row of the cached head g.
4191  wire [31:0] at_kw = cache_int8 ? AH : 2 * AH, at_vw = cache_int8 ? AV : 2 * AV;
4192  wire [63:0] at_kbyte = {{32'd0, region}} * 64 + at_keys + ({{32'd0, at_kj}} * at_kvh + at_g) * at_kw;
4193  wire [63:0] at_vbyte = {{32'd0, region}} * 64 + at_vals + ({{32'd0, at_vj}} * at_kvh + at_g) * at_vw;
4194  wire [63:0] at_ksbyte = {{32'd0, region}} * 64 + at_kscales + ({{32'd0, at_kj}} * at_kvh + at_g) * 2;
4195  wire [63:0] at_vsbyte = {{32'd0, region}} * 64 + at_vscales + ({{32'd0, at_what == 2'd1 ? at_sj : at_vj}} * at_kvh + at_g) * 2;
4196  // The vector units' beat of a fetched line: its binary16 half widened, or sixteen int8 codes times their row's scale.
4197  wire [63:0] at_beat_byte = at_what == 2'd0 ? at_kbyte + at_kb * (cache_int8 ? 16 : 32) : at_vbyte + at_vb * (cache_int8 ? 16 : 32);
4198  wire [511:0] at_wide, at_deq;
4199  wire [31:0] at_scale_f;
4200  logos_f16_to_f32 asf(.h(at_rowscale), .x(at_scale_f));
4201  genvar agv;
4202  generate
4203    for (agv = 0; agv < 16; agv = agv + 1) begin : at_lanes
4204      logos_f16_to_f32 aw(.h(at_beat_byte[5] ? at_line[256 + 16*agv +: 16] : at_line[16*agv +: 16]), .x(at_wide[32*agv +: 32]));
4205      wire [7:0] code = at_line[8 * ({{2'd0, at_beat_byte[5:4]}} * 16 + agv) +: 8];
4206      wire [31:0] cf;
4207      logos_i2f ai(.n({{{{24{{code[7]}}}}, code}}), .y(cf));
4208      logos_fmul am(.a(cf), .b(at_scale_f), .y(at_deq[32*agv +: 32]));
4209    end
4210  endgenerate
4211  // The head's output beat in the job's output slot.
4212  wire [63:0] at_to_slot = slott[at_tos];
4213  wire [31:0] at_to_beat = at_to_slot[31:0] + {{16'd0, k}} * at_to_slot[63:32] + ((h * AV) >> 4) + at_ob_i;
4214{unit}
4215"#
4216    );
4217    let common = r#"      AT_HDR: begin
4218        at_qs <= jw[4:0]; at_tos <= jw[9:5]; at_heads <= jw[25:10]; at_scale <= jw[57:26];
4219        pc <= pc + 32'd1; k <= 16'd0; state <= AT_ROW;
4220      end
4221      AT_ROW: begin
4222        if (k == rows) state <= next_job;
4223        else begin h <= 32'd0; state <= AT_HEAD; end
4224      end
4225      AT_HEAD: begin
4226        // The head's cached head, and its row's positions to its own.
4227        at_g <= h / ({16'd0, at_heads} / at_kvh); at_n <= position + 32'd1; at_kj <= 32'd0; at_kb <= 32'd0; at_sj <= 32'd0;
4228        at_vj <= 32'd0; at_vb <= 32'd0; at_ob_i <= 32'd0; at_lj <= 32'd0; state <= AT_Q;
4229      end
4230      AT_Q: begin
4231        // The head's query, into the unit and the activation quantizer.
4232        if (at_lj == AH_BEATS) begin at_lj <= 32'd0; state <= AT_Q_GAP; end
4233        else begin
4234          at_q[512 * at_lj +: 512] <= st_rd;
4235          at_lj <= at_lj + 32'd1;
4236        end
4237      end
4238      AT_Q_GAP: if (!aq_busy) begin at_start <= 1'b1; state <= AT_RUN; end
4239      AT_TAKEN: begin
4240        // The unit took the item on the edge it was offered: the stream moves to its next position, or beat.
4241        if (at_kv) begin
4242          at_kv <= 1'b0;
4243          if (AT_BEATS && at_kb + 32'd1 != AH_BEATS) at_kb <= at_kb + 32'd1; else begin at_kb <= 32'd0; at_kj <= at_kj + 32'd1; end
4244        end
4245        if (at_sv) begin at_sv <= 1'b0; at_sj <= at_sj + 32'd1; end
4246        if (at_vv) begin
4247          at_vv <= 1'b0;
4248          if (AT_BEATS && at_vb + 32'd1 != AV_BEATS) at_vb <= at_vb + 32'd1; else begin at_vb <= 32'd0; at_vj <= at_vj + 32'd1; end
4249        end
4250        state <= AT_RUN;
4251      end
4252"#;
4253    let run = r#"      AT_RUN: begin
4254        // Serve the unit whatever it is ready for, each stream its next position; its output beats into the store.
4255        if (at_ov) at_ob_i <= at_ob_i + 32'd1;
4256        if (at_ol) begin
4257          if (h + 32'd1 == {16'd0, at_heads}) begin k <= k + 16'd1; state <= AT_ROW; end
4258          else begin h <= h + 32'd1; state <= AT_HEAD; end
4259        end
4260        else if (at_kr) begin at_what <= 2'd0; at_lj <= 32'd0; state <= AT_FETCH; end
4261        else if (at_sr) begin at_what <= 2'd1; state <= AT_SCALE; end
4262        else if (at_vr) begin at_what <= 2'd2; at_lj <= 32'd0; state <= AT_FETCH; end
4263      end
4264      AT_SCALE: begin rq_v <= 1'b1; rq_line <= at_vsbyte[37:6]; state <= AT_SCALE_W; end
4265      AT_SCALE_W: if (mem_rd_valid) begin at_vscale <= mem_rd_data[8 * at_vsbyte[5:0] +: 16]; at_sv <= 1'b1; state <= AT_TAKEN; end
4266"#;
4267    let fetch = if array {
4268        r#"      AT_FETCH: begin
4269        // A row's codes, a line at a time; then a key row's scale.
4270        if (at_lj == (at_what == 2'd0 ? AH_LINES : AV_LINES)) begin
4271          if (at_what == 2'd0) begin rq_v <= 1'b1; rq_line <= at_ksbyte[37:6]; state <= AT_KS; end
4272          else begin at_vv <= 1'b1; state <= AT_TAKEN; end
4273        end else begin
4274          rq_v <= 1'b1; rq_line <= (at_what == 2'd0 ? at_kbyte[37:6] : at_vbyte[37:6]) + at_lj; state <= AT_FETCH_W;
4275        end
4276      end
4277      AT_FETCH_W: if (mem_rd_valid) begin
4278        if (at_what == 2'd0) at_krow[512 * at_lj +: 512] <= mem_rd_data; else at_vrow[512 * at_lj +: 512] <= mem_rd_data;
4279        at_lj <= at_lj + 32'd1; state <= AT_FETCH;
4280      end
4281      AT_KS: if (mem_rd_valid) begin at_kscale <= mem_rd_data[8 * at_ksbyte[5:0] +: 16]; at_kv <= 1'b1; state <= AT_TAKEN; end
4282"#
4283    } else {
4284        r#"      AT_FETCH: begin rq_v <= 1'b1; rq_line <= at_beat_byte[37:6]; state <= AT_FETCH_W; end
4285      AT_FETCH_W: if (mem_rd_valid) begin
4286        at_line <= mem_rd_data;
4287        if (cache_int8) begin
4288          rq_v <= 1'b1; rq_line <= (at_what == 2'd0 ? at_ksbyte[37:6] : at_vsbyte[37:6]); state <= AT_KS;
4289        end else state <= AT_GIVE;
4290      end
4291      AT_KS: if (mem_rd_valid) begin
4292        at_rowscale <= mem_rd_data[8 * (at_what == 2'd0 ? at_ksbyte[5:0] : at_vsbyte[5:0]) +: 16]; state <= AT_GIVE;
4293      end
4294      AT_GIVE: begin
4295        at_beat <= cache_int8 ? at_deq : at_wide;
4296        if (at_what == 2'd0) at_kv <= 1'b1; else at_vv <= 1'b1;
4297        state <= AT_TAKEN;
4298      end
4299"#
4300    };
4301    let cases = format!("{common}{run}{fetch}").replace("AT_BEATS", if array { "1'b0" } else { "1'b1" });
4302    let reads = "      AT_Q: st_ra = slott[at_qs][31:0] + {16'd0, k} * slott[at_qs][63:32] + ((h * AH) >> 4) + at_lj;\n".to_string();
4303    let writes = r#"    else if (!rst && state == AT_RUN && at_ov) begin
4304      st_we = 1'b1; st_wa = at_to_beat; st_wd = at_ob; st_wm = {64{1'b1}};
4305    end
4306"#
4307    .to_string();
4308    UnitVerilog { decls, cases, reads, writes }
4309}