Skip to main content

logicaffeine_compile/spatial/decode_engine/
ofs.rs

1//! The decode engine as an accelerator function of the Open FPGA Stack on the Intel PAC D5005.
2//!
3//! [`afu_sources`] writes the files an OFS partial-reconfiguration build of the card's FIM takes
4//! (`afu_synth_setup -s hw/rtl/sources.txt <build dir>`, then `afu_synth`):
5//!   • `ofs_plat_afu`, the boundary with the Platform Interface Manager: host channel 0 as Avalon-MM MMIO and host
6//!     memory in pClk, every local-memory bank as Avalon-MM in its own EMIF clock (its ECC lanes carried as data at
7//!     [`LocalMemoryLine::DataAndEcc576`], holding each line's SECDED(72,64) check bytes at
8//!     [`LocalMemoryLine::Data512Secded`]), the compute clock the user clock the AFU JSON asks for;
9//!   • `logos_d5005_shell`, every platform port flat in its own clock, the only paths between clocks the dual-clock
10//!     FIFOs `logos_dcfifo_mlab` and `logos_dcfifo_m20k` (Gray pointers through two-register synchronizers);
11//!   • the stream engines (a reader and a writer on each memory channel's one port, and on the host memory port's
12//!     two; on SECDED lines the line coder at each bank's port and the decoder in one registered stage behind it,
13//!     `logos_secded.v` from [`secded_verilog`], the code [`secded_line`] and [`secded_read`] define), the registers
14//!     ([`REGISTERS`]: the OFS feature header at word 0, the AFU ID, a command doorbell, the last status record), and
15//!     the bring-up core ([`Op`]): fills and checks of every channel at full rate, copies between host memory and a
16//!     channel, and on SECDED lines flips injected into the lines written and the words corrected and lost, so every
17//!     path is proven on the card before the decode engine takes the core's ports;
18//!   • the AFU JSON, the timing constraints of every FIFO crossing (the recipe the FIM's own `syn/setup/dcfifo.sdc`
19//!     applies), the clock-domain map [`CLOCK_DOMAINS_FILE`] the CDC gate reads, and the C header a driver includes;
20//!   • the host side: `sw/logos_hal.h`, the device a host program drives (its MMIO words, buffers the accelerator
21//!     reaches by I/O address, its user clock), its OPAE 2.12 backend for the card and its physical backend
22//!     (`sw/logos_hal_pci.c`: the VF's BAR and command word from sysfs, buffers in huge pages at their physical
23//!     addresses, for a host with no IOMMU), `sw/logos_core.h`, the core's commands as a program rings them,
24//!     `sw/logos_receipt.c`, the bring-up receipt (identity, every channel filled and checked alone and all at once
25//!     with its bandwidth, a wrong seed seen, a host buffer's round trip through a channel, on SECDED lines every
26//!     channel's code proven by injected flips) as one JSON object, and `sw/logos_load.c`, the image loader (a weight
27//!     image the image writer made copied onto the channels at their full line width, read back against its files
28//!     with every tile's check word checked, and with `--self-test` a damaged line found in its tile and the tile
29//!     loaded again);
30//!   • the simulation of all of it: `sim/logos_d5005_cosim.v`, the shell with four memory channels on four unrelated
31//!     clocks behind each the [`ChannelModel`] the configuration names (`sim/logos_avmm_memory_model.v`, a memory that
32//!     waits at random, or `sim/logos_ddr4_channel_model.v`, a DDR4-2400 channel timed by the controller model of
33//!     `codegen_sva::ddr4_jedec`), and the same backend over a simulator (`sim/logos_hal_sim.h`), Icarus Verilog's
34//!     VPI (`sim/logos_hal_vpi.c`) or Verilator's DPI-C (`sim/logos_hal_dpi.c`, the top built with `LOGOS_DPI`), so a
35//!     program that runs on the card runs unchanged against the RTL, the simulation stepping only while the program
36//!     waits on an MMIO access;
37//!   • on the engine build, `sw/logos_step.c`, the program that runs a model on the engine one token a step from what
38//!     [`write_engine_program`] wrote, and `sim/logos_engine_gated.v`, the engine a simulation runs in place of
39//!     `hw/rtl/logos_engine.v`: the same engine with its core's clock gated to the user cycles the core acts in
40//!     (`rtl::engine::simulation_twin`).
41//!
42//! The platform's widths are the D5005 FIM's (`src/top/ofs_d5005.ini`, `ipss/mem/includes/ofs_fim_emif_cfg_pkg.sv`):
43//! four banks of 2^27 lines, 512 data bits and 64 ECC bits a line on a 72-bit DDR4-2400 bus whose controller has ECC
44//! off, bursts of up to 64 lines; host channel lines of 512 bits at 51 address bits, MMIO words of 64 bits at 15.
45
46use crate::codegen_sva::ddr4_jedec::{verilog_model, Ddr4Grade, LOOKAHEAD};
47use crate::codegen_sva::scratchpad_rtl::{decode, encode, Decode};
48
49use super::physical::card_port_verilog;
50use super::rtl::engine::{engine_verilog, hard_float_model, simulation_twin, EngineParams, ENGINE_MIN_DIVIDE};
51
52/// Memory banks the D5005 FIM gives the AFU.
53pub const BANKS: u32 = 4;
54/// Bits of a bank's line address.
55pub const BANK_ADDRESS_BITS: u32 = 27;
56/// Bits of a bank's burst count.
57pub const BANK_BURST_BITS: u32 = 7;
58/// Bits of a host channel line address and of its line.
59pub const HOST_ADDRESS_BITS: u32 = 51;
60pub const HOST_LINE_BITS: u32 = 512;
61/// Bits of an MMIO word address (64-bit words of an 18-bit byte space).
62pub const MMIO_WORD_ADDRESS_BITS: u32 = 15;
63
64/// What a local-memory line carries to the AFU.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum LocalMemoryLine {
67    /// The 512 data bits; the PIM's `LOCAL_MEM_AVALON_MEM_PARAMS_DEFAULT`.
68    Data512,
69    /// The data bits and the 64 ECC lanes the controller leaves unused, 576 bits as data; the PIM's
70    /// `LOCAL_MEM_AVALON_MEM_PARAMS_FULL_BUS_DEFAULT`.
71    DataAndEcc576,
72    /// The 512 data bits, each of a line's eight 64-bit words guarded by SECDED(72,64) whose check byte the shell keeps
73    /// in an ECC lane ([`secded_line`]): the PIM's full bus at the bank, every line coded as it is written and
74    /// corrected as it is read.
75    Data512Secded,
76}
77
78impl LocalMemoryLine {
79    /// Bits of a line as the core moves it.
80    pub fn bits(self) -> u32 {
81        match self {
82            LocalMemoryLine::Data512 | LocalMemoryLine::Data512Secded => 512,
83            LocalMemoryLine::DataAndEcc576 => 576,
84        }
85    }
86
87    /// Bits of a line on the bank's bus.
88    pub fn bus_bits(self) -> u32 {
89        match self {
90            LocalMemoryLine::Data512 => 512,
91            LocalMemoryLine::DataAndEcc576 | LocalMemoryLine::Data512Secded => 576,
92        }
93    }
94
95    /// Whether the shell codes every line SECDED(72,64).
96    pub fn secded(self) -> bool {
97        self == LocalMemoryLine::Data512Secded
98    }
99
100    fn pim_params(self) -> &'static str {
101        match self {
102            LocalMemoryLine::Data512 => "LOCAL_MEM_AVALON_MEM_PARAMS_DEFAULT",
103            LocalMemoryLine::DataAndEcc576 | LocalMemoryLine::Data512Secded => "LOCAL_MEM_AVALON_MEM_PARAMS_FULL_BUS_DEFAULT",
104        }
105    }
106
107    fn pim_width(self) -> &'static str {
108        match self {
109            LocalMemoryLine::Data512 => "LOCAL_MEM_DATA_WIDTH",
110            LocalMemoryLine::DataAndEcc576 | LocalMemoryLine::Data512Secded => "LOCAL_MEM_FULL_BUS_WIDTH",
111        }
112    }
113}
114
115// ── SECDED(72,64) on a channel line ─────────────────────────────────────────────────────────────────
116
117/// Words of a channel line, each its own SECDED(72,64) codeword.
118pub const SECDED_WORDS: usize = 8;
119
120/// The codeword position of bit `j` of a word's check byte: the overall parity, then the Hamming checks at the powers
121/// of two, as `codegen_sva::scratchpad_rtl` places them.
122pub const SECDED_CHECKS: [u32; 8] = [0, 1, 2, 4, 8, 16, 32, 64];
123
124/// The ECC lane holding word `w`'s check byte: bus byte `64 + 7 − w`. The controller's 576-bit word is either the
125/// memory burst's eight beats of 72 DQ pins each (bus bit `i` on pin `i mod 72`), or its eight beats of the 64 data
126/// pins beneath the eight beats of the eight ECC pins (data bit `i` on pin `i mod 64`, ECC bit `e` on pin
127/// `64 + e mod 8`). Under either, word `w`'s data bits and ECC lane `7 − w` lie on 72 different pins, so a failing pin
128/// flips one bit of each codeword, which the code corrects; pairing word `w` with lane `w` holds only under the
129/// second. Were the bus word each group of eight pins' beats in turn, no grouping of whole bytes would hold under all
130/// three.
131pub fn secded_check_byte(word: usize) -> usize {
132    SECDED_WORDS - 1 - word
133}
134
135/// The codeword position of each of a word's data bits.
136fn secded_positions() -> &'static [u32; 64] {
137    static AT: std::sync::OnceLock<[u32; 64]> = std::sync::OnceLock::new();
138    AT.get_or_init(|| {
139        let mut at = [0u32; 64];
140        for (k, p) in at.iter_mut().enumerate() {
141            let cw = encode(1u64 << k);
142            *p = (0..72u32).find(|&q| cw >> q & 1 == 1 && !SECDED_CHECKS.contains(&q)).expect("a data bit has a place");
143        }
144        at
145    })
146}
147
148/// A word's check byte: bit `j` the codeword's bit at [`SECDED_CHECKS`]`[j]`.
149fn secded_check(word: u64) -> u8 {
150    let cw = encode(word);
151    SECDED_CHECKS.iter().enumerate().fold(0u8, |c, (j, &p)| c | ((cw >> p & 1) as u8) << j)
152}
153
154/// A channel line as the bus holds it: its 64 data bytes where they are, then the eight ECC lanes, word `w`'s check
155/// byte at lane [`secded_check_byte`]`(w)`.
156pub fn secded_line(data: &[u8; 64]) -> [u8; 72] {
157    let mut bus = [0u8; 72];
158    bus[..64].copy_from_slice(data);
159    for w in 0..SECDED_WORDS {
160        let word = u64::from_le_bytes(data[8 * w..8 * w + 8].try_into().expect("eight bytes"));
161        bus[64 + secded_check_byte(w)] = secded_check(word);
162    }
163    bus
164}
165
166/// A bus line read back: its data, each word corrected where its codeword shows one flipped bit, and bit `w` of
167/// `corrected` and of `uncorrectable` the verdict on word `w`.
168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub struct SecdedRead {
170    pub data: [u8; 64],
171    pub corrected: u8,
172    pub uncorrectable: u8,
173}
174
175/// What the shell makes of a bus line it reads: each word's codeword reassembled from its data bits and its check
176/// byte and decoded as `scratchpad_rtl::decode` decodes it.
177pub fn secded_read(bus: &[u8; 72]) -> SecdedRead {
178    let positions = secded_positions();
179    let mut out = SecdedRead { data: [0; 64], corrected: 0, uncorrectable: 0 };
180    for w in 0..SECDED_WORDS {
181        let word = u64::from_le_bytes(bus[8 * w..8 * w + 8].try_into().expect("eight bytes"));
182        let check = bus[64 + secded_check_byte(w)];
183        let mut cw = 0u128;
184        for (k, &p) in positions.iter().enumerate() {
185            cw |= ((word >> k & 1) as u128) << p;
186        }
187        for (j, &p) in SECDED_CHECKS.iter().enumerate() {
188            cw |= ((check >> j & 1) as u128) << p;
189        }
190        let (data, verdict) = decode(cw);
191        out.data[8 * w..8 * w + 8].copy_from_slice(&data.to_le_bytes());
192        match verdict {
193            Decode::NoError => {}
194            Decode::Corrected(_) | Decode::CorrectedParityBit => out.corrected |= 1 << w,
195            Decode::DoubleError => out.uncorrectable |= 1 << w,
196        }
197    }
198    out
199}
200
201/// `logos_secded.v`: the line coder and decoder, every parity a mask of a word's data bits read off
202/// [`encode`]'s codewords, every correction the data bit whose codeword position the syndrome names.
203pub fn secded_verilog() -> String {
204    let positions = secded_positions();
205    let mut emask = [0u64; 8];
206    for k in 0..64 {
207        let c = secded_check(1u64 << k);
208        for (j, m) in emask.iter_mut().enumerate() {
209            *m |= ((c >> j & 1) as u64) << k;
210        }
211    }
212    let smask = |i: u32| (0..64).filter(|&k| positions[k] >> i & 1 == 1).fold(0u64, |m, k| m | 1 << k);
213    let mut v = String::from(SECDED_HEAD);
214    v += "module logos_secded_word_encode(input [63:0] d, output [7:0] c);\n";
215    for (j, m) in emask.iter().enumerate() {
216        v += &format!("  assign c[{j}] = ^(d & 64'h{m:016x});\n");
217    }
218    v += "endmodule\n\n";
219    v += SECDED_WORD_DECODE_HEAD;
220    v += "module logos_secded_word_decode(input [63:0] d, input [7:0] c, output [63:0] q, output corrected,\n  output uncorrectable);\n";
221    let syndrome: Vec<String> = (0..7u32).rev().map(|i| format!("^(d & 64'h{:016x}) ^ c[{}]", smask(i), i + 1)).collect();
222    v += &format!("  wire [6:0] s = {{{}}};\n", syndrome.join(",\n                   "));
223    v += "  wire       odd = ^d ^ ^c;\n";
224    v += "  assign corrected = odd && s <= 7'd71;\n";
225    v += "  assign uncorrectable = odd ? s > 7'd71 : s != 7'd0;\n";
226    for (k, p) in positions.iter().enumerate() {
227        v += &format!("  assign q[{k}] = d[{k}] ^ (odd && s == 7'd{p});\n");
228    }
229    v += "endmodule\n\n";
230    v += SECDED_LINE;
231    v
232}
233
234const SECDED_HEAD: &str = r##"// SECDED(72,64) on a channel line, generated from codegen_sva::scratchpad_rtl's code: each of the line's eight
235// 64-bit words is one codeword, its data bits where they are on the bus (bits 64w..64w+63) and its check byte in ECC
236// lane 7 - w (bus bits 512 + 8(7 - w) ..), check bit j the codeword's bit at position C_j, C = 0, 1, 2, 4, 8, 16, 32, 64
237// (C_0 the overall parity). Lane 7 - w puts each codeword on 72 different DQ pins whether the controller's bus word
238// runs beat by beat over all 72 pins or holds the data pins' beats beneath the ECC pins'.
239
240// A word's check byte: each bit the parity of the data bits that feed it.
241"##;
242
243const SECDED_WORD_DECODE_HEAD: &str = r##"// A word read back: syndrome bit i the parity of the codeword positions with bit i set, so a single flipped bit's
244// position; the overall parity odd for an odd count of flips. Odd with a syndrome naming a position: one flip,
245// corrected (a check bit's flip leaves the data as it is). Even with a syndrome, or odd with one naming no position:
246// uncorrectable, the data as read.
247"##;
248
249const SECDED_LINE: &str = r##"// A line coded: the data bits as they are, word w's check byte in ECC lane 7 - w.
250module logos_secded_encode(input [511:0] d, output [575:0] bus);
251  assign bus[511:0] = d;
252  genvar w;
253  generate for (w = 0; w < 8; w = w + 1) begin : word
254    logos_secded_word_encode coder(.d(d[64*w +: 64]), .c(bus[512 + 8*(7-w) +: 8]));
255  end endgenerate
256endmodule
257
258// A line read back: the data, each word corrected where it can be, and each word's verdict, bit w for word w.
259module logos_secded_decode(input [575:0] bus, output [511:0] d, output [7:0] corrected, output [7:0] uncorrectable);
260  genvar w;
261  generate for (w = 0; w < 8; w = w + 1) begin : word
262    logos_secded_word_decode decoder(.d(bus[64*w +: 64]), .c(bus[512 + 8*(7-w) +: 8]), .q(d[64*w +: 64]),
263      .corrected(corrected[w]), .uncorrectable(uncorrectable[w]));
264  end endgenerate
265endmodule
266"##;
267
268/// What stands behind each memory channel in the co-simulation.
269#[derive(Clone, Copy, Debug, PartialEq, Eq)]
270pub enum ChannelModel {
271    /// A memory that waits and gaps at random, the stress the shell's FIFOs and the bring-up are proved under.
272    Random,
273    /// A DDR4-2400 channel timed by the controller model of `codegen_sva::ddr4_jedec` (`logos_ddr4_channel_model`):
274    /// the cycles a run takes are the controller model's for the lines the shell asks for.
275    Ddr4,
276}
277
278/// The accelerator a build makes.
279#[derive(Clone, Debug, PartialEq)]
280pub struct AfuConfig {
281    /// The accelerator's name in the AFU JSON.
282    pub name: &'static str,
283    /// The `accelerator-type-uuid` a driver opens it by, and the AFU ID its feature header carries.
284    pub uuid: u128,
285    /// A build identity the BUILD register reads back.
286    pub build: u64,
287    pub line: LocalMemoryLine,
288    /// The user clock the AFU JSON asks for, MHz: the fitter's target, the clock set from the timing it closes. A divided
289    /// build asks for exactly this clock.
290    pub user_clock_mhz: u32,
291    /// The compute clock the shell runs on: the user clock divided by this, an even number, or the user clock itself at
292    /// 1. A divided build closes timing at the divided period.
293    pub compute_divide: u32,
294    /// Lines a burst, on every port.
295    pub max_burst: u32,
296    /// log2 of the lines a read-data FIFO holds, and a write-data FIFO.
297    pub read_fifo_log2: u32,
298    pub write_fifo_log2: u32,
299    /// The co-simulation's memory channels.
300    pub channel_model: ChannelModel,
301    /// The decode engine the core runs steps on (`rtl::engine::engine_verilog`, its memory on the channels through the
302    /// card port, `physical::card_port_verilog`), or none for the bring-up build.
303    pub engine: Option<EngineParams>,
304    /// Placement assignments for Quartus, as Tcl: the tree's `hw/rtl/logos_floorplan.tcl`, named in sources.txt as
305    /// `QI:` so the build sources it; none when no floorplan is given.
306    pub floorplan: Option<String>,
307    /// Whether the accelerator is compiled into the FIM itself (a flat image, `scripts/d5005/build-afu.sh --flat`)
308    /// rather than as a persona. The FIM's static region instantiates its exercisers (`he_lb_top`, PF0 VF0) whatever
309    /// the port holds, but the Stratix 10 release's `afu_main.tcl` loads their sources only when no PIM-wrapped AFU is
310    /// configured, so a flat build's sources.txt names them: [`FIM_STATIC_EXERCISERS`], from the FIM that builds it.
311    pub flat: bool,
312}
313
314/// The FIM's static exercisers' file list, as sources.txt names it for a flat build (`QI:`, so Quartus sources it):
315/// under `OFS_ROOTDIR`, the FIM source tree the FIM's build runs from, which OPAE's rtl_src_config expands.
316pub const FIM_STATIC_EXERCISERS: &str = "${OFS_ROOTDIR}/ofs-common/src/common/afu_main_std_exerciser_design_files.tcl";
317
318/// Every accelerator the engine builds for the card is found by this UUID: the name-based UUID of
319/// `logicaffeine:d5005:decode-engine` (the first 16 bytes of its SHA-256, version 5).
320pub const AFU_UUID: u128 = 0x1adf73eb_54c5_5b4c_8a2e_3a459f9b2a6e;
321
322/// The user clock an engine build asks for, MHz, and the divide its compute clock is: the reference engine's
323/// single-cycle units close at that period on the 1SX280.
324pub const ENGINE_USER_CLOCK_MHZ: u32 = 100;
325pub const ENGINE_COMPUTE_DIVIDE: u32 = 64;
326
327impl AfuConfig {
328    /// The engine build for `engine` at `line` (data64 or SECDED lines): the bring-up build's core and channels with
329    /// the engine and its card port behind them, its compute clock [`ENGINE_USER_CLOCK_MHZ`] divided by
330    /// [`ENGINE_COMPUTE_DIVIDE`].
331    pub fn engine(engine: EngineParams, line: LocalMemoryLine) -> AfuConfig {
332        AfuConfig {
333            name: "logos_d5005_engine",
334            line,
335            user_clock_mhz: ENGINE_USER_CLOCK_MHZ,
336            compute_divide: ENGINE_COMPUTE_DIVIDE,
337            engine: Some(engine),
338            ..AfuConfig::d5005()
339        }
340    }
341
342    /// The bring-up build: the ECC lanes as data, the user clock at the engine plan's 450 MHz, 64-line bursts, 512
343    /// lines of read FIFO a channel (a run of eight bursts in flight) and 64 of write FIFO.
344    pub fn d5005() -> AfuConfig {
345        AfuConfig {
346            name: "logos_d5005_bringup",
347            uuid: AFU_UUID,
348            build: 0,
349            line: LocalMemoryLine::DataAndEcc576,
350            user_clock_mhz: 450,
351            compute_divide: 1,
352            max_burst: 64,
353            read_fifo_log2: 9,
354            write_fifo_log2: 6,
355            channel_model: ChannelModel::Random,
356            engine: None,
357            floorplan: None,
358            flat: false,
359        }
360    }
361
362    /// Whether the build's engine is a hybrid one: its matvec jobs on a streaming unit in the user clock.
363    pub fn hybrid(&self) -> bool {
364        self.engine.as_ref().is_some_and(|e| e.streaming.is_some())
365    }
366
367    /// Whether the engine's streaming unit pairs its cores on processing elements in a clock of twice theirs
368    /// ([`super::rtl::column::ColumnSet::pe_pairs`]): the user side in the PIM's `uClk_usrDiv2`, the pairs in `uClk_usr`.
369    pub fn pe_pairs(&self) -> bool {
370        self.engine.as_ref().and_then(|e| e.streaming.as_ref()).is_some_and(|s| s.set.pe_pairs)
371    }
372
373    /// Whether the engine's array issues its held beat against two token-rows a clock
374    /// ([`super::rtl::column::ColumnSet::with_token_pairs`]).
375    pub fn token_pairs(&self) -> bool {
376        self.engine.as_ref().and_then(|e| e.streaming.as_ref()).is_some_and(|s| s.set.token_pairs)
377    }
378
379    /// Whether the engine's f32 products are the card's floating-point DSP blocks' ([`super::numerics::FpUnit::Hard`]):
380    /// its programs are written at the card's products.
381    pub fn fp_hard(&self) -> bool {
382        self.engine
383            .as_ref()
384            .and_then(|e| e.streaming.as_ref())
385            .and_then(|s| s.hard_float)
386            .is_some_and(|h| h.fp_unit == super::numerics::FpUnit::Hard)
387    }
388
389    /// The engine's sequencer contexts ([`super::rtl::engine::Streaming::contexts`]): 1 for one sequencer or none.
390    pub fn contexts(&self) -> usize {
391        self.engine.as_ref().and_then(|e| e.streaming.as_ref()).map_or(1, |s| s.contexts)
392    }
393
394    /// The UUID as the AFU JSON writes it.
395    pub fn uuid_text(&self) -> String {
396        let h = format!("{:032x}", self.uuid);
397        format!("{}-{}-{}-{}-{}", &h[0..8], &h[8..12], &h[12..16], &h[16..20], &h[20..32])
398    }
399}
400
401/// How the host reaches a register.
402#[derive(Clone, Copy, Debug, PartialEq, Eq)]
403pub enum Access {
404    ReadOnly,
405    ReadWrite,
406    /// A write pushes a command; a read returns zero.
407    Doorbell,
408    /// A written one clears that bit.
409    WriteOneToClear,
410}
411
412/// One 64-bit register of the MMIO space.
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
414pub struct Register {
415    pub name: &'static str,
416    /// The 64-bit word it is at; its byte offset is eight times that.
417    pub word: u32,
418    pub access: Access,
419}
420
421/// The register map: the OFS AFU feature header (type 1, end of the list, no next feature) and the AFU ID at words 0
422/// to 4, as every OFS AFU carries them, then the engine's.
423pub const REGISTERS: [Register; 18] = [
424    Register { name: "DFH", word: 0, access: Access::ReadOnly },
425    Register { name: "ID_L", word: 1, access: Access::ReadOnly },
426    Register { name: "ID_H", word: 2, access: Access::ReadOnly },
427    Register { name: "RSVD0", word: 3, access: Access::ReadOnly },
428    Register { name: "RSVD1", word: 4, access: Access::ReadOnly },
429    Register { name: "SCRATCH", word: 5, access: Access::ReadWrite },
430    Register { name: "BUILD_ID", word: 6, access: Access::ReadOnly },
431    Register { name: "STATUS", word: 8, access: Access::ReadOnly },
432    Register { name: "RESULT0", word: 9, access: Access::ReadOnly },
433    Register { name: "RESULT1", word: 10, access: Access::ReadOnly },
434    Register { name: "STATUS_SEQ", word: 11, access: Access::ReadOnly },
435    Register { name: "CMD_SPACE", word: 12, access: Access::ReadOnly },
436    Register { name: "ERRORS", word: 13, access: Access::WriteOneToClear },
437    Register { name: "DROPPED", word: 14, access: Access::ReadOnly },
438    Register { name: "ARG0", word: 16, access: Access::ReadWrite },
439    Register { name: "ARG1", word: 17, access: Access::ReadWrite },
440    Register { name: "ARG2", word: 18, access: Access::ReadWrite },
441    Register { name: "DOORBELL", word: 19, access: Access::Doorbell },
442];
443
444/// The bring-up core's commands.
445#[derive(Clone, Copy, Debug, PartialEq, Eq)]
446pub enum Op {
447    Nop,
448    /// Every channel in mask ARG0 writes ARG2\[31:0\] lines from line ARG1, line L the pattern seeded ARG2\[63:32\].
449    Fill,
450    /// Every channel in the mask reads those lines back; RESULT0 the lines that differ.
451    Check,
452    /// ARG2\[31:0\] host lines from host line ARG0 into channel ARG1\[63:56\] at line ARG1\[26:0\], zero-extended.
453    HostToMem,
454    /// The reverse, a channel line truncated to a host line; complete when every host write burst is acknowledged.
455    MemToHost,
456    /// ARG2\[31:0\] channel lines of the channel's full width into channel ARG1\[63:56\] at line ARG1\[26:0\], read packed end
457    /// to end from host line ARG0 on: ceil(lines × line bits / 512) host lines, the bytes past the last channel line
458    /// unused.
459    HostToMemPacked,
460    /// The reverse: the channel lines packed end to end into host lines from ARG0, the last host line zero past them;
461    /// complete when every host write burst is acknowledged.
462    MemToHostPacked,
463    /// On SECDED lines: the next ARG2\[31:0\] lines written to each channel in mask ARG0 have bus bits ARG1\[9:0\] and
464    /// ARG1\[25:16\] flipped after they are coded, one bit when the two are equal; complete at once.
465    Inject,
466    /// On SECDED lines: RESULT0 = {words found uncorrectable, words corrected} of channel ARG0\[7:0\] since reset, each
467    /// counted modulo 2^32, so a host's difference of two reads is exact; complete at once.
468    EccCounts,
469    /// With the engine: its configuration a program keeps — ARG0\[15:0\] the token-rows of a step, ARG0\[19:16\] {per-block
470    /// scaling, two words, an int8 cache, an f16 state} — and the card port's map: ARG1\[31:0\] the granule space's first
471    /// logical line, ARG2\[26:0\] the channel line it starts at, ARG2\[60:56\] log2 of a granule's lines; complete at once.
472    EngineConfig,
473    /// With the engine: ARG2\[31:0\] host lines from host line ARG0 into the engine's table ARG1\[34:32\] from entry
474    /// ARG1\[31:0\] on, one entry a line ([`crate::spatial::decode_engine::rtl::engine::Tables`]; table 6 the store).
475    EngineTable,
476    /// With the engine: a step, the jobs from word ARG0\[31:0\] to word ARG1\[31:0\], the card port holding the channels;
477    /// complete when the engine is idle again, RESULT0 = {the word the sequencer reached \[63:32\], the card port's fault
478    /// \[1\], the engine's fault \[0\]}.
479    EngineRun,
480    /// With the engine: ARG2\[31:0\] beats of its store from beat ARG1\[31:0\] into host lines from ARG0; complete when every
481    /// host write burst is acknowledged.
482    StoreRead,
483}
484
485impl Op {
486    pub const ALL: [Op; 13] = [
487        Op::Nop,
488        Op::Fill,
489        Op::Check,
490        Op::HostToMem,
491        Op::MemToHost,
492        Op::HostToMemPacked,
493        Op::MemToHostPacked,
494        Op::Inject,
495        Op::EccCounts,
496        Op::EngineConfig,
497        Op::EngineTable,
498        Op::EngineRun,
499        Op::StoreRead,
500    ];
501
502    pub fn code(self) -> u8 {
503        match self {
504            Op::Nop => 0,
505            Op::Fill => 1,
506            Op::Check => 2,
507            Op::HostToMem => 3,
508            Op::MemToHost => 4,
509            Op::HostToMemPacked => 5,
510            Op::MemToHostPacked => 6,
511            Op::Inject => 7,
512            Op::EccCounts => 8,
513            Op::EngineConfig => 9,
514            Op::EngineTable => 10,
515            Op::EngineRun => 11,
516            Op::StoreRead => 12,
517        }
518    }
519
520    pub fn name(self) -> &'static str {
521        match self {
522            Op::Nop => "NOP",
523            Op::Fill => "FILL",
524            Op::Check => "CHECK",
525            Op::HostToMem => "HOST_TO_MEM",
526            Op::MemToHost => "MEM_TO_HOST",
527            Op::HostToMemPacked => "HOST_TO_MEM_PACKED",
528            Op::MemToHostPacked => "MEM_TO_HOST_PACKED",
529            Op::Inject => "INJECT",
530            Op::EccCounts => "ECC_COUNTS",
531            Op::EngineConfig => "ENGINE_CONFIG",
532            Op::EngineTable => "ENGINE_TABLE",
533            Op::EngineRun => "ENGINE_RUN",
534            Op::StoreRead => "STORE_READ",
535        }
536    }
537}
538
539// ── The engine's program on the card ───────────────────────────────────────────────────────────────
540
541/// What a host program runs a model on the engine build with, one token-row a step: written by
542/// [`write_engine_program`] as `engine.txt`, beside the engine's tables, the memory image and the embedding table.
543#[derive(Clone, Debug, PartialEq, Eq)]
544pub struct EngineProgram {
545    /// ENGINE_CONFIG's knobs, {per_block, two_words, cache_int8, state_f16}.
546    pub knobs: u8,
547    /// The card port's map: the space's first logical line, the channel line it starts at, log2 of a granule's lines.
548    pub space: u64,
549    pub base: u64,
550    pub granule_log2: u32,
551    /// Words of the step's jobs after the host's embedding, table 0's entries.
552    pub job_words: usize,
553    /// The store's first beat and beats a row of a row's embedding and of its logits.
554    pub residual: (u32, u32),
555    pub logits: (u32, u32),
556    /// Whether a row's logits slot holds their arg-max and that logit ([`super::machine::StoreLayout::greedy`]) in place
557    /// of the logits.
558    pub greedy: bool,
559    /// The store's beats over the program's rows: a build whose store is smaller cannot run it.
560    pub store_beats: u32,
561    /// Tokens the embedding table holds, its row's elements, and the logits a step reads back.
562    pub tokens: usize,
563    pub hidden: usize,
564    pub vocab: usize,
565    /// A row's ring index is its position modulo this.
566    pub ring_rows: usize,
567    /// The first logical line of sequence 0's region, a region's lines, the regions, the positions a sequence reaches.
568    pub regions: u64,
569    pub region: u64,
570    pub sequences: usize,
571    pub ctx: usize,
572    /// Lines of each channel's image.
573    pub channel_lines: Vec<u64>,
574    /// The streaming unit the program's stream entries are written for, as its build names it
575    /// (`LOGOS_D5005_STREAMING`): empty for the reference engine.
576    pub streaming: String,
577    /// The engine's sequencer contexts the program is written for: sequence s runs in context s mod the contexts, as its
578    /// row s / the contexts, the store laid for a context's rows.
579    pub contexts: usize,
580    /// The multiplier the program's numerics take ([`super::numerics::FpUnit`]): its build's products must be the same
581    /// (`LOGOS_D5005_FP_HARD`).
582    pub fp_unit: super::numerics::FpUnit,
583}
584
585/// One 64-byte host line holding `bits` from its lowest byte.
586fn host_line(bits: &[u8]) -> [u8; 64] {
587    let mut line = [0u8; 64];
588    line[..bits.len()].copy_from_slice(bits);
589    line
590}
591
592/// The job listing of a step whose jobs past the host's embedding are `jobs` (the engine's job table, as
593/// [`super::machine::encode`] lays them in `table0.bin`): a "job <j> word <w> <kind>" line a job, job 0 the host's
594/// embedding at word 0 and job j the table's `jobs[j - 1]` at the word the table opens it at, its kind its `Debug` text
595/// cut at 240 characters. `sw/logos_step.c` profiles a step by it (LOGOS_PROFILE), a job at a time.
596pub fn job_listing(jobs: &[super::machine::Job]) -> String {
597    use std::fmt::Write;
598    let mut listing = String::from("job 0 word 0 Embed\n");
599    let mut word = 0;
600    for (j, job) in jobs.iter().enumerate() {
601        writeln!(listing, "job {} word {word} {}", j + 1, format!("{job:?}").chars().take(240).collect::<String>()).unwrap();
602        word += super::machine::encode(std::slice::from_ref(job)).len();
603    }
604    listing
605}
606
607/// [`job_listing`] of the program in `dir` read back from its job table (`table0.bin`, a word a 64-byte line) alone: for
608/// a program written before programs carried their listing.
609pub fn job_listing_of(dir: &std::path::Path) -> std::io::Result<String> {
610    let table = std::fs::read(dir.join("table0.bin"))?;
611    let words: Vec<u64> = table.chunks_exact(64).map(|line| u64::from_le_bytes(line[..8].try_into().unwrap())).collect();
612    let jobs = super::machine::decode(&words).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}: {e}", dir.display())))?;
613    Ok(job_listing(&jobs))
614}
615
616/// Write the program `machine` — as [`super::machine::Machine::new`] left it, its regions empty — runs on the engine
617/// build over `map`, a flat map ([`super::physical::PhysicalMap::flat`]: the reference engine reads its weights a
618/// line at a time through the card port), into `dir`: `engine.txt`, `table<k>.bin` for the engine's static tables
619/// (0 the step's jobs after the host's embedding, 2 the slots, 3 the tensors, 4 the rotary tables, 5 the blocks), a
620/// 64-byte line an entry, `jobs.txt` the step's job listing ([`job_listing`]), `channel<k>.bin` the memory image, and
621/// `embed.bin`, every token's embedding as `f32`.
622pub fn write_engine_program(machine: &super::machine::Machine, map: &super::physical::PhysicalMap, dir: &std::path::Path) -> std::io::Result<EngineProgram> {
623    assert_eq!(map.tensors_end, 0, "the reference engine reads its weights through the card port, over a flat map");
624    assert!(map.image.tensors.is_empty(), "a flat map has no weight image");
625    let channels = map.channel_bytes(machine.memory(), &vec![Vec::new(); map.channel_lines.len()]);
626    write_program(machine, map, &|at| Some(at), &channels, String::new(), false, 1, dir)
627}
628
629/// Write the program `machine` runs on the hybrid engine build over `map`, a hybrid map
630/// ([`super::physical::PhysicalMap::hybrid`]: the matrices the machine streams in their image at each channel's start,
631/// the memory compacted by `compaction` with the streamed tensors' stream entries after it, the space the engine's card
632/// port reads), for a streaming unit built for `set`, the image's tiles read from `weights`: the files
633/// [`write_engine_program`] writes, every address the engine reads in the flat space where `compaction` finds it.
634pub fn write_hybrid_program(
635    machine: &super::machine::Machine,
636    map: &super::physical::PhysicalMap,
637    compaction: &super::physical::Compaction,
638    set: &super::rtl::column::ColumnSet,
639    weights: &mut dyn crate::model::reference::WeightSource,
640    dir: &std::path::Path,
641) -> std::io::Result<EngineProgram> {
642    hybrid_program(machine, map, compaction, set, weights, false, 1, dir)
643}
644
645/// [`write_hybrid_program`] with a greedy head: the head's stream entry asks the streaming unit for each row's arg-max of
646/// its logits rather than the logits, and the store is laid greedy ([`super::machine::StoreLayout::greedy`]), so a row's
647/// read-back is one beat however wide the vocabulary and the store holds many rows. An error when the head's matrix is
648/// not one the unit streams.
649pub fn write_greedy_program(
650    machine: &super::machine::Machine,
651    map: &super::physical::PhysicalMap,
652    compaction: &super::physical::Compaction,
653    set: &super::rtl::column::ColumnSet,
654    weights: &mut dyn crate::model::reference::WeightSource,
655    dir: &std::path::Path,
656) -> std::io::Result<EngineProgram> {
657    hybrid_program(machine, map, compaction, set, weights, true, 1, dir)
658}
659
660/// [`write_hybrid_program`], greedy when `greedy` ([`write_greedy_program`]), for an engine of `contexts` sequencer
661/// contexts ([`super::rtl::engine::Streaming::contexts`]): the machine's sequences dealt over them, sequence s in
662/// context s mod `contexts` as its row s / `contexts`, the store laid for a context's rows — an error unless the
663/// sequences fill every context alike.
664#[allow(clippy::too_many_arguments)]
665pub fn write_contexts_program(
666    machine: &super::machine::Machine,
667    map: &super::physical::PhysicalMap,
668    compaction: &super::physical::Compaction,
669    set: &super::rtl::column::ColumnSet,
670    weights: &mut dyn crate::model::reference::WeightSource,
671    greedy: bool,
672    contexts: usize,
673    dir: &std::path::Path,
674) -> std::io::Result<EngineProgram> {
675    let sequences = machine.layout.sequences;
676    if contexts < 1 || sequences % contexts != 0 {
677        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{sequences} sequences over {contexts} contexts")));
678    }
679    hybrid_program(machine, map, compaction, set, weights, greedy, contexts, dir)
680}
681
682#[allow(clippy::too_many_arguments)]
683fn hybrid_program(
684    machine: &super::machine::Machine,
685    map: &super::physical::PhysicalMap,
686    compaction: &super::physical::Compaction,
687    set: &super::rtl::column::ColumnSet,
688    weights: &mut dyn crate::model::reference::WeightSource,
689    greedy: bool,
690    contexts: usize,
691    dir: &std::path::Path,
692) -> std::io::Result<EngineProgram> {
693    use super::rtl::streaming::StreamEntry;
694    assert_eq!(map.tensors_end, 0, "the hybrid engine's card port reads the flat space from logical line 0");
695    let invalid = |e: String| std::io::Error::new(std::io::ErrorKind::InvalidInput, e);
696    let head = &machine.layout.tensors[head_tensor(&machine.jobs)].name;
697    let streamed = map.image.tensors.iter().position(|t| &t.name == head);
698    if greedy && streamed.is_none() {
699        return Err(invalid(format!("a greedy head streams its matrix, and `{head}` is not streamed")));
700    }
701    let folds = folds_of(machine).map_err(invalid)?;
702    let entries = (0..map.image.tensors.len())
703        .map(|k| {
704            let fold = folds.get(map.image.tensors[k].name.as_str()).copied().unwrap_or(super::machine::Fold::Put);
705            StreamEntry::of(&map.image, k, set).map(|e| StreamEntry { greedy: greedy && Some(k) == streamed, fold, ..e })
706        })
707        .collect::<Result<Vec<_>, String>>()
708        .map_err(invalid)?;
709    let folding = folds.values().any(|&f| f != super::machine::Fold::Put);
710    let normed = machine.jobs.iter().any(|j| matches!(j, super::machine::Job::Delta { norm: Some(_), .. }));
711    let image = map.image.fill(weights)?;
712    let channels = map.channel_bytes(&compaction.space(machine.memory(), &entries), &image);
713    let pairs = map.image.layout.stripe.expect("a hybrid map's image laid in stripes");
714    let streaming = format!(
715        "{}/{pairs}{}{}{}",
716        set.signature(),
717        if folding { "+folds" } else { "" },
718        if normed { "+norm" } else { "" },
719        super::rtl::engine::behind(Some(machine.layout.numerics.flush))
720    );
721    write_program(machine, map, &|at| compaction.find(at), &channels, streaming, greedy, contexts, dir)
722}
723
724/// Each tensor a matrix job of `machine`'s step reads and how that job folds its sums: an error naming a tensor two jobs
725/// fold two ways, which no stream entry could say.
726fn folds_of(machine: &super::machine::Machine) -> Result<std::collections::HashMap<&str, super::machine::Fold>, String> {
727    use super::machine::Job;
728    let mut folds = std::collections::HashMap::new();
729    for job in &machine.jobs {
730        if let Job::Matvec { tensor, fold, .. } = job {
731            let name = machine.layout.tensors[*tensor].name.as_str();
732            if folds.insert(name, *fold).is_some_and(|f| f != *fold) {
733                return Err(format!("`{name}` folded two ways by two jobs"));
734            }
735        }
736    }
737    Ok(folds)
738}
739
740/// The tensor of the head, the last matvec job writing the logits.
741fn head_tensor(jobs: &[super::machine::Job]) -> usize {
742    use super::machine::Job;
743    use super::program::Slot;
744    jobs.iter()
745        .rev()
746        .find_map(|j| match j {
747            Job::Matvec { tensor, to: Slot::Logits, .. } => Some(*tensor),
748            _ => None,
749        })
750        .expect("a head writes the logits")
751}
752
753/// The program's files: the engine's tables with every address placed by `place`, the step's job listing (`jobs.txt`,
754/// [`job_listing`]), the channels' bytes `channels`, the embeddings and `engine.txt`, which names the streaming unit
755/// `streaming` when the program is a hybrid engine's and says when its head is greedy, then the model's program the
756/// step was read off: `program.lg`, the LOGOS text the author wrote, and `program.txt`, its identity line. The store holds a row for each of the machine's sequences, a
757/// step a token each — a bank of the store a context, each the rows of its sequences, for `contexts` contexts.
758#[allow(clippy::too_many_arguments)]
759fn write_program(
760    machine: &super::machine::Machine,
761    map: &super::physical::PhysicalMap,
762    place: &dyn Fn(u64) -> Option<u64>,
763    channels: &[Vec<u8>],
764    streaming: String,
765    greedy: bool,
766    contexts: usize,
767    dir: &std::path::Path,
768) -> std::io::Result<EngineProgram> {
769    use super::machine::{BlockState, Job, Row, StoreLayout, LINE};
770    use super::program::Slot;
771    let layout = &machine.layout;
772    let jobs = &machine.jobs;
773    let Some(Job::Embed { tensor: embed }) = jobs.first() else { panic!("a step opens with the host's embedding") };
774    assert!(map.granule.is_power_of_two(), "the card port's granule is a power of two");
775    let rows = layout.sequences / contexts;
776    let store = if greedy { StoreLayout::greedy(jobs, layout, rows) } else { StoreLayout::of(jobs, layout, rows) };
777    let t = super::rtl::engine::tables_at(layout, &store, &jobs[1..], &[Row { sequence: 0, position: 0, token: 0, ring: 0 }], place);
778    std::fs::create_dir_all(dir)?;
779    let write = |name: &str, lines: Vec<[u8; 64]>| std::fs::write(dir.join(name), lines.concat());
780    write("table0.bin", t.jobs.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
781    write("table2.bin", t.slots.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
782    write("table3.bin", t.tensors.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
783    write("table4.bin", t.ropes.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
784    write("table5.bin", t.blocks.iter().map(|f| host_line(&f.iter().flat_map(|x| x.to_le_bytes()).collect::<Vec<u8>>())).collect())?;
785    std::fs::write(dir.join("jobs.txt"), job_listing(&jobs[1..]))?;
786    for (k, channel) in channels.iter().enumerate() {
787        std::fs::write(dir.join(format!("channel{k}.bin")), channel)?;
788    }
789    let e = &layout.tensors[*embed];
790    let (hidden, tokens) = (e.dims[0] as usize, e.dims[1] as usize);
791    let rb = e.row_bytes() as usize;
792    let mut table = Vec::with_capacity(tokens * hidden * 4);
793    for token in 0..tokens {
794        let at = e.at as usize + token * rb;
795        let mut v = Vec::new();
796        e.ty.dequantize(&machine.memory()[at..at + rb], &mut v);
797        table.extend(v.iter().flat_map(|x| x.to_le_bytes()));
798    }
799    std::fs::write(dir.join("embed.bin"), table)?;
800    let vocab = layout.tensors[head_tensor(jobs)].dims[1] as usize;
801    let ring_rows = layout
802        .blocks
803        .iter()
804        .find_map(|b| match b {
805            BlockState::Delta { taps, .. } => Some(taps.saturating_sub(1).max(1)),
806            _ => None,
807        })
808        .unwrap_or(1);
809    assert!(layout.regions % LINE == 0 && layout.region % LINE == 0, "regions on lines");
810    let regions = place(layout.regions).expect("a place for the regions");
811    let p = EngineProgram {
812        knobs: t.state_f16 as u8 | (t.cache_int8 as u8) << 1 | (t.two_words as u8) << 2 | (t.per_block as u8) << 3,
813        space: map.tensors_end / LINE,
814        base: map.base,
815        granule_log2: map.granule.trailing_zeros(),
816        job_words: t.jobs.len(),
817        residual: store.slots[Slot::Residual.code() as usize],
818        logits: store.slots[Slot::Logits.code() as usize],
819        greedy,
820        store_beats: store.beats,
821        tokens,
822        hidden,
823        vocab,
824        ring_rows,
825        regions: regions / LINE,
826        region: layout.region / LINE,
827        sequences: layout.sequences,
828        ctx: layout.ctx,
829        channel_lines: map.channel_lines.clone(),
830        streaming,
831        contexts,
832        fp_unit: layout.numerics.fp_unit,
833    };
834    let lines: Vec<String> = p.channel_lines.iter().map(u64::to_string).collect();
835    let text = format!(
836        "knobs {}\nspace {}\nbase {}\ngranule_log2 {}\njob_words {}\nresidual {} {}\nlogits {} {}\ntokens {}\nhidden {}\n\
837         vocab {}\nring_rows {}\nregions {}\nregion {}\nsequences {}\nctx {}\nchannels {}\nstore {}\n",
838        p.knobs,
839        p.space,
840        p.base,
841        p.granule_log2,
842        p.job_words,
843        p.residual.0,
844        p.residual.1,
845        p.logits.0,
846        p.logits.1,
847        p.tokens,
848        p.hidden,
849        p.vocab,
850        p.ring_rows,
851        p.regions,
852        p.region,
853        p.sequences,
854        p.ctx,
855        lines.join(" "),
856        p.store_beats
857    );
858    let text = if p.streaming.is_empty() { text } else { format!("{text}streaming {}\n", p.streaming) };
859    let text = if p.greedy { format!("{text}greedy 1\n") } else { text };
860    let text = if p.contexts > 1 { format!("{text}contexts {}\n", p.contexts) } else { text };
861    let text = if p.fp_unit == super::numerics::FpUnit::Hard { format!("{text}fp hard\n") } else { text };
862    std::fs::write(dir.join("engine.txt"), text)?;
863    let (source, _, _) = machine.identity.cut.write(&machine.spec, "");
864    assert_eq!(
865        crate::model::program::Identity::of(machine.identity.cut, &source),
866        machine.identity,
867        "the machine's step is read off the program its identity names"
868    );
869    std::fs::write(dir.join("program.lg"), source)?;
870    std::fs::write(dir.join("program.txt"), format!("{}\n", machine.identity.line()))?;
871    Ok(p)
872}
873
874/// The status code of a completed command, and of one whose op the core does not know.
875pub const STATUS_OK: u8 = 0x00;
876/// The code of a running command's progress record: the core cuts one every `2^HB` of its cycles in which the
877/// command's lines, its host lines or the engine's sequencer moved, so a host waits while a long command moves.
878pub const STATUS_RUNNING: u8 = 0x01;
879pub const STATUS_UNKNOWN_OP: u8 = 0x80;
880/// ERRORS bit 0: a doorbell rang with the command FIFO full, and was dropped.
881pub const ERROR_COMMAND_DROPPED: u64 = 1;
882
883/// The clock-domain map's path among [`afu_sources`].
884pub const CLOCK_DOMAINS_FILE: &str = "hw/logos_d5005_domains.json";
885
886/// One emitted file: its path under the AFU's root and its text.
887#[derive(Clone, Debug, PartialEq, Eq)]
888pub struct AfuFile {
889    pub path: &'static str,
890    pub text: String,
891}
892
893/// Every file of the accelerator under `cfg`.
894pub fn afu_sources(cfg: &AfuConfig) -> Vec<AfuFile> {
895    assert!(cfg.compute_divide == 1 || (cfg.compute_divide >= 2 && cfg.compute_divide % 2 == 0), "a compute clock divided by {}", cfg.compute_divide);
896    assert!(cfg.max_burst.is_power_of_two() && cfg.max_burst <= 1 << (BANK_BURST_BITS - 1), "{} lines a burst", cfg.max_burst);
897    assert!(1u32 << cfg.read_fifo_log2 >= 2 * cfg.max_burst, "a read-data FIFO holds two bursts");
898    if cfg.engine.is_some() {
899        assert!(cfg.compute_divide >= ENGINE_MIN_DIVIDE, "an engine's clock divided by {}, under {ENGINE_MIN_DIVIDE}", cfg.compute_divide);
900    }
901    if cfg.hybrid() {
902        assert!(!cfg.line.secded(), "a hybrid engine's streaming unit reads lines without SECDED verdicts");
903    }
904    let file = |path, text| AfuFile { path, text };
905    let mut files = vec![
906        file("hw/rtl/sources.txt", sources_txt(cfg)),
907        file("hw/rtl/logos_d5005.json", afu_json(cfg)),
908        file("hw/rtl/ofs_plat_afu.sv", ofs_plat_afu(cfg)),
909        file("hw/rtl/logos_d5005_shell.v", shell_text(cfg.engine.as_ref(), cfg.compute_divide, cfg.contexts())),
910        file("hw/rtl/logos_dcfifo.v", dcfifo_verilog()),
911        file("hw/rtl/logos_secded.v", secded_verilog()),
912        file("hw/rtl/logos_stream.v", STREAM.to_string()),
913        file("hw/rtl/logos_csr.v", csr_verilog()),
914        file("hw/rtl/logos_bringup_core.v", core_contexts(&core_verilog(), cfg.contexts())),
915        file("hw/rtl/logos_d5005.sdc", sdc_text(cfg, &shell_text(cfg.engine.as_ref(), cfg.compute_divide, cfg.contexts()))),
916        file(CLOCK_DOMAINS_FILE, clock_domains_json(cfg)),
917        file("sw/logos_d5005.h", c_header(cfg)),
918        file("sw/logos_hal.h", HAL_H.to_string()),
919        file("sw/logos_hal_opae.c", HAL_OPAE.to_string()),
920        file("sw/logos_hal_pci.c", HAL_PCI.to_string()),
921        file("sw/logos_core.h", CORE_H.to_string()),
922        file("sw/logos_receipt.c", RECEIPT.to_string()),
923        file("sw/logos_load.c", LOAD.to_string()),
924        file("sim/logos_hal_sim.h", HAL_SIM.to_string()),
925        file("sim/logos_hal_vpi.c", HAL_VPI.to_string()),
926        file("sim/logos_hal_dpi.c", HAL_DPI.to_string()),
927        file("sim/logos_d5005_cosim.v", cosim_verilog(cfg)),
928        file("sim/logos_avmm_memory_model.v", MEMORY_MODEL.to_string()),
929        file("sim/logos_ddr4_channel_model.v", verilog_model(&Ddr4Grade::D5005_EMIF.at(), LOOKAHEAD)),
930    ];
931    if cfg.compute_divide > 1 {
932        files.push(file("hw/rtl/logos_compute_clock.v", COMPUTE_CLOCK_VERILOG.to_string()));
933    }
934    if let Some(plan) = &cfg.floorplan {
935        files.push(file("hw/rtl/logos_floorplan.tcl", plan.clone()));
936    }
937    if let Some(p) = &cfg.engine {
938        assert_eq!(cfg.line.bits(), 512, "the engine's card port moves lines of 64 data bytes");
939        files.push(file("hw/rtl/logos_card_port.v", card_port_verilog()));
940        files.push(file("hw/rtl/logos_settings.tcl", ENGINE_SETTINGS.to_string()));
941        let engine = engine_verilog(p);
942        files.push(file("sim/logos_engine_gated.v", simulation_twin(&engine)));
943        files.push(file("hw/rtl/logos_engine.v", engine));
944        if let Some(hard) = p.streaming.as_ref().and_then(|s| s.hard_float) {
945            files.push(file("sim/logos_fp_mac_model.v", hard_float_model(&hard)));
946        }
947        files.push(file("sw/logos_step.c", STEP.to_string()));
948    }
949    files
950}
951
952/// The shell, its compute side stepping at the user clock divided by `divide` ([`compute_step`]), with the engine and
953/// its card port behind the core when `engine` ([`engine_shell`]); a hybrid engine's with every channel's fast port
954/// ([`fast_channels`]).
955fn shell_text(engine: Option<&EngineParams>, divide: u32, contexts: usize) -> String {
956    let text = SHELL
957        .replace("/*ENGINE_PARAM*/", &format!("  localparam ENGINE = {};", engine.is_some() as u32))
958        .replace("/*COMPUTE*/", &compute_step(divide))
959        .replace("/*ENGINE*/", if engine.is_some() { SHELL_ENGINE } else { SHELL_NO_ENGINE });
960    let text = if contexts > 1 {
961        assert_eq!(text.matches("  wire [15:0]   eng_rows;\n").count(), 1, "the shell's engine rows");
962        text.replace("  wire [15:0]   eng_rows;\n", &format!("  wire [{}:0]   eng_rows;\n", 16 * contexts - 1))
963    } else {
964        text
965    };
966    let shell = match engine {
967        Some(p) if p.streaming.is_some() => fast_channels(&engine_shell(&text), p.streaming.as_ref().is_some_and(|s| s.recurrence)),
968        Some(_) => engine_shell(&text),
969        None => text,
970    };
971    if engine.and_then(|p| p.streaming.as_ref()).is_some_and(|s| s.set.pe_pairs) {
972        pe_pairs_shell(&shell)
973    } else {
974        shell
975    }
976}
977
978/// The shell `text` of an engine whose streaming unit pairs its cores ([`AfuConfig::pe_pairs`]): `uclk2`, twice `uclk`,
979/// their rising edges together, given to the engine for its pairs' processing elements.
980fn pe_pairs_shell(text: &str) -> String {
981    edited(
982        text,
983        &[
984            (
985                "  input                  uclk,\n  input                  uclk_rst_n,\n",
986                "  input                  uclk,\n  input                  uclk_rst_n,\n  input                  uclk2,            // twice uclk, their rising edges together\n",
987            ),
988            (".fclk(uclk), .frst(frst),", ".fclk(uclk), .clk2(uclk2), .frst(frst),"),
989        ],
990    )
991}
992
993/// The shell of a hybrid engine build at the engine's divide, with the fast ports' writes when `recurrence`: the shell
994/// [`afu_sources`] writes for an engine whose streaming unit is built so.
995pub fn hybrid_shell_verilog(recurrence: bool) -> String {
996    let text = SHELL
997        .replace("/*ENGINE_PARAM*/", "  localparam ENGINE = 1;")
998        .replace("/*COMPUTE*/", &compute_step(ENGINE_COMPUTE_DIVIDE))
999        .replace("/*ENGINE*/", SHELL_ENGINE);
1000    fast_channels(&engine_shell(&text), recurrence)
1001}
1002
1003/// The shell's compute step: `cce` high in the user cycles that end with the rising edges of the user clock divided by
1004/// `divide`, `fmid` in those that end with its falling edges, and `crst_n` the compute side's reset, released at the
1005/// second rising edge ([`COMPUTE_CLOCK_VERILOG`], its divided clock's level read by nothing), the marks and the release
1006/// [`MARK_LEAD`] user clocks on through registers placed beside their loads; every user cycle, from the user clock's
1007/// reset, undivided.
1008fn compute_step(divide: u32) -> String {
1009    if divide == 1 {
1010        return "  // The compute side steps every user cycle, from the user clock's reset.\n  wire cce = 1'b1;\n  wire crst_n = uclk_rst_n;".to_string();
1011    }
1012    format!(
1013        "  // The compute side's step: the user cycles that end with the rising edges of the user clock divided by {divide},\n  \
1014         // those that end with its falling edges, and the compute side's reset, released at the second rising edge; the\n  \
1015         // divided clock's level (cphase) clocks and feeds nothing.\n  wire cce, fmid, crst_n, cphase;\n  \
1016         logos_compute_clock #(.DIVIDE({divide}), .LEAD({MARK_LEAD})) compute_clock(.clk(uclk), .rst_n(uclk_rst_n), .cclk(cphase),\n    \
1017         .cclk_rst_n(crst_n), .fedge(cce), .fmid(fmid));"
1018    )
1019}
1020
1021/// The user clocks a divided build's compute-clock marks and compute-side reset release come out of the divider late:
1022/// registers of no reset between the divider and the marks' tens of thousands of loads, which the retimer may move
1023/// along the wire and the last of which Quartus copies beside them.
1024pub const MARK_LEAD: u32 = 2;
1025
1026/// `text` with every `(from, to)` of `edits` made, each `from` there exactly once.
1027fn edited(text: &str, edits: &[(&str, &str)]) -> String {
1028    let mut out = text.to_string();
1029    for (from, to) in edits {
1030        assert_eq!(out.matches(from).count(), 1, "the shell's `{from}`");
1031        out = out.replace(from, to);
1032    }
1033    out
1034}
1035
1036/// The shell `text` of an engine build: the engine given the user clock it steps in, from the user clock's reset two
1037/// registers on, the last copied by Quartus a copy each 256 of its loads, so no reset wire crosses the die, and the
1038/// compute clock's marks of the user cycles that end with its rising and falling edges.
1039fn engine_shell(text: &str) -> String {
1040    edited(
1041        text,
1042        &[
1043            ("  logos_engine engine(", FAST_RESET),
1044            ("    .mem_wr_ready(e_wr_ready));\n", "    .mem_wr_ready(e_wr_ready), .fclk(uclk), .frst(frst), .fedge(cce), .fmid(fmid));\n"),
1045        ],
1046    )
1047}
1048
1049/// The engine's user-clock side's reset, ahead of the engine's instance: the user clock's reset two registers on, the
1050/// second to be copied a copy each 256 loads.
1051const FAST_RESET: &str = "  // The engine's user-clock side's reset, the user clock's two clocks on; Quartus copies the last register a copy each\n  \
1052                          // 256 of its loads, each placed beside them.\n  reg frst_a;\n  (* maxfan = 256 *) reg frst;\n  \
1053                          always @(posedge uclk) begin frst_a <= !uclk_rst_n; frst <= frst_a; end\n  logos_engine engine(";
1054
1055/// The shell `text` for a hybrid engine: each channel a fast port — a descriptor FIFO into the channel's clock and a
1056/// read-data FIFO out of it, both the user clock's on the engine's side — its stream engine serving the core's FIFOs
1057/// and the fast port's one side at a time: idle, with nothing offered from the side it serves and a descriptor offered
1058/// from the other, it turns to the other, and a line read goes back through the side that asked. With `recurrence`
1059/// the fast port writes as well ([`fast_writes`]).
1060fn fast_channels(text: &str, recurrence: bool) -> String {
1061    let edits: [(&str, &str); 8] = [
1062        (
1063            "// a whole run of bursts the channel has in flight. The FIFOs' sides toward the core step at its step (uce).\n",
1064            "// a whole run of bursts the channel has in flight. The FIFOs' sides toward the core step at its step (uce). A hybrid\n\
1065             // engine's streaming unit reads the channel through a fast port: FIFOs of its own in the fast clock, every cycle\n\
1066             // of it, the stream engine serving one side at a time, turning only idle.\n",
1067        ),
1068        (
1069            "  input              mdone_ready,\n",
1070            "  input              mdone_ready,\n  input              fclk,\n  input              fclk_rst_n,\n  input              fd_valid,\n  \
1071             output             fd_ready,\n  input  [AW+LW:0]   fd_data,\n  output             fr_valid,\n  input              fr_ready,\n  \
1072             output [DWL-1:0]   fr_data,\n",
1073        ),
1074        (
1075            "  logos_dcfifo_mlab #(.W(DLW), .AW(2), .HOLDS(1)) desc_fifo(",
1076            "  // The core's side of the stream engine's descriptors and lines, and the fast port's.\n  \
1077             wire           sd_valid, sd_ready, fq_valid, fq_ready, sr_push, fr_push, fr_wready;\n  wire [DLW-1:0] sd_data, fq_data;\n  \
1078             wire [RAW:0]   sr_room, fr_room, fr_used;\n  wire [2:0]     fd_wfree, fd_rused;\n  \
1079             logos_dcfifo_mlab #(.W(DLW), .AW(2), .HOLDS(1)) desc_fifo(",
1080        ),
1081        (".rvalid(d_valid),\n    .rready(d_ready), .rdata(d_data), .rused(d_rused));", ".rvalid(sd_valid),\n    .rready(sd_ready), .rdata(sd_data), .rused(d_rused));"),
1082        (
1083            "read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(r_push),\n    .wready(r_wready), .wdata(r_word), .wfree(r_room),",
1084            "read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(sr_push),\n    .wready(r_wready), .wdata(r_word), .wfree(sr_room),",
1085        ),
1086        (
1087            "  wire [RAW:0] owed;\n  wire         busy;\n",
1088            r#"  wire [RAW:0] owed;
1089  wire         busy;
1090  // The fast port: its descriptors and lines through FIFOs of their own; the side the stream engine serves. The
1091  // descriptors' writer is the channel multiplexer, which may offer another port's descriptor at the step after one is
1092  // refused, the refused one still held at its port: a writer that holds no one word, so its FIFO says neither.
1093  logos_dcfifo_mlab #(.W(DLW), .AW(2)) fast_desc_fifo(.wclk(fclk), .wce(1'b1), .wrst_n(fclk_rst_n), .wvalid(fd_valid),
1094    .wready(fd_ready), .wdata(fd_data), .wfree(fd_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n), .rvalid(fq_valid),
1095    .rready(fq_ready), .rdata(fq_data), .rused(fd_rused));
1096  logos_dcfifo_m20k #(.W(DWL), .AW(RAW), .HOLDS(1)) fast_read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(fr_push),
1097    .wready(fr_wready), .wdata(r_data), .wfree(fr_room), .rclk(fclk), .rce(1'b1), .rrst_n(fclk_rst_n), .rvalid(fr_valid),
1098    .rready(fr_ready), .rdata(fr_data), .rused(fr_used));
1099  reg            fast;
1100  always @(posedge mem_clk or negedge mem_rst_n)
1101    if (!mem_rst_n) fast <= 1'b0;
1102    else if (!busy && !(fast ? fq_valid : sd_valid) && (fast ? sd_valid : fq_valid)) fast <= !fast;
1103  assign d_valid = fast ? fq_valid : sd_valid;
1104  assign d_data = fast ? fq_data : sd_data;
1105  assign sd_ready = d_ready && !fast;
1106  assign fq_ready = d_ready && fast;
1107  assign sr_push = r_push && !fast;
1108  assign fr_push = r_push && fast;
1109  assign r_room = fast ? fr_room : sr_room;
1110"#,
1111        ),
1112        (
1113            "  wire [NB*21-1:0]  mw_flip;\n\n  genvar b;\n",
1114            "  wire [NB*21-1:0]  mw_flip;\n  // Every channel's fast port, the engine's streaming unit's.\n  wire [NB-1:0]     f_md_valid, f_md_ready, f_mr_valid, f_mr_ready;\n  \
1115             wire [NB*DLW-1:0] f_md_data;\n  wire [NB*DWL-1:0] f_mr_data;\n\n  genvar b;\n",
1116        ),
1117        (
1118            "      .mdone_valid(mdone_valid[b]), .mdone_ready(mdone_ready[b]),\n",
1119            "      .mdone_valid(mdone_valid[b]), .mdone_ready(mdone_ready[b]),\n      .fclk(uclk), .fclk_rst_n(uclk_rst_n), .fd_valid(f_md_valid[b]), \
1120             .fd_ready(f_md_ready[b]),\n      .fd_data(f_md_data[b*DLW +: DLW]), .fr_valid(f_mr_valid[b]), .fr_ready(f_mr_ready[b]),\n      \
1121             .fr_data(f_mr_data[b*DWL +: DWL]),\n",
1122        ),
1123    ];
1124    let out = edited(text, &edits);
1125    let out = edited(
1126        &out,
1127        &[(
1128            "    .mem_wr_ready(e_wr_ready), .fclk(uclk), .frst(frst), .fedge(cce), .fmid(fmid));\n",
1129            "    .mem_wr_ready(e_wr_ready), .fclk(uclk), .frst(frst), .fedge(cce), .fmid(fmid), .sm_md_valid(f_md_valid),\n    \
1130             .sm_md_ready(f_md_ready), .sm_md_data(f_md_data), .sm_mr_valid(f_mr_valid), .sm_mr_ready(f_mr_ready), .sm_mr_data(f_mr_data));\n",
1131        )],
1132    );
1133    if recurrence {
1134        fast_writes(&out)
1135    } else {
1136        out
1137    }
1138}
1139
1140/// The hybrid shell `text` ([`fast_channels`]) with each channel's fast port writing too: its lines through a FIFO of
1141/// their own into the channel's clock and its writes' done tokens back through another, the stream engine's writer
1142/// taking the lines of the side it serves and a write done going back to that side; and the engine given the fast
1143/// ports' writes and the card port's map, which its fast side's port places its lines by.
1144fn fast_writes(text: &str) -> String {
1145    edited(
1146        text,
1147        &[
1148            (
1149                "// of it, the stream engine serving one side at a time, turning only idle.\n",
1150                "// of it, the stream engine serving one side at a time, turning only idle. Its recurrence unit writes the channel\n\
1151                 // through the fast port too, a write's done token back to the side that wrote.\n",
1152            ),
1153            (
1154                "  output [DWL-1:0]   fr_data,\n",
1155                "  output [DWL-1:0]   fr_data,\n  input              fw_valid,\n  output             fw_ready,\n  input  [DWL-1:0]   fw_data,\n  \
1156                 output             fdone_valid,\n  input              fdone_ready,\n",
1157            ),
1158            (".wvalid(done),\n    .wready(done_wready)", ".wvalid(done_core),\n    .wready(done_wready)"),
1159            ("  wire           r_wready, done_wready, done_token;\n", "  wire           r_wready, done_wready, done_token, done_core;\n"),
1160            (
1161                ".w_valid(w_valid), .w_ready(w_ready),\n    .w_data(w_data), .w_flip(w_flip),",
1162                ".w_valid(e_w_valid), .w_ready(e_w_ready),\n    .w_data(e_w_data), .w_flip(e_w_flip),",
1163            ),
1164            ("  assign r_room = fast ? fr_room : sr_room;\n", FAST_WRITES),
1165            (
1166                "  wire [NB*DLW-1:0] f_md_data;\n  wire [NB*DWL-1:0] f_mr_data;\n",
1167                "  wire [NB*DLW-1:0] f_md_data;\n  wire [NB*DWL-1:0] f_mr_data;\n  wire [NB-1:0]     f_mw_valid, f_mw_ready, f_mdone_valid, f_mdone_ready;\n  \
1168                 wire [NB*DWL-1:0] f_mw_data;\n",
1169            ),
1170            (
1171                "      .fr_data(f_mr_data[b*DWL +: DWL]),\n",
1172                "      .fr_data(f_mr_data[b*DWL +: DWL]), .fw_valid(f_mw_valid[b]), .fw_ready(f_mw_ready[b]),\n      \
1173                 .fw_data(f_mw_data[b*DWL +: DWL]), .fdone_valid(f_mdone_valid[b]), .fdone_ready(f_mdone_ready[b]),\n",
1174            ),
1175            (
1176                ".sm_mr_valid(f_mr_valid), .sm_mr_ready(f_mr_ready), .sm_mr_data(f_mr_data));\n",
1177                ".sm_mr_valid(f_mr_valid), .sm_mr_ready(f_mr_ready), .sm_mr_data(f_mr_data),\n    \
1178                 .sm_mw_valid(f_mw_valid), .sm_mw_ready(f_mw_ready), .sm_mw_data(f_mw_data), .sm_mdone_valid(f_mdone_valid),\n    \
1179                 .sm_mdone_ready(f_mdone_ready), .map_space(port_space), .map_base(port_base), .map_granule_log2(port_granule_log2));\n",
1180            ),
1181        ],
1182    )
1183}
1184
1185/// A channel's fast writes ([`fast_writes`]): the FIFOs and the writer's side.
1186const FAST_WRITES: &str = r#"  assign r_room = fast ? fr_room : sr_room;
1187  // The fast port's lines to write through a FIFO of their own and its writes' done tokens back through another: the
1188  // stream engine's writer takes the lines of the side it serves, and a write done goes back to that side.
1189  wire           fw_q_valid, fw_q_ready, fdone_wready, fdone_token, e_w_valid, e_w_ready;
1190  wire [DWL-1:0] fw_q_data, e_w_data;
1191  wire [20:0]    e_w_flip;
1192  wire [WAW:0]   fw_wfree, fw_rused;
1193  wire [2:0]     fdone_wfree, fdone_rused;
1194  logos_dcfifo_m20k #(.W(DWL), .AW(WAW), .HOLDS(1)) fast_write_fifo(.wclk(fclk), .wce(1'b1), .wrst_n(fclk_rst_n), .wvalid(fw_valid),
1195    .wready(fw_ready), .wdata(fw_data), .wfree(fw_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n),
1196    .rvalid(fw_q_valid), .rready(fw_q_ready), .rdata(fw_q_data), .rused(fw_rused));
1197  logos_dcfifo_mlab #(.W(1), .AW(2), .TOKENS(1)) fast_done_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(done && fast),
1198    .wready(fdone_wready), .wdata(1'b1), .wfree(fdone_wfree), .rclk(fclk), .rce(1'b1), .rrst_n(fclk_rst_n),
1199    .rvalid(fdone_valid), .rready(fdone_ready), .rdata(fdone_token), .rused(fdone_rused));
1200  assign e_w_valid = fast ? fw_q_valid : w_valid;
1201  assign e_w_data = fast ? fw_q_data : w_data;
1202  assign e_w_flip = fast ? 21'h0 : w_flip;
1203  assign w_ready = e_w_ready && !fast;
1204  assign fw_q_ready = e_w_ready && fast;
1205  assign done_core = done && !fast;
1206"#;
1207
1208/// The user clock's period in the co-simulation, picoseconds: the clock the AFU JSON asks for, the shell dividing it as
1209/// the build divides it, so a divided build's compute side meets its memory channels at the ratio it does on the card.
1210pub fn cosim_user_clock_ps(cfg: &AfuConfig) -> u64 {
1211    (1e6 / cfg.user_clock_mhz as f64).round() as u64 / 2 * 2
1212}
1213
1214/// The module that stands behind channel `b` of the co-simulation, and its parameters.
1215fn channel_model(model: ChannelModel) -> &'static str {
1216    match model {
1217        ChannelModel::Random => "logos_avmm_memory_model #(.AW(AW), .DW(DWM), .BW(MBW), .LINES_LOG2(20), .SEED(100 + b))",
1218        ChannelModel::Ddr4 => "logos_ddr4_channel_model #(.AW(AW), .DW(DWM), .BW(MBW), .LINES_LOG2(20), .LOG(DDR4_LOG), .ID(b))",
1219    }
1220}
1221
1222fn cosim_verilog(cfg: &AfuConfig) -> String {
1223    [
1224        ("{mem_bits}", cfg.line.bus_bits().to_string()),
1225        ("{ecc}", (cfg.line.secded() as u32).to_string()),
1226        ("{id_l}", format!("{:016x}", cfg.uuid as u64)),
1227        ("{id_h}", format!("{:016x}", (cfg.uuid >> 64) as u64)),
1228        ("{build}", format!("{:016x}", cfg.build)),
1229        ("{uclk_ps}", cosim_user_clock_ps(cfg).to_string()),
1230        ("{divide}", cfg.compute_divide.to_string()),
1231        ("{channel_model}", channel_model(cfg.channel_model).to_string()),
1232        (
1233            "{uclk2_clock}",
1234            if cfg.pe_pairs() {
1235                assert_eq!(cosim_user_clock_ps(cfg) % 4, 0, "a paired engine's twice clock takes a quarter of the user period");
1236                "  // The paired processing elements' clock, twice uclk, its rising edges on uclk's and between them.\n  \
1237                 reg uclk2 = 1;\n  always #(UCLK_PS / 4) uclk2 = ~uclk2;\n"
1238                    .to_string()
1239            } else {
1240                String::new()
1241            },
1242        ),
1243        ("{uclk2_cosim}", if cfg.pe_pairs() { " .uclk2(uclk2),".to_string() } else { String::new() }),
1244    ]
1245    .iter()
1246    .fold(COSIM_TEMPLATE.to_string(), |text, (marker, value)| text.replace(marker, value))
1247}
1248
1249/// The Verilog every simulation of the shell reads: the shell and all it instantiates, in order.
1250pub fn shell_verilog() -> Vec<(&'static str, String)> {
1251    vec![
1252        ("logos_d5005_shell.v", shell_text(None, 1, 1)),
1253        ("logos_dcfifo.v", dcfifo_verilog()),
1254        ("logos_secded.v", secded_verilog()),
1255        ("logos_stream.v", STREAM.to_string()),
1256        ("logos_csr.v", csr_verilog()),
1257        ("logos_bringup_core.v", core_verilog()),
1258    ]
1259}
1260
1261/// The files `afu_synth_setup` reads, in the order Quartus compiles them.
1262fn sources_txt(cfg: &AfuConfig) -> String {
1263    let engine: &[&str] = if cfg.engine.is_some() { &["logos_engine.v", "logos_card_port.v"] } else { &[] };
1264    let divider: &[&str] = if cfg.compute_divide > 1 { &["logos_compute_clock.v"] } else { &[] };
1265    let floorplan: &[&str] = if cfg.floorplan.is_some() { &["QI:logos_floorplan.tcl"] } else { &[] };
1266    let settings: &[&str] = if cfg.engine.is_some() { &["QI:logos_settings.tcl"] } else { &[] };
1267    let exercisers = format!("QI:{FIM_STATIC_EXERCISERS}");
1268    let flat: &[&str] = if cfg.flat { &[exercisers.as_str()] } else { &[] };
1269    ["logos_d5005.json", "logos_dcfifo.v", "logos_secded.v", "logos_stream.v", "logos_csr.v", "logos_bringup_core.v"]
1270        .iter()
1271        .chain(engine)
1272        .chain(divider)
1273        .chain(&["logos_d5005_shell.v", "ofs_plat_afu.sv", "logos_d5005.sdc"])
1274        .chain(settings)
1275        .chain(floorplan)
1276        .chain(flat)
1277        .map(|f| format!("{f}\n"))
1278        .collect()
1279}
1280
1281/// An engine build's project settings, as Tcl its build sources (`hw/rtl/logos_settings.tcl`, named in sources.txt as
1282/// `QI:`). The engine's memories of thirty-two words or fewer read without a register after them are in MLABs checking
1283/// no read-during-write (`rtl::ram::RAMSTYLE`, `LOGOS_RAMSTYLE_ASYNC`), which Quartus Prime Pro otherwise leaves untimed
1284/// between an MLAB's write and a read of the same word: timed, a word read in the clock after its write is the one
1285/// written, as the RTL reads it.
1286pub const ENGINE_SETTINGS: &str = "# The engine's MLABs read without a register after them: an MLAB's write and a read of the same word timed, so a word
1287# read in the clock after its write is the one written, as the RTL reads it.
1288set_global_assignment -name MLAB_ADD_TIMING_CONSTRAINTS_FOR_MIXED_PORT_FEED_THROUGH_MODE_SETTING_DONT_CARE ON
1289";
1290
1291/// The SDC: the FIFO crossings, and an engine build's engine stepping in the user clock at the compute clock's edges
1292/// ([`ENGINE_SDC`]). No clock is generated: every clock is the platform's.
1293fn sdc_text(cfg: &AfuConfig, shell: &str) -> String {
1294    let step = if cfg.compute_divide > 1 { compute_step_sdc(cfg.compute_divide, shell) } else { String::new() };
1295    if cfg.engine.is_none() {
1296        return format!("{SDC}{step}");
1297    }
1298    let d = cfg.compute_divide;
1299    let engine = [("{half}", d / 2 - 1), ("{half_hold}", d / 2 - 2), ("{held}", d / 2 - 2), ("{held_hold}", d / 2 - 3)]
1300        .iter()
1301        .fold(ENGINE_SDC.to_string(), |t, (m, v)| t.replace(m, &v.to_string()));
1302    format!("{SDC}{step}{engine}")
1303}
1304
1305/// The sides of the shell's dual-clock FIFOs that step at the compute clock's step, from the shell's text: each FIFO
1306/// instance's name and its side, `'w'` its writes' and `'r'` its reads', clocked by the user clock under the compute
1307/// side's enable (`cce`, a channel's `uce`). Every other side runs on its own clock or every user cycle.
1308pub fn compute_fifo_sides(shell: &str) -> Vec<(String, char)> {
1309    let mut sides = Vec::new();
1310    for (at, _) in shell.match_indices("logos_dcfifo_") {
1311        let text = &shell[at..];
1312        let text = &text[..text.find(");").unwrap_or(text.len())];
1313        let Some(open) = text.find(") ") else { continue };
1314        let name: String = text[open + 2..].chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect();
1315        let port = |p: &str| text.find(&format!(".{p}(")).map(|i| text[i + p.len() + 2..].split(')').next().unwrap_or("").trim().to_string());
1316        let compute = |clk: &str, ce: &str| port(clk).as_deref() == Some("uclk") && matches!(port(ce).as_deref(), Some("cce") | Some("uce"));
1317        if name.is_empty() {
1318            continue;
1319        }
1320        if compute("wclk", "wce") {
1321            sides.push((name.clone(), 'w'));
1322        }
1323        if compute("rclk", "rce") {
1324            sides.push((name, 'r'));
1325        }
1326    }
1327    sides.sort();
1328    sides.dedup();
1329    sides
1330}
1331
1332/// The registers of a FIFO's side: its pointer, the other side's pointer crossing in and its binary, its RAM, and on the
1333/// read side the word and valid it holds.
1334pub const FIFO_SIDE_REGISTERS: [(char, &[&str]); 2] =
1335    [('w', &["wbin", "wgray", "rgray_s1", "rgray_s2", "rbin_w"]), ('r', &["rbin", "rgray", "wgray_s1", "wgray_s2", "wbin_r", "rdata_q", "rvalid_q"])];
1336
1337/// The shell's compute side in a divided build: the core, the card port and the FIFO sides [`compute_fifo_sides`] names,
1338/// every register of which steps only at the compute clock's step, `divide` user clocks apart, so a path between two of
1339/// them has that many user clocks; a RAM's ports are its side's (its write port written only at the step, its read
1340/// port read only then). A path from or to anything else — the compute clock's marks, the engine and its crossing, a
1341/// FIFO's other side, the fast side — keeps its own constraint.
1342fn compute_step_sdc(divide: u32, shell: &str) -> String {
1343    let mut sides = String::new();
1344    for (name, side) in compute_fifo_sides(shell) {
1345        sides.push_str(&format!(" {name} {side}"));
1346    }
1347    let regs = |side: char| FIFO_SIDE_REGISTERS.iter().find(|(s, _)| *s == side).map_or(String::new(), |(_, r)| r.join(" "));
1348    COMPUTE_STEP_SDC
1349        .replace("{sides}", sides.trim_start())
1350        .replace("{w_regs}", &regs('w'))
1351        .replace("{r_regs}", &regs('r'))
1352        .replace("{setup}", &divide.to_string())
1353        .replace("{hold}", &(divide - 1).to_string())
1354}
1355
1356/// The compute side's paths, each the compute clock's period.
1357const COMPUTE_STEP_SDC: &str = r##"
1358# The shell's compute side steps only at the compute clock's step: a path between two of its registers has the step's
1359# user clocks.
1360set logos_step [get_keepers -nowarn {*|shell|core|* *|shell|port|*}]
1361foreach {name side} {{sides}} {
1362  foreach entity {logos_dcfifo_mlab logos_dcfifo_m20k} {
1363    foreach inst [get_entity_instances $entity] {
1364      if {![string match "*|$name" $inst]} { continue }
1365      set regs [expr {$side eq "w" ? {{w_regs}} : {{r_regs}}}]
1366      foreach reg $regs {
1367        set logos_step [add_to_collection $logos_step [get_keepers -nowarn $inst|$reg]]
1368        set logos_step [add_to_collection $logos_step [get_keepers -nowarn $inst|$reg[*]]]
1369      }
1370      set logos_step [add_to_collection $logos_step [get_keepers -nowarn $inst|mem*]]
1371    }
1372  }
1373}
1374if {[get_collection_size $logos_step] > 0} {
1375  set_multicycle_path -from $logos_step -to $logos_step -setup -end {setup}
1376  set_multicycle_path -from $logos_step -to $logos_step -hold -end {hold}
1377}
1378"##;
1379
1380/// The user clock request: the fitter's best up to the clock asked for, or exactly it for a divided build.
1381/// The user clocks the AFU JSON asks for, {high, low}: auto up to the bring-up build's target, or a divided build's
1382/// user clock and its half, the pair the PIM's `user_clock_config.tcl` takes (a fixed high clock with an auto low one
1383/// fails its build).
1384fn clock_request(cfg: &AfuConfig) -> (String, String) {
1385    if cfg.pe_pairs() {
1386        // The user side in the low clock, the paired processing elements in the high one, twice it.
1387        return ((2 * cfg.user_clock_mhz).to_string(), cfg.user_clock_mhz.to_string());
1388    }
1389    if cfg.compute_divide == 1 {
1390        (format!("\"auto-{}\"", cfg.user_clock_mhz), "\"auto\"".to_string())
1391    } else {
1392        let low = if cfg.user_clock_mhz % 2 == 0 { (cfg.user_clock_mhz / 2).to_string() } else { format!("{}.5", cfg.user_clock_mhz / 2) };
1393        (cfg.user_clock_mhz.to_string(), low)
1394    }
1395}
1396
1397fn afu_json(cfg: &AfuConfig) -> String {
1398    format!(
1399        "{{\n  \"version\": 1,\n  \"afu-image\": {{\n    \"power\": 0,\n    \"clock-frequency-high\": {high},\n    \
1400         \"clock-frequency-low\": {low},\n    \"afu-top-interface\": {{\n      \"class\": \"ofs_plat_afu\"\n    }},\n    \
1401         \"accelerator-clusters\": [\n      {{\n        \"name\": \"{name}\",\n        \"total-contexts\": 1,\n        \
1402         \"accelerator-type-uuid\": \"{uuid}\"\n      }}\n    ]\n  }}\n}}\n",
1403        high = clock_request(cfg).0,
1404        low = clock_request(cfg).1,
1405        name = cfg.name,
1406        uuid = cfg.uuid_text(),
1407    )
1408}
1409
1410fn ofs_plat_afu(cfg: &AfuConfig) -> String {
1411    let lanes = match cfg.line {
1412        LocalMemoryLine::Data512 => "its data bits",
1413        LocalMemoryLine::DataAndEcc576 => "its ECC lanes carried as data",
1414        LocalMemoryLine::Data512Secded => "its ECC lanes holding each line's SECDED check bytes",
1415    };
1416    [
1417        ("{lanes}", lanes.to_string()),
1418        ("{mem_params}", cfg.line.pim_params().to_string()),
1419        ("{mem_width}", cfg.line.pim_width().to_string()),
1420        ("{ecc}", (cfg.line.secded() as u32).to_string()),
1421        ("{host_burst_bits}", BANK_BURST_BITS.to_string()),
1422        ("{max_burst}", cfg.max_burst.to_string()),
1423        ("{read_fifo_log2}", cfg.read_fifo_log2.to_string()),
1424        ("{write_fifo_log2}", cfg.write_fifo_log2.to_string()),
1425        ("{id_l}", format!("{:016x}", cfg.uuid as u64)),
1426        ("{id_h}", format!("{:016x}", (cfg.uuid >> 64) as u64)),
1427        ("{build}", format!("{:016x}", cfg.build)),
1428        ("{user_clocks}", user_clocks(cfg).to_string()),
1429        ("{uclk2}", if cfg.pe_pairs() { " .uclk2(uclk2),".to_string() } else { String::new() }),
1430    ]
1431    .iter()
1432    .fold(AFU_TEMPLATE.to_string(), |text, (marker, value)| text.replace(marker, value))
1433}
1434
1435/// The PIM's user clocks the accelerator runs on: `uclk` its user side's, and for an engine whose cores are paired
1436/// ([`AfuConfig::pe_pairs`]) `uclk2`, twice it, one PLL's two outputs, their rising edges together: `uClk_usrDiv2` and
1437/// `uClk_usr`; otherwise `uClk_usr` alone.
1438fn user_clocks(cfg: &AfuConfig) -> &'static str {
1439    if cfg.pe_pairs() {
1440        "  // The user side in uClk_usrDiv2 and the engine's paired processing elements in uClk_usr, twice it: one PLL's two\n  \
1441         // outputs, their rising edges together.\n  wire uclk = plat_ifc.clocks.ports[0].uClk_usrDiv2.clk;\n  \
1442         wire uclk_rst_n = plat_ifc.clocks.ports[0].uClk_usrDiv2.reset_n;\n  wire uclk2 = plat_ifc.clocks.ports[0].uClk_usr.clk;"
1443    } else {
1444        "  wire uclk = plat_ifc.clocks.ports[0].uClk_usr.clk;\n  wire uclk_rst_n = plat_ifc.clocks.ports[0].uClk_usr.reset_n;"
1445    }
1446}
1447
1448/// The user clock divided by `DIVIDE` (even), as marks in the user clock: a register toggled every `DIVIDE / 2` user
1449/// clock edges (the divided clock, a level that clocks nothing); `fedge` and `fmid` high in the user cycles that end
1450/// with its rising and falling edges, where the shell's compute side and an engine's core step and an engine's step
1451/// boundary takes the engine's values; and a reset asserted with the user clock's and released in the user clock at the
1452/// second divided rising edge after it.
1453pub const COMPUTE_CLOCK_VERILOG: &str = r#"module logos_compute_clock #(parameter DIVIDE = 2, parameter LEAD = 0)
1454  (input clk, input rst_n, output reg cclk, output cclk_rst_n, output fedge, output fmid);
1455  localparam W = (DIVIDE > 2) ? $clog2(DIVIDE / 2) : 1;
1456  localparam [W-1:0] LAST = DIVIDE / 2 - 1;
1457  reg [W-1:0] count;
1458  always @(posedge clk or negedge rst_n)
1459    if (!rst_n) begin count <= {W{1'b0}}; cclk <= 1'b0; end
1460    else if (count == LAST) begin count <= {W{1'b0}}; cclk <= ~cclk; end
1461    else count <= count + 1'b1;
1462  // The marks registered, each the next cycle's, so an enable of the whole engine leaves a register.
1463  wire [W-1:0] count_next = (count == LAST) ? {W{1'b0}} : count + 1'b1;
1464  wire cclk_next = (count == LAST) ? ~cclk : cclk;
1465  (* maxfan = 2000 *) reg mark_edge, mark_mid;
1466  always @(posedge clk or negedge rst_n)
1467    if (!rst_n) begin mark_edge <= LAST == {W{1'b0}}; mark_mid <= 1'b0; end
1468    else begin mark_edge <= count_next == LAST && !cclk_next; mark_mid <= count_next == LAST && cclk_next; end
1469  reg [1:0] release_q;
1470  always @(posedge clk or negedge rst_n)
1471    if (!rst_n) release_q <= 2'b00; else if (mark_edge) release_q <= {release_q[0], 1'b1};
1472  // LEAD > 0: the marks and the reset's release LEAD user clocks on, through registers of no reset the retimer may move
1473  // along the wire and the last of which Quartus copies beside its loads, so no mark crosses the die in one clock; the
1474  // reset still asserted the moment the user clock's is. Every consumer sees the divider LEAD clocks late, alike.
1475  generate
1476    if (LEAD == 0) begin : now
1477      assign fedge = mark_edge;
1478      assign fmid = mark_mid;
1479      assign cclk_rst_n = release_q[1];
1480    end else begin : late
1481      (* maxfan = 64 *) reg [LEAD-1:0] edge_d, mid_d;
1482      reg [LEAD-1:0] release_d;
1483      always @(posedge clk) begin
1484        edge_d <= (edge_d << 1) | {{(LEAD-1){1'b0}}, mark_edge};
1485        mid_d <= (mid_d << 1) | {{(LEAD-1){1'b0}}, mark_mid};
1486        release_d <= (release_d << 1) | {{(LEAD-1){1'b0}}, release_q[1]};
1487      end
1488      assign fedge = edge_d[LEAD-1];
1489      assign fmid = mid_d[LEAD-1];
1490      assign cclk_rst_n = release_q[1] && release_d[LEAD-1];
1491    end
1492  endgenerate
1493endmodule
1494"#;
1495
1496/// An engine build's engine, its core stepping in the user clock at the compute clock's rising edges and reading its
1497/// memories the user cycle after the falling edges: the paths out of its step boundary's held values (`held_*`), which
1498/// end in the shell's compute side, taking them only at its step, have half a compute cycle less two; those within the
1499/// core, between it and the user-clock side, and from the boundary's captures (`cap_*`) into either, half a compute
1500/// cycle less one, the least of them: from a step to the next read, from a read to the next step. The shell's paths
1501/// into the captures keep their one user cycle.
1502const ENGINE_SDC: &str = r##"
1503# The engine stepping in the user clock at the compute clock's edges, and its step boundary with the shell.
1504set logos_cap [get_keepers -nowarn {*|engine|bridge|cap_*}]
1505set logos_held [get_keepers -nowarn {*|engine|bridge|held_*}]
1506set logos_core [get_keepers -nowarn {*|engine|core|*}]
1507set logos_fast [get_keepers -nowarn {*|engine|fast|*}]
1508if {[get_collection_size $logos_held] > 0} {
1509  set_multicycle_path -from $logos_held -setup -end {held}
1510  set_multicycle_path -from $logos_held -hold -end {held_hold}
1511}
1512foreach {from to} [list $logos_cap $logos_core $logos_core $logos_core $logos_core $logos_fast $logos_fast $logos_core $logos_cap $logos_fast] {
1513  if {[get_collection_size $from] > 0 && [get_collection_size $to] > 0} {
1514    set_multicycle_path -from $from -to $to -setup -end {half}
1515    set_multicycle_path -from $from -to $to -hold -end {half_hold}
1516  }
1517}
1518"##;
1519
1520fn dcfifo_verilog() -> String {
1521    [("logos_dcfifo_mlab", "MLAB"), ("logos_dcfifo_m20k", "M20K")]
1522        .iter()
1523        .map(|(name, style)| DCFIFO.replace("MODULE_NAME", name).replace("RAMSTYLE", style))
1524        .collect::<Vec<_>>()
1525        .join("\n")
1526}
1527
1528fn csr_verilog() -> String {
1529    let map: Vec<String> = REGISTERS.iter().map(|r| format!("{} = {}", r.name, r.word)).collect();
1530    CSR.replace("/*REGISTER_MAP*/", &format!("localparam [MAW-1:0] {};", map.join(", ")))
1531}
1532
1533/// The bring-up core `text` ([`core_verilog`]'s) for an engine of `contexts` sequencer contexts: ENGINE_CONFIG takes
1534/// context c's token-rows at arg0[32 + 16(c − 1) +: 16] beside context 0's, and ENGINE_TABLE and STORE_READ the context
1535/// at arg1[63:56], given the engine in its host address's top four bits; `text` itself for one.
1536fn core_contexts(text: &str, contexts: usize) -> String {
1537    if contexts == 1 {
1538        return text.to_string();
1539    }
1540    let rows: Vec<String> = (1..contexts).rev().map(|c| format!("cmd_data[{} +: 16]", 96 + 16 * (c - 1))).chain(["cmd_data[64 +: 16]".to_string()]).collect();
1541    let mut out = text.to_string();
1542    for (from, to) in [
1543        ("  output reg [15:0]         eng_rows,", format!("  output reg [{}:0]         eng_rows,", 16 * contexts - 1)),
1544        ("                  eng_rows <= cmd_data[64 +: 16];\n", format!("                  eng_rows <= {{{}}};\n", rows.join(", "))),
1545        ("                  tbase <= cmd_data[128 +: 32];\n", "                  tbase <= {cmd_data[184 +: 4], cmd_data[128 +: 28]};\n".to_string()),
1546        ("                default: sbase <= cmd_data[128 +: 32];\n", "                default: sbase <= {cmd_data[184 +: 4], cmd_data[128 +: 28]};\n".to_string()),
1547        (
1548            "//   Any other op completes at once with code 0x80,",
1549            format!(
1550                "//   With an engine of {contexts} contexts, ENGINE_CONFIG's context c's token-rows at arg0[32 + 16(c - 1) +: 16] for c >= 1,\n\
1551                 //   ENGINE_TABLE's and STORE_READ's context at arg1[63:56] (the engine's host address's top four bits), ENGINE_RUN\n\
1552                 //   starting every context with rows, its word each context's 16 bits, context 0's lowest.\n\
1553                 //   Any other op completes at once with code 0x80,"
1554            ),
1555        ),
1556    ] {
1557        assert_eq!(out.matches(from).count(), 1, "`{from}`");
1558        out = out.replace(from, &to);
1559    }
1560    out
1561}
1562
1563fn core_verilog() -> String {
1564    let ops: Vec<String> = Op::ALL.iter().map(|o| format!("{} = {}", o.name(), o.code())).collect();
1565    CORE.replace(
1566        "/*OPS*/",
1567        &format!(
1568            "localparam [7:0] {};\n  localparam [7:0] UNKNOWN_OP = 8'h{STATUS_UNKNOWN_OP:02x}, RUNNING = 8'h{STATUS_RUNNING:02x};",
1569            ops.join(", ")
1570        ),
1571    )
1572}
1573
1574/// Every shell port's clock, and the shell's parameters as the build sets them. A vector port of the memory channels is
1575/// one channel's per `width` bits.
1576fn clock_domains_json(cfg: &AfuConfig) -> String {
1577    let p = |name: &str, clock: &str| format!("    {{\"port\": \"{name}\", \"clock\": \"{clock}\"}}");
1578    let per_bank = |name: &str, width: &str| format!("    {{\"port\": \"{name}\", \"clock\": \"mem_clk\", \"per_bank\": \"{width}\"}}");
1579    let mut rows = vec![p("pclk_rst_n", "pclk"), p("uclk_rst_n", "uclk")];
1580    for port in [
1581        "mmio_address", "mmio_read", "mmio_write", "mmio_writedata", "mmio_byteenable", "mmio_waitrequest",
1582        "mmio_readdata", "mmio_readdatavalid", "host_rd_address", "host_rd_read", "host_rd_burstcount",
1583        "host_rd_waitrequest", "host_rd_readdata", "host_rd_readdatavalid", "host_wr_address", "host_wr_write",
1584        "host_wr_burstcount", "host_wr_writedata", "host_wr_byteenable", "host_wr_waitrequest",
1585        "host_wr_writeresponsevalid",
1586    ] {
1587        rows.push(p(port, "pclk"));
1588    }
1589    for (port, width) in [
1590        ("mem_rst_n", "1"), ("mem_address", "AW"), ("mem_read", "1"), ("mem_write", "1"), ("mem_burstcount", "MBW"),
1591        ("mem_writedata", "DWM"), ("mem_byteenable", "DWM/8"), ("mem_waitrequest", "1"), ("mem_readdata", "DWM"),
1592        ("mem_readdatavalid", "1"),
1593    ] {
1594        rows.push(per_bank(port, width));
1595    }
1596    if cfg.engine.is_some() {
1597        // An engine build's engine in the user clock: its core, and a hybrid engine's user-clock side, each proven
1598        // single-clock and boxed, the step boundary between the shell's compute side and the engine declared.
1599        let boxes = if cfg.hybrid() { "\"logos_engine_core_ce\", \"logos_engine_fast\"" } else { "\"logos_engine_core_ce\"" };
1600        // A paired engine's processing elements in uclk2 within the user side's box, proven apart (`cdc_check.py pair`):
1601        // two cores' (`logos_matvec_pass_x2`), or with token pairs a core's two token-rows' (`logos_matvec_pass_x2t`).
1602        let pair = if cfg.token_pairs() { "logos_matvec_pass_x2t" } else { "logos_matvec_pass_x2" };
1603        let (uclk2, pairs) = if cfg.pe_pairs() { (" \"uclk2\",".to_string(), format!("\n  \"pairs\": [\"{pair}\"],")) } else { (String::new(), String::new()) };
1604        return format!(
1605            "{{\n  \"top\": \"logos_d5005_shell\",\n  \"parameters\": {{\"DWM\": {}, \"ECC\": {}}},\n  \"clocks\": [\"pclk\", \"uclk\",{uclk2} \
1606             \"mem_clk\"],\n  \"crossings\": [\"logos_dcfifo_mlab\", \"logos_dcfifo_m20k\", \"logos_engine_bridge\"],\n  \
1607             \"single_clock\": [{boxes}],{pairs}\n  \"ports\": [\n{}\n  ]\n}}\n",
1608            cfg.line.bus_bits(),
1609            cfg.line.secded() as u32,
1610            rows.join(",\n")
1611        );
1612    }
1613    format!(
1614        "{{\n  \"top\": \"logos_d5005_shell\",\n  \"parameters\": {{\"DWM\": {}, \"ECC\": {}}},\n  \"clocks\": [\"pclk\", \"uclk\", \
1615         \"mem_clk\"],\n  \"crossings\": [\"logos_dcfifo_mlab\", \"logos_dcfifo_m20k\"],\n  \"ports\": [\n{}\n  ]\n}}\n",
1616        cfg.line.bus_bits(),
1617        cfg.line.secded() as u32,
1618        rows.join(",\n")
1619    )
1620}
1621
1622fn c_header(cfg: &AfuConfig) -> String {
1623    let mut h = String::from(
1624        "/* The LOGOS decode engine's bring-up accelerator on the Intel PAC D5005: the ID a driver opens it by, the byte\n   \
1625         offsets of its MMIO registers, its commands and their status codes. */\n#ifndef LOGOS_D5005_H\n#define LOGOS_D5005_H\n\n",
1626    );
1627    h += &format!("#define LOGOS_D5005_AFU_ID \"{}\"\n\n", cfg.uuid_text());
1628    h += &format!(
1629        "#define LOGOS_D5005_BANKS {BANKS}\n#define LOGOS_D5005_MEM_LINE_BYTES {}\n#define LOGOS_D5005_HOST_LINE_BYTES {}\n\n",
1630        cfg.line.bits() / 8,
1631        HOST_LINE_BITS / 8
1632    );
1633    h += &format!(
1634        "/* Whether each channel line's words are SECDED(72,64)-coded on the bus, and the bus bit of check bit `j` of word\n   \
1635         `w` (INJECT's bit numbers: data bit `i` of a line is bus bit `i`). */\n#define LOGOS_D5005_SECDED {}\n\
1636         #define LOGOS_D5005_SECDED_WORDS {SECDED_WORDS}u\n\
1637         #define LOGOS_D5005_SECDED_CHECK_BIT(w, j) ({} + 8u * ({}u - (w)) + (j))\n\n",
1638        cfg.line.secded() as u32,
1639        LocalMemoryLine::Data512Secded.bits(),
1640        SECDED_WORDS - 1
1641    );
1642    h += &format!("/* Whether the core runs the decode engine's steps (ENGINE_CONFIG, ENGINE_TABLE, ENGINE_RUN, STORE_READ). */\n#define LOGOS_D5005_ENGINE {}\n\n", cfg.engine.is_some() as u32);
1643    h += &format!("/* The divide from the user clock to the clock the core runs at. */\n#define LOGOS_D5005_COMPUTE_DIVIDE {}u\n\n", cfg.compute_divide);
1644    let (rows, store) = cfg.engine.as_ref().map_or((0, 0), |e| (e.rows, e.store_beats));
1645    h += &format!(
1646        "/* The most token-rows a step of the engine holds and its token-row store's beats: a program for more rows, or\n   \
1647         whose store is larger, cannot run on it. */\n#define LOGOS_D5005_ROWS {rows}u\n#define LOGOS_D5005_STORE_BEATS {store}u\n\n"
1648    );
1649    if cfg.contexts() > 1 {
1650        h += &format!(
1651            "/* The engine's sequencer contexts, each its own token-rows (LOGOS_D5005_ROWS of them) and bank of the store: a\n   \
1652             program is written for as many. */\n#define LOGOS_D5005_CONTEXTS {}u\n\n",
1653            cfg.contexts()
1654        );
1655    }
1656    if cfg.fp_hard() {
1657        h += "/* The engine's f32 products are the card's floating-point DSP blocks' (FpUnit::Hard): a program is written at\n   \
1658              them, and one written at the engine's own multiply is refused. */\n#define LOGOS_D5005_FP_HARD 1\n\n";
1659    }
1660    let streaming = cfg.engine.as_ref().map_or(String::new(), |e| e.streaming_signature());
1661    h += &format!(
1662        "/* A hybrid engine's streaming unit, its column set and the row pairs a channel's stripe holds, as the programs\n   \
1663         it runs name it in engine.txt; empty for an engine that streams nothing. */\n#define LOGOS_D5005_STREAMING \"{streaming}\"\n\n"
1664    );
1665    for r in REGISTERS {
1666        h += &format!("#define LOGOS_D5005_REG_{} 0x{:04x}u\n", r.name, 8 * r.word);
1667    }
1668    h += "\n";
1669    for o in Op::ALL {
1670        h += &format!("#define LOGOS_D5005_OP_{} {}u\n", o.name(), o.code());
1671    }
1672    h += &format!(
1673        "\n#define LOGOS_D5005_STATUS_OK 0x{STATUS_OK:02x}u\n#define LOGOS_D5005_STATUS_UNKNOWN_OP 0x{STATUS_UNKNOWN_OP:02x}u\n\
1674         #define LOGOS_D5005_STATUS_RUNNING 0x{STATUS_RUNNING:02x}u\n\
1675         #define LOGOS_D5005_ERROR_COMMAND_DROPPED 0x{ERROR_COMMAND_DROPPED:x}u\n\n#endif\n"
1676    );
1677    h
1678}
1679
1680/// The PIM boundary, its configured values at the `{…}` markers.
1681const AFU_TEMPLATE: &str = r##"// The engine's boundary with the OFS platform on the D5005: the PIM's host channel as Avalon-MM MMIO and host memory
1682// in pClk, each local-memory bank as Avalon-MM in its own EMIF clock, {lanes}, and the shell behind them on the user
1683// clock, which it divides into its compute side's steps itself: every clock the platform's.
1684`include "ofs_plat_if.vh"
1685
1686module ofs_plat_afu (ofs_plat_if plat_ifc);
1687  localparam NB = local_mem_cfg_pkg::LOCAL_MEM_NUM_BANKS;
1688  localparam AW = local_mem_cfg_pkg::LOCAL_MEM_ADDR_WIDTH;
1689  localparam DWM = local_mem_cfg_pkg::{mem_width};
1690  localparam MBW = local_mem_cfg_pkg::LOCAL_MEM_BURST_CNT_WIDTH;
1691  localparam HAW = ofs_plat_host_chan_pkg::ADDR_WIDTH_LINES;
1692  localparam DWH = ofs_plat_host_chan_pkg::DATA_WIDTH;
1693  localparam HBW = {host_burst_bits};
1694
1695  wire pclk = plat_ifc.clocks.ports[0].pClk.clk;
1696  wire pclk_rst_n = plat_ifc.clocks.ports[0].pClk.reset_n;
1697{user_clocks}
1698
1699  ofs_plat_avalon_mem_rdwr_if #(`HOST_CHAN_AVALON_MEM_RDWR_PARAMS, .BURST_CNT_WIDTH(HBW)) host_mem();
1700  ofs_plat_avalon_mem_if #(`HOST_CHAN_AVALON_MMIO_PARAMS(64)) mmio64();
1701  ofs_plat_host_chan_as_avalon_mem_rdwr_with_mmio #(.ADD_CLOCK_CROSSING(0)) host_shim(
1702    .to_fiu(plat_ifc.host_chan.ports[0]),
1703    .host_mem_to_afu(host_mem),
1704    .mmio_to_afu(mmio64),
1705    .afu_clk(pclk),
1706    .afu_reset_n(pclk_rst_n));
1707
1708  ofs_plat_avalon_mem_if #(`{mem_params}) mem[NB]();
1709
1710  wire [NB-1:0]       mem_clk, mem_rst_n, mem_read, mem_write, mem_waitrequest, mem_readdatavalid;
1711  wire [NB*AW-1:0]    mem_address;
1712  wire [NB*MBW-1:0]   mem_burstcount;
1713  wire [NB*DWM-1:0]   mem_writedata, mem_readdata;
1714  wire [NB*DWM/8-1:0] mem_byteenable;
1715
1716  genvar b;
1717  for (b = 0; b < NB; b = b + 1) begin : bank
1718    ofs_plat_local_mem_as_avalon_mem #(.ADD_CLOCK_CROSSING(0)) mem_shim(
1719      .to_fiu(plat_ifc.local_mem.banks[b]),
1720      .to_afu(mem[b]),
1721      .afu_clk(uclk),
1722      .afu_reset_n(uclk_rst_n));
1723    assign mem_clk[b] = mem[b].clk;
1724    assign mem_rst_n[b] = mem[b].reset_n;
1725    assign mem[b].address = mem_address[b*AW +: AW];
1726    assign mem[b].read = mem_read[b];
1727    assign mem[b].write = mem_write[b];
1728    assign mem[b].burstcount = mem_burstcount[b*MBW +: MBW];
1729    assign mem[b].writedata = mem_writedata[b*DWM +: DWM];
1730    assign mem[b].byteenable = mem_byteenable[b*DWM/8 +: DWM/8];
1731    assign mem[b].user = '0;
1732    assign mem_waitrequest[b] = mem[b].waitrequest;
1733    assign mem_readdata[b*DWM +: DWM] = mem[b].readdata;
1734    assign mem_readdatavalid[b] = mem[b].readdatavalid;
1735  end
1736
1737  ofs_plat_if_tie_off_unused #(.HOST_CHAN_IN_USE_MASK(1), .LOCAL_MEM_IN_USE_MASK((1 << NB) - 1)) tie_off(plat_ifc);
1738
1739  assign host_mem.rd_byteenable = '1;
1740  assign host_mem.rd_user = '0;
1741  assign host_mem.wr_user = '0;
1742  assign mmio64.response = '0;
1743  assign mmio64.readresponseuser = '0;
1744  assign mmio64.writeresponsevalid = 1'b0;
1745  assign mmio64.writeresponse = '0;
1746  assign mmio64.writeresponseuser = '0;
1747
1748  logos_d5005_shell #(
1749    .NB(NB), .AW(AW), .DWM(DWM), .ECC({ecc}), .MBW(MBW), .HAW(HAW), .DWH(DWH), .HBW(HBW),
1750    .MAW(ofs_plat_host_chan_pkg::MMIO_ADDR_WIDTH_BYTES - 3),
1751    .MAXB({max_burst}), .RAW({read_fifo_log2}), .WAW({write_fifo_log2}),
1752    .AFU_ID_L(64'h{id_l}), .AFU_ID_H(64'h{id_h}), .BUILD(64'h{build})
1753  ) shell (
1754    .pclk(pclk), .pclk_rst_n(pclk_rst_n), .uclk(uclk), .uclk_rst_n(uclk_rst_n),{uclk2}
1755    .mmio_address(mmio64.address), .mmio_read(mmio64.read), .mmio_write(mmio64.write),
1756    .mmio_writedata(mmio64.writedata), .mmio_byteenable(mmio64.byteenable), .mmio_waitrequest(mmio64.waitrequest),
1757    .mmio_readdata(mmio64.readdata), .mmio_readdatavalid(mmio64.readdatavalid),
1758    .host_rd_address(host_mem.rd_address), .host_rd_read(host_mem.rd_read), .host_rd_burstcount(host_mem.rd_burstcount),
1759    .host_rd_waitrequest(host_mem.rd_waitrequest), .host_rd_readdata(host_mem.rd_readdata),
1760    .host_rd_readdatavalid(host_mem.rd_readdatavalid),
1761    .host_wr_address(host_mem.wr_address), .host_wr_write(host_mem.wr_write), .host_wr_burstcount(host_mem.wr_burstcount),
1762    .host_wr_writedata(host_mem.wr_writedata), .host_wr_byteenable(host_mem.wr_byteenable),
1763    .host_wr_waitrequest(host_mem.wr_waitrequest), .host_wr_writeresponsevalid(host_mem.wr_writeresponsevalid),
1764    .mem_clk(mem_clk), .mem_rst_n(mem_rst_n), .mem_address(mem_address), .mem_read(mem_read), .mem_write(mem_write),
1765    .mem_burstcount(mem_burstcount), .mem_writedata(mem_writedata), .mem_byteenable(mem_byteenable),
1766    .mem_waitrequest(mem_waitrequest), .mem_readdata(mem_readdata), .mem_readdatavalid(mem_readdatavalid));
1767endmodule
1768"##;
1769
1770const DCFIFO: &str = r##"// A dual-clock FIFO of 2^AW words: the only way a value crosses between two of the engine's clocks.
1771//
1772// Each side keeps a binary and a Gray pointer in its own clock. A Gray pointer changes one bit a step, so the other
1773// side samples it through two registers and sees either the old or the new count, never a mix. The RAM is written in
1774// wclk and read in rclk; a word is read only after its write pointer has crossed, two rclk edges after the write.
1775// The read side shows ahead: rvalid means rdata holds the oldest word. wfree and rused are each side's conservative
1776// view: the other side's last pointer to cross. Each side steps at its enable (wce, rce), its synchronizers too, as a
1777// side clocked that much slower does: its outputs move only at the edges it steps at.
1778//
1779// A FIFO says what its writer is: HOLDS, one that holds a refused word until it is taken (checked in simulation);
1780// TOKENS, one that gives tokens (W is 1) as pulses, counted on the write side until taken; or neither, one that may
1781// let a refused word fall.
1782module MODULE_NAME #(parameter W = 64, parameter AW = 4, parameter HOLDS = 0, parameter TOKENS = 0) (
1783  input               wclk,
1784  input               wce,
1785  input               wrst_n,
1786  input               wvalid,
1787  output              wready,
1788  input  [W-1:0]      wdata,
1789  output [AW:0]       wfree,
1790  input               rclk,
1791  input               rce,
1792  input               rrst_n,
1793  output              rvalid,
1794  input               rready,
1795  output [W-1:0]      rdata,
1796  output [AW:0]       rused
1797);
1798  localparam [AW:0] DEPTH = 1 << AW;
1799
1800  function [AW:0] gray(input [AW:0] b);
1801    gray = b ^ (b >> 1);
1802  endfunction
1803
1804  function [AW:0] binary(input [AW:0] g);
1805    integer i;
1806    begin
1807      binary[AW] = g[AW];
1808      for (i = AW - 1; i >= 0; i = i - 1) binary[i] = binary[i + 1] ^ g[i];
1809    end
1810  endfunction
1811
1812  (* ramstyle = "RAMSTYLE, no_rw_check" *) reg [W-1:0] mem [0:DEPTH-1];
1813
1814  // Write side. Each Gray pointer stays a register of its own, the one its crossing is constrained from (its top bit
1815  // is the binary pointer's, which synthesis would otherwise merge it with); the other side's pointer, synchronized,
1816  // is converted to binary in a register, a cycle older than the synchronizer, so the free count only under-reports.
1817  reg  [AW:0] wbin;
1818  (* dont_merge *) reg [AW:0] wgray;
1819  (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] rgray_s1;
1820  (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] rgray_s2;
1821  (* dont_merge *) reg [AW:0] rgray;
1822  reg  [AW:0] rbin_w;
1823  wire        full = (wbin - rbin_w) == DEPTH;
1824  // With TOKENS a write is a token given as a pulse whatever wready says: the write side counts the tokens it has not
1825  // taken and takes one a step as it has room, so none is let fall while the FIFO looks full. Otherwise a write is the
1826  // writer's word, offered until the FIFO takes it.
1827  wire        offer;
1828  generate if (TOKENS) begin : tokens
1829    reg [15:0] owed;
1830    always @(posedge wclk or negedge wrst_n)
1831      if (!wrst_n) owed <= 16'd0;
1832      else if (wce) owed <= owed + {15'd0, wvalid} - {15'd0, owed != 16'd0 && !full};
1833    assign offer = owed != 16'd0;
1834  end else begin : words
1835    assign offer = wvalid;
1836  end endgenerate
1837  wire        push = wce && offer && !full;
1838  assign wready = !full;
1839  assign wfree = DEPTH - (wbin - rbin_w);
1840  // With HOLDS the writer holds a word the FIFO refused until it is taken: in simulation, a refused word not offered
1841  // again at the next step, unchanged, stops the run.
1842  // synthesis translate_off
1843`ifndef SYNTHESIS
1844  reg          sim_refused = 1'b0;
1845  reg  [W-1:0] sim_word;
1846  always @(posedge wclk)
1847    if (!wrst_n) sim_refused <= 1'b0;
1848    else if (wce) begin
1849      if (HOLDS != 0 && sim_refused && !(wvalid && wdata === sim_word))
1850        $fatal(1, "%m: a word the FIFO refused was not offered again");
1851      sim_refused <= wvalid && full;
1852      sim_word <= wdata;
1853    end
1854`endif
1855  // synthesis translate_on
1856
1857  always @(posedge wclk) if (push) mem[wbin[AW-1:0]] <= wdata;
1858
1859  always @(posedge wclk or negedge wrst_n) begin
1860    if (!wrst_n) begin
1861      wbin <= 0;
1862      wgray <= 0;
1863      rgray_s1 <= 0;
1864      rgray_s2 <= 0;
1865      rbin_w <= 0;
1866    end else if (wce) begin
1867      rgray_s1 <= rgray;
1868      rgray_s2 <= rgray_s1;
1869      rbin_w <= binary(rgray_s2);
1870      if (push) begin
1871        wbin <= wbin + 1'b1;
1872        wgray <= gray(wbin + 1'b1);
1873      end
1874    end
1875  end
1876
1877  // Read side.
1878  reg  [AW:0] rbin;
1879  (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] wgray_s1;
1880  (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] wgray_s2;
1881  reg  [W-1:0] rdata_q;
1882  reg          rvalid_q;
1883  reg  [AW:0] wbin_r;
1884  wire        empty = wbin_r == rbin;
1885  wire        pop = rce && !empty && (!rvalid_q || rready);
1886  assign rvalid = rvalid_q;
1887  assign rdata = rdata_q;
1888  assign rused = (wbin_r - rbin) + {{AW{1'b0}}, rvalid_q};
1889
1890  always @(posedge rclk) if (pop) rdata_q <= mem[rbin[AW-1:0]];
1891
1892  always @(posedge rclk or negedge rrst_n) begin
1893    if (!rrst_n) begin
1894      rbin <= 0;
1895      rgray <= 0;
1896      wgray_s1 <= 0;
1897      wgray_s2 <= 0;
1898      wbin_r <= 0;
1899      rvalid_q <= 1'b0;
1900    end else if (rce) begin
1901      wgray_s1 <= wgray;
1902      wgray_s2 <= wgray_s1;
1903      wbin_r <= binary(wgray_s2);
1904      if (pop) begin
1905        rbin <= rbin + 1'b1;
1906        rgray <= gray(rbin + 1'b1);
1907      end
1908      if (pop) rvalid_q <= 1'b1;
1909      else if (rready) rvalid_q <= 1'b0;
1910    end
1911  end
1912endmodule
1913"##;
1914
1915const STREAM: &str = r##"// The stream engines, each in its channel's own clock: descriptors in, Avalon-MM bursts out, lines through FIFOs.
1916//
1917// A descriptor {line address, lines} moves `lines` lines in bursts of at most MAXB. A reader issues a burst only
1918// when the read-data FIFO has room for it beside every line still owed: readdatavalid cannot be held off, so the
1919// room is reserved at issue (`r_room` is the FIFO's write-side free count, which only under-reports), one request on
1920// the bus at a time so each is sized against what the last reserved. A writer takes a burst's beats from the
1921// write-data FIFO as they come; an Avalon burst may pause between beats.
1922module logos_stream_reader #(
1923  parameter AW = 27,           // line address bits
1924  parameter DW = 512,          // data bits a line
1925  parameter BW = 7,            // burstcount bits
1926  parameter LW = 32,           // line-count bits of a descriptor
1927  parameter MAXB = 64,         // lines a burst, at most 2^(BW-1)
1928  parameter RW = 10            // read-FIFO count bits
1929) (
1930  input               clk,
1931  input               rst_n,
1932  input               d_valid,
1933  output              d_ready,
1934  input  [AW+LW-1:0]  d_data,
1935  output              r_valid,
1936  output [DW-1:0]     r_data,
1937  input  [RW-1:0]     r_room,
1938  output reg [AW-1:0] avm_address,
1939  output reg          avm_read,
1940  output reg [BW-1:0] avm_burstcount,
1941  input               avm_waitrequest,
1942  input  [DW-1:0]     avm_readdata,
1943  input               avm_readdatavalid,
1944  output reg [RW-1:0] owed,
1945  output              active
1946);
1947  localparam [LW-1:0] MAXL = MAXB;
1948  reg          busy_q;
1949  reg [AW-1:0] next_addr;
1950  reg [LW-1:0] left;
1951  // The next burst and the room beside every line owed, each a register a cycle behind left and owed; a burst issues
1952  // only while both are current, a cycle after anything changed them. The room only grows meanwhile (the FIFO's other
1953  // side frees it; a line returning leaves the room and the lines owed together), so it never over-reports.
1954  reg [LW-1:0] chunk;
1955  reg [RW:0]   spare;
1956  reg          current;
1957  wire [AW+LW-1:0] chunk_wide = {{AW{1'b0}}, chunk};
1958  wire [AW-1:0] step = chunk_wide[AW-1:0];
1959  // A burst is at most MAXB lines, fewer than the read FIFO holds, so the room and the burst compare at its width.
1960  wire          fits = spare >= {1'b0, chunk[RW-1:0]};
1961  wire          accepted = avm_read && !avm_waitrequest;
1962  wire [RW-1:0] burst_lines = avm_burstcount;
1963  wire [RW-1:0] returned = avm_readdatavalid;
1964  assign d_ready = !busy_q;
1965  assign active = busy_q;
1966  assign r_valid = avm_readdatavalid;
1967  assign r_data = avm_readdata;
1968  always @(posedge clk or negedge rst_n) begin
1969    if (!rst_n) begin
1970      busy_q <= 1'b0;
1971      next_addr <= 0;
1972      left <= 0;
1973      avm_address <= 0;
1974      avm_read <= 1'b0;
1975      avm_burstcount <= 0;
1976      owed <= 0;
1977      chunk <= 0;
1978      spare <= 0;
1979      current <= 1'b0;
1980    end else begin
1981      owed <= owed + (accepted ? burst_lines : {RW{1'b0}}) - returned;
1982      chunk <= (left > MAXL) ? MAXL : left;
1983      spare <= {1'b0, r_room} - {1'b0, owed};
1984      current <= 1'b1;
1985      if (!busy_q) begin
1986        if (d_valid) begin
1987          busy_q <= 1'b1;
1988          next_addr <= d_data[AW+LW-1:LW];
1989          left <= d_data[LW-1:0];
1990          current <= 1'b0;
1991        end
1992      end else if (avm_read) begin
1993        if (!avm_waitrequest) begin avm_read <= 1'b0; current <= 1'b0; end
1994      end else if (left == 0) begin
1995        busy_q <= 1'b0;
1996      end else if (current && fits) begin
1997        current <= 1'b0;
1998        avm_read <= 1'b1;
1999        avm_address <= next_addr;
2000        avm_burstcount <= chunk[BW-1:0];
2001        next_addr <= next_addr + step;
2002        left <= left - chunk;
2003      end
2004    end
2005  end
2006endmodule
2007
2008module logos_stream_writer #(
2009  parameter AW = 27,
2010  parameter DW = 512,
2011  parameter BW = 7,
2012  parameter LW = 32,
2013  parameter MAXB = 64
2014) (
2015  input               clk,
2016  input               rst_n,
2017  input               d_valid,
2018  output              d_ready,
2019  input  [AW+LW-1:0]  d_data,
2020  input               w_valid,
2021  output              w_ready,
2022  input  [DW-1:0]     w_data,
2023  output reg [AW-1:0] avm_address,
2024  output              avm_write,
2025  output reg [BW-1:0] avm_burstcount,
2026  output [DW-1:0]     avm_writedata,
2027  output [DW/8-1:0]   avm_byteenable,
2028  input               avm_waitrequest,
2029  output              active,
2030  output reg          done
2031);
2032  localparam [LW-1:0] MAXL = MAXB;
2033  reg          busy_q, in_burst;
2034  reg [AW-1:0] next_addr;
2035  reg [LW-1:0] left;
2036  reg [BW-1:0] beats;
2037  wire [LW-1:0] chunk = (left > MAXL) ? MAXL : left;
2038  wire [AW+LW-1:0] chunk_wide = {{AW{1'b0}}, chunk};
2039  wire [AW-1:0] step = chunk_wide[AW-1:0];
2040  wire          accepted = avm_write && !avm_waitrequest;
2041  assign d_ready = !busy_q;
2042  assign active = busy_q;
2043  assign avm_write = in_burst && w_valid;
2044  assign w_ready = accepted;
2045  assign avm_writedata = w_data;
2046  assign avm_byteenable = {(DW/8){1'b1}};
2047  always @(posedge clk or negedge rst_n) begin
2048    if (!rst_n) begin
2049      busy_q <= 1'b0;
2050      in_burst <= 1'b0;
2051      next_addr <= 0;
2052      left <= 0;
2053      beats <= 0;
2054      avm_address <= 0;
2055      avm_burstcount <= 0;
2056      done <= 1'b0;
2057    end else begin
2058      done <= 1'b0;
2059      if (!busy_q) begin
2060        if (d_valid) begin
2061          busy_q <= 1'b1;
2062          next_addr <= d_data[AW+LW-1:LW];
2063          left <= d_data[LW-1:0];
2064        end
2065      end else if (in_burst) begin
2066        if (accepted) begin
2067          beats <= beats - 1'b1;
2068          if (beats == 1) in_burst <= 1'b0;
2069        end
2070      end else if (left == 0) begin
2071        busy_q <= 1'b0;
2072        done <= 1'b1;
2073      end else begin
2074        in_burst <= 1'b1;
2075        avm_address <= next_addr;
2076        avm_burstcount <= chunk[BW-1:0];
2077        beats <= chunk[BW-1:0];
2078        next_addr <= next_addr + step;
2079        left <= left - chunk;
2080      end
2081    end
2082  end
2083endmodule
2084
2085// One local-memory channel: a reader and a writer on the bank's single Avalon-MM port. A descriptor {write, address,
2086// lines} goes to the engine its op names once neither engine is issuing, so one engine drives the bus at a time;
2087// lines a read still owes keep returning while a write issues.
2088//
2089// With ECC set every line is SECDED(72,64)-coded on the bus (logos_secded.v): a line written is coded, then has the
2090// bus bits `w_flip` = {enable, b, a} names flipped (one bit when a = b); a line read is decoded in one registered
2091// stage, its words' verdicts `r_ecc` = {uncorrectable, corrected} beside it, and the line in the stage counted
2092// against the read FIFO's room as the lines the reader still owes are.
2093module logos_stream_avmm #(
2094  parameter AW = 27,
2095  parameter DW = 512,          // bus bits a line
2096  parameter BW = 7,
2097  parameter LW = 32,
2098  parameter MAXB = 64,
2099  parameter RW = 10,
2100  parameter ECC = 0,
2101  parameter DL = ECC ? DW / 9 * 8 : DW   // data bits a line
2102) (
2103  input               clk,
2104  input               rst_n,
2105  input               d_valid,
2106  output              d_ready,
2107  input  [AW+LW:0]    d_data,
2108  output              r_valid,
2109  output [DL-1:0]     r_data,
2110  output [15:0]       r_ecc,
2111  input  [RW-1:0]     r_room,
2112  input               w_valid,
2113  output              w_ready,
2114  input  [DL-1:0]     w_data,
2115  input  [20:0]       w_flip,
2116  output [AW-1:0]     avm_address,
2117  output              avm_read,
2118  output              avm_write,
2119  output [BW-1:0]     avm_burstcount,
2120  output [DW-1:0]     avm_writedata,
2121  output [DW/8-1:0]   avm_byteenable,
2122  input               avm_waitrequest,
2123  input  [DW-1:0]     avm_readdata,
2124  input               avm_readdatavalid,
2125  output [RW-1:0]     owed,
2126  output              busy,
2127  output              wdone
2128);
2129  wire          is_write = d_data[AW+LW];
2130  wire          rd_active, wr_active, rd_ready, wr_ready;
2131  wire          idle = !rd_active && !wr_active;
2132  wire [AW-1:0] rd_address, wr_address;
2133  wire [BW-1:0] rd_burstcount, wr_burstcount;
2134  wire          bus_valid;
2135  wire [DW-1:0] bus_rdata, bus_wdata;
2136  wire [RW-1:0] room;
2137  wire          staged;
2138  // The bus's read data in a register on its way in, the platform's bridge register to this one. The reader counts a
2139  // line returned when it leaves this register, so a line is owed until it reaches the read FIFO, or the decoding
2140  // stage on SECDED lines, which the room counts.
2141  reg           in_valid;
2142  reg  [DW-1:0] in_rdata;
2143  always @(posedge clk) in_rdata <= avm_readdata;
2144  always @(posedge clk or negedge rst_n)
2145    if (!rst_n) in_valid <= 1'b0;
2146    else in_valid <= avm_readdatavalid;
2147  assign d_ready = idle;
2148  // The engines' requests in a two-deep queue on their way out to the platform's bridge: the engines see room while the
2149  // queue holds at most one, its count a register, and the bus takes from its head, so a request reaches the bus in
2150  // the order the engines made it, a read after a write behind it, and nothing in the engines waits on the bus. The
2151  // head's read and write are registers of their own, cleared as the head leaves with nothing behind it, so what the
2152  // bridge registers is a register.
2153  localparam QW = AW + BW + DW + DW / 8;
2154  wire          q_read, q_write;
2155  wire [AW-1:0] q_address = wr_active ? wr_address : rd_address;
2156  wire [BW-1:0] q_burstcount = wr_active ? wr_burstcount : rd_burstcount;
2157  wire [DW-1:0] q_writedata;
2158  wire [DW/8-1:0] q_byteenable;
2159  reg  [1:0]    q_count;
2160  reg           head_read, head_write, next_read, next_write;
2161  reg  [QW-1:0] q_head, q_next;
2162  wire          q_room = q_count != 2'd2;
2163  wire          q_in = (q_read || q_write) && q_room;
2164  wire          q_out = (head_read || head_write) && !avm_waitrequest;
2165  wire [QW-1:0] q_entry = {q_address, q_burstcount, q_writedata, q_byteenable};
2166  assign busy = !idle || owed != 0 || q_count != 2'd0 || staged;
2167  always @(posedge clk or negedge rst_n)
2168    if (!rst_n) begin
2169      q_count <= 2'd0;
2170      {head_read, head_write, next_read, next_write} <= 4'b0000;
2171    end else begin
2172      q_count <= q_count + {1'b0, q_in} - {1'b0, q_out};
2173      if (q_out) begin
2174        if (q_count == 2'd2) {head_read, head_write} <= {next_read, next_write};
2175        else if (q_in) {head_read, head_write} <= {q_read, q_write};
2176        else {head_read, head_write} <= 2'b00;
2177      end else if (q_in) begin
2178        if (q_count == 2'd0) {head_read, head_write} <= {q_read, q_write};
2179        else {next_read, next_write} <= {q_read, q_write};
2180      end
2181    end
2182  always @(posedge clk)
2183    if (q_out) begin
2184      if (q_count == 2'd2) q_head <= q_next;
2185      else if (q_in) q_head <= q_entry;
2186    end else if (q_in) begin
2187      if (q_count == 2'd0) q_head <= q_entry;
2188      else q_next <= q_entry;
2189    end
2190  assign avm_read = head_read;
2191  assign avm_write = head_write;
2192  assign {avm_address, avm_burstcount, avm_writedata, avm_byteenable} = q_head;
2193  logos_stream_reader #(.AW(AW), .DW(DW), .BW(BW), .LW(LW), .MAXB(MAXB), .RW(RW)) reader(
2194    .clk(clk), .rst_n(rst_n), .d_valid(d_valid && idle && !is_write), .d_ready(rd_ready), .d_data(d_data[AW+LW-1:0]),
2195    .r_valid(bus_valid), .r_data(bus_rdata), .r_room(room), .avm_address(rd_address), .avm_read(q_read),
2196    .avm_burstcount(rd_burstcount), .avm_waitrequest(!q_room), .avm_readdata(in_rdata),
2197    .avm_readdatavalid(in_valid), .owed(owed), .active(rd_active));
2198  logos_stream_writer #(.AW(AW), .DW(DW), .BW(BW), .LW(LW), .MAXB(MAXB)) writer(
2199    .clk(clk), .rst_n(rst_n), .d_valid(d_valid && idle && is_write), .d_ready(wr_ready), .d_data(d_data[AW+LW-1:0]),
2200    .w_valid(w_valid), .w_ready(w_ready), .w_data(bus_wdata), .avm_address(wr_address), .avm_write(q_write),
2201    .avm_burstcount(wr_burstcount), .avm_writedata(q_writedata), .avm_byteenable(q_byteenable),
2202    .avm_waitrequest(!q_room), .active(wr_active), .done(wdone));
2203  generate if (ECC) begin : secded
2204    wire [DW-1:0] one = {{(DW-1){1'b0}}, 1'b1};
2205    wire [DW-1:0] coded;
2206    wire [DL-1:0] fixed;
2207    wire [7:0]    corrected, uncorrectable;
2208    reg           q_valid;
2209    reg  [DL-1:0] q_data;
2210    reg  [15:0]   q_ecc;
2211    logos_secded_encode coder(.d(w_data), .bus(coded));
2212    assign bus_wdata = coded ^ (w_flip[20] ? (one << w_flip[9:0]) | (one << w_flip[19:10]) : {DW{1'b0}});
2213    logos_secded_decode decoder(.bus(bus_rdata), .d(fixed), .corrected(corrected), .uncorrectable(uncorrectable));
2214    always @(posedge clk or negedge rst_n) begin
2215      if (!rst_n) begin
2216        q_valid <= 1'b0;
2217        q_data <= 0;
2218        q_ecc <= 0;
2219      end else begin
2220        q_valid <= bus_valid;
2221        q_data <= fixed;
2222        q_ecc <= {uncorrectable, corrected};
2223      end
2224    end
2225    assign r_valid = q_valid;
2226    assign r_data = q_data;
2227    assign r_ecc = q_ecc;
2228    assign room = r_room - {{(RW-1){1'b0}}, q_valid};
2229    assign staged = q_valid;
2230  end else begin : plain
2231    assign bus_wdata = w_data;
2232    assign r_valid = bus_valid;
2233    assign r_data = bus_rdata;
2234    assign r_ecc = 16'h0;
2235    assign room = r_room;
2236    assign staged = 1'b0;
2237  end endgenerate
2238endmodule
2239"##;
2240
2241const CSR: &str = r##"// The accelerator's registers, in the host channel's clock: the OFS feature header and ID the driver finds it by,
2242// a command doorbell into the engine, and the engine's last status record.
2243//
2244// MMIO addresses are 64-bit word indices. A write to DOORBELL pushes {arg2, arg1, arg0, doorbell} into the command
2245// FIFO in that cycle; with the FIFO full it is dropped, counted in DROPPED and ERRORS bit 0 set, so a driver reads
2246// CMD_SPACE first. Every
2247// status record the engine pushes is taken at once and mirrored. Reads answer the next cycle, in order.
2248module logos_csr #(
2249  parameter MAW = 15,                     // MMIO word-address bits
2250  parameter [63:0] AFU_ID_L = 64'h0,
2251  parameter [63:0] AFU_ID_H = 64'h0,
2252  parameter [3:0]  AFU_MAJOR = 4'd1,
2253  parameter [3:0]  AFU_MINOR = 4'd0,
2254  parameter [63:0] BUILD = 64'h0,
2255  parameter CW = 8                        // command-FIFO count bits
2256) (
2257  input             clk,
2258  input             rst_n,
2259  input  [MAW-1:0]  mmio_address,
2260  input             mmio_read,
2261  input             mmio_write,
2262  input  [63:0]     mmio_writedata,
2263  input  [7:0]      mmio_byteenable,
2264  output            mmio_waitrequest,
2265  output reg [63:0] mmio_readdata,
2266  output reg        mmio_readdatavalid,
2267  // Commands, into the engine's clock.
2268  output            cmd_valid,
2269  input             cmd_ready,
2270  output [255:0]    cmd_data,
2271  input  [CW-1:0]   cmd_space,
2272  // Status records {seq, status, result0, result1}, from the engine's clock.
2273  input             st_valid,
2274  output            st_ready,
2275  input  [255:0]    st_data
2276);
2277  /*REGISTER_MAP*/
2278
2279  reg [63:0] scratch, arg0, arg1, arg2, status, result0, result1, status_seq, errors, dropped;
2280  wire       ring = mmio_write && mmio_address == DOORBELL;
2281
2282  assign mmio_waitrequest = 1'b0;
2283  assign cmd_valid = ring;
2284  assign cmd_data = {arg2, arg1, arg0, mmio_writedata};
2285  assign st_ready = 1'b1;
2286
2287  wire [63:0] dfh = {4'h1, 8'h0, AFU_MINOR, 7'h0, 1'b1, 24'h0, AFU_MAJOR, 12'h0};
2288
2289  function [63:0] merge(input [63:0] old, input [63:0] new_, input [7:0] be);
2290    integer i;
2291    for (i = 0; i < 8; i = i + 1) merge[8*i +: 8] = be[i] ? new_[8*i +: 8] : old[8*i +: 8];
2292  endfunction
2293
2294  always @(posedge clk or negedge rst_n) begin
2295    if (!rst_n) begin
2296      scratch <= 0; arg0 <= 0; arg1 <= 0; arg2 <= 0; dropped <= 0;
2297      status <= 0; result0 <= 0; result1 <= 0; status_seq <= 0; errors <= 0;
2298      mmio_readdata <= 0; mmio_readdatavalid <= 1'b0;
2299    end else begin
2300      if (ring && !cmd_ready) begin
2301        errors[0] <= 1'b1;
2302        dropped <= dropped + 1'b1;
2303      end
2304      if (st_valid) begin
2305        status_seq <= st_data[63:0];
2306        status <= st_data[127:64];
2307        result0 <= st_data[191:128];
2308        result1 <= st_data[255:192];
2309      end
2310      if (mmio_write) begin
2311        case (mmio_address)
2312          SCRATCH: scratch <= merge(scratch, mmio_writedata, mmio_byteenable);
2313          ARG0: arg0 <= merge(arg0, mmio_writedata, mmio_byteenable);
2314          ARG1: arg1 <= merge(arg1, mmio_writedata, mmio_byteenable);
2315          ARG2: arg2 <= merge(arg2, mmio_writedata, mmio_byteenable);
2316          ERRORS: errors <= errors & ~mmio_writedata;
2317          default: ;
2318        endcase
2319      end
2320      mmio_readdatavalid <= mmio_read;
2321      if (mmio_read) begin
2322        case (mmio_address)
2323          DFH: mmio_readdata <= dfh;
2324          ID_L: mmio_readdata <= AFU_ID_L;
2325          ID_H: mmio_readdata <= AFU_ID_H;
2326          SCRATCH: mmio_readdata <= scratch;
2327          BUILD_ID: mmio_readdata <= BUILD;
2328          STATUS: mmio_readdata <= status;
2329          RESULT0: mmio_readdata <= result0;
2330          RESULT1: mmio_readdata <= result1;
2331          STATUS_SEQ: mmio_readdata <= status_seq;
2332          CMD_SPACE: mmio_readdata <= {{(64-CW){1'b0}}, cmd_space};
2333          ERRORS: mmio_readdata <= errors;
2334          DROPPED: mmio_readdata <= dropped;
2335          ARG0: mmio_readdata <= arg0;
2336          ARG1: mmio_readdata <= arg1;
2337          ARG2: mmio_readdata <= arg2;
2338          default: mmio_readdata <= 64'h0;
2339        endcase
2340      end
2341    end
2342  end
2343endmodule
2344"##;
2345
2346const CORE: &str = r##"// The bring-up engine, in the compute clock — every register stepping in its clock at ce: fills and checks the memory
2347// channels at full rate and copies lines between host memory and a channel, so the shell's every path is exercised on
2348// the card before the decode engine takes its place behind the same ports.
2349//
2350// A command is {arg2, arg1, arg0, {tag, op}}; each completes with a status record {seq, {tag, code}, result0,
2351// result1}: seq the count of commands completed with this one, result1 the compute cycles it took. While a command
2352// runs, every 2^HB cycles in which it moved since the last, the core cuts a progress record {seq, {tag, RUNNING},
2353// {pc, progress}, cycles so far}: seq the commands completed before it, so a host waiting for the command's own record
2354// still waits, pc the engine's word (each context's in its 16 bits), and progress the low 32 bits of the count of
2355// cycles since reset in which a line crossed a memory channel (on whichever side holds the channels) or the host
2356// channel or the engine's sequencer took its next word.
2357//   FILL  (1): every channel in mask arg0 writes `lines` = arg2[31:0] lines from line arg1, line L = pattern(seed, L)
2358//              with seed = arg2[63:32]; done when each channel reports its last beat accepted.
2359//   CHECK (2): every channel in the mask reads those lines back; result0 = lines that differ.
2360//   HOST_TO_MEM (3): `lines` host lines from host line arg0 into channel arg1[63:56] at line arg1[AW-1:0]; a line is
2361//              the host line, zero-extended.
2362//   MEM_TO_HOST (4): the reverse, a channel line truncated to a host line; done when every host write burst is
2363//              acknowledged.
2364//   HOST_TO_MEM_PACKED (5): `lines` channel lines at the channel's full width into channel arg1[63:56] at line
2365//              arg1[AW-1:0], read packed end to end from host line arg0 on, the fewest host lines that hold them; done
2366//              when the channel's writes are done.
2367//   MEM_TO_HOST_PACKED (6): the reverse, the channel lines packed end to end into host lines, the last zero past
2368//              them; done when every host write burst is acknowledged.
2369//   A packed copy moves its bytes through one gather register of 64-bit words: a line leaves it once it holds a
2370//   line's words, a line comes into it when its words fit.
2371//   INJECT (7): on SECDED channels, the next arg2[31:0] lines written to each channel in mask arg0 have bus bits
2372//              arg1[9:0] and arg1[25:16] flipped after they are coded, one bit when the two are equal.
2373//   ECC_COUNTS (8): on SECDED channels, result0 = {words found uncorrectable, words corrected} of channel arg0[7:0]
2374//              since reset, each modulo 2^32, every line read counted, whichever side took it.
2375//   ENGINE_CONFIG (9): with the engine, its token-rows arg0[15:0] and knobs arg0[19:16] {per_block, two_words,
2376//              cache_int8, state_f16}, the card port's space arg1[31:0], base arg2[AW-1:0] and log2 granule
2377//              arg2[60:56], held until the next.
2378//   ENGINE_TABLE (10): with the engine, `lines` host lines from host line arg0 into its table arg1[34:32] from entry
2379//              arg1[31:0] on, a line an entry; done when the last is written.
2380//   ENGINE_RUN (11): with the engine, the step from job word arg0[31:0] to arg1[31:0]: the card port's fault cleared,
2381//              the engine started, the channels the card port's until the engine is idle again; result0 = {the word
2382//              the sequencer reached, 30'b0, the card port's fault, the engine's fault}.
2383//   STORE_READ (12): with the engine, `lines` beats of its store from beat arg1[31:0] into host lines from host line
2384//              arg0, each beat read a cycle after its address is set; done when every host write burst is acknowledged.
2385//   Any other op completes at once with code 0x80, as INJECT and ECC_COUNTS do on channels without SECDED and the
2386//   engine's four without the engine; NOP (0) with code 0.
2387module logos_bringup_core #(
2388  parameter NB = 4,            // memory channels
2389  parameter AW = 27,           // channel line-address bits
2390  parameter DWM = 512,         // channel line bits
2391  parameter ECC = 0,           // the channels' lines SECDED-coded
2392  parameter ENGINE = 0,        // an engine and its card port behind the core
2393  parameter HAW = 42,          // host line-address bits
2394  parameter DWH = 512,         // host line bits
2395  parameter LW = 32,
2396  parameter HMAXB = 64,        // lines a host write burst
2397  parameter HB = 16            // log2 of the cycles between a running command's progress records
2398) (
2399  input                     clk,
2400  input                     ce,
2401  input                     rst_n,
2402  input                     cmd_valid,
2403  output                    cmd_ready,
2404  input  [255:0]            cmd_data,
2405  output                    st_valid,
2406  input                     st_ready,
2407  output [255:0]            st_data,
2408  // Channel descriptors {write, address, lines}, lines read, lines to write, write descriptors finished.
2409  output [NB-1:0]           md_valid,
2410  input  [NB-1:0]           md_ready,
2411  output [NB*(AW+LW+1)-1:0] md_data,
2412  input  [NB-1:0]           mr_valid,
2413  output [NB-1:0]           mr_ready,
2414  input  [NB*DWM-1:0]       mr_data,
2415  input  [NB*16-1:0]        mr_ecc,      // each line read's words' verdicts {uncorrectable, corrected}
2416  output [NB-1:0]           mw_valid,
2417  input  [NB-1:0]           mw_ready,
2418  output [NB*DWM-1:0]       mw_data,
2419  output [NB*21-1:0]        mw_flip,     // the bus bits each line written flips {enable, b, a}
2420  input  [NB-1:0]           mdone_valid,
2421  output [NB-1:0]           mdone_ready,
2422  // Host read and write descriptors {address, lines}, host lines read, host lines to write, host write acknowledgements.
2423  output                    hrd_valid,
2424  input                     hrd_ready,
2425  output [HAW+LW-1:0]       hrd_data,
2426  input                     hr_valid,
2427  output                    hr_ready,
2428  input  [DWH-1:0]          hr_data,
2429  output                    hwd_valid,
2430  input                     hwd_ready,
2431  output [HAW+LW-1:0]       hwd_data,
2432  output                    hw_valid,
2433  input                     hw_ready,
2434  output [DWH-1:0]          hw_data,
2435  input                     hack_valid,
2436  output                    hack_ready,
2437  // The lines each read FIFO gives up, to whichever side takes them; a line crossing any channel, either way.
2438  input  [NB-1:0]           mr_taken,
2439  input                     moved,
2440  // The engine: its run, its configuration, its tables and store; the card port's map, its fault, and the channels.
2441  output reg                card_mode,
2442  output reg                eng_start,
2443  output reg [31:0]         eng_job_at,
2444  output reg [31:0]         eng_job_end,
2445  output reg [15:0]         eng_rows,
2446  output reg [3:0]          eng_knobs,
2447  input                     eng_busy,
2448  input                     eng_fault,
2449  input  [31:0]             eng_pc,
2450  output                    eng_host_wr,
2451  output reg [2:0]          eng_host_table,
2452  output [31:0]             eng_host_addr,
2453  output [DWH-1:0]          eng_host_wdata,
2454  output [31:0]             eng_host_raddr,
2455  input  [DWH-1:0]          eng_host_rdata,
2456  output reg [31:0]         port_space,
2457  output reg [AW-1:0]       port_base,
2458  output reg [4:0]          port_granule_log2,
2459  output reg                port_clear,
2460  input                     port_fault
2461);
2462  /*OPS*/
2463  localparam LANES = DWM / 64;
2464  localparam BB = NB > 1 ? $clog2(NB) : 1;
2465  localparam [LW-1:0] HMAXL = HMAXB;
2466  localparam HMAXB_LOG2 = $clog2(HMAXB);
2467
2468  // Line L of a fill seeded `seed`: each 64-bit lane distinct, so a swapped lane or line reads back wrong.
2469  function [DWM-1:0] pattern(input [31:0] seed, input [31:0] line);
2470    integer k;
2471    reg [31:0] lane;
2472    begin
2473      for (k = 0; k < LANES; k = k + 1) begin
2474        lane = k;
2475        pattern[64*k +: 64] = {seed ^ line ^ (lane * 32'h9e3779b9), ~line + lane * 32'h85ebca6b};
2476      end
2477    end
2478  endfunction
2479
2480  // The 32 low bits of channel line `base + i`, the line a pattern is seeded by.
2481  function [31:0] line_of(input [AW-1:0] base_line, input [LW-1:0] i);
2482    reg [AW-1:0] at;
2483    begin
2484      at = base_line + i[AW-1:0];
2485      line_of = at;
2486    end
2487  endfunction
2488
2489  reg              busy, reported;
2490  reg [7:0]        op;
2491  reg [55:0]       tag;
2492  reg [7:0]        code;
2493  reg [63:0]       seq, cycles, started, result0;
2494  reg [NB-1:0]     mask, need_desc, wrote, done;
2495  reg [AW-1:0]     base;
2496  reg [LW-1:0]     lines;
2497  reg [31:0]       seed;
2498  reg [HAW-1:0]    hbase;
2499  reg [7:0]        bank;
2500  reg [LW-1:0]     sent [0:NB-1];
2501  reg [LW-1:0]     seen [0:NB-1];
2502  reg [63:0]       wrong [0:NB-1];
2503  reg              need_hdesc;
2504  reg [LW-1:0]     acks, bursts;
2505  // SECDED: lines each channel still flips and the two bus bits, the words corrected and lost since reset.
2506  reg [31:0]       flips_left [0:NB-1];
2507  reg [19:0]       flip_bits [0:NB-1];
2508  reg [31:0]       fixed [0:NB-1];
2509  reg [31:0]       lost [0:NB-1];
2510
2511  function [3:0] ones(input [7:0] v);
2512    integer k;
2513    begin
2514      ones = 0;
2515      for (k = 0; k < 8; k = k + 1) ones = ones + v[k];
2516    end
2517  endfunction
2518
2519  wire packed_in = op == HOST_TO_MEM_PACKED;
2520  wire packed_out = op == MEM_TO_HOST_PACKED;
2521  wire eng_table = op == ENGINE_TABLE;
2522  wire store_read = op == STORE_READ;
2523  // The engine's tables and store: the entry a table write starts at and the host lines it has taken; the beat a store
2524  // read starts at, the beats it has sent, and whether the current beat's address has stood a cycle.
2525  reg [31:0]       tbase, taken, sbase, sent_beats;
2526  reg              beat_ready;
2527  reg [1:0]        run_hold;
2528  // A table write's end, from registers: every line taken, or every line but the one taken now.
2529  wire taken_all = taken == lines, taken_last = taken + 32'd1 == lines;
2530  wire to_mem = op == FILL || op == HOST_TO_MEM || packed_in;
2531  wire from_host = op == HOST_TO_MEM || packed_in;
2532  wire to_host = op == MEM_TO_HOST || packed_out;
2533
2534  // The packed copies' gather register: `gn` 64-bit words of the bytes in flight, the rest zero.
2535  localparam LWD = DWM / 64, HWD = DWH / 64, GW = LWD + HWD;
2536  localparam [7:0] LWD8 = LWD, HWD8 = HWD, GW8 = GW;
2537  reg [64*GW-1:0] g;
2538  reg [7:0]       gn;
2539  reg [LW-1:0]    hlines;
2540  // The host lines a command moves: a packed copy's channel lines end to end in host lines, any other one each.
2541  wire [LW-1:0]   cmd_lines = cmd_data[192+LW-1:192];
2542  wire [LW+7:0]   cmd_words = cmd_lines * LWD8;
2543  wire [LW+7:0]   cmd_hwords = (cmd_words + HWD8 - 8'd1) / HWD8;
2544  wire [LW-1:0]   cmd_hlines = (cmd_data[7:0] == HOST_TO_MEM_PACKED || cmd_data[7:0] == MEM_TO_HOST_PACKED)
2545                               ? cmd_hwords[LW-1:0] : cmd_lines;
2546
2547  // Progress: cycles in which a line or the sequencer moved, each counted the step after (`moved_q`), so the count's
2548  // carry chain never waits on the channels' handshakes; `told` the count the last progress record carried.
2549  reg [63:0]       progress, told;
2550  reg              moved_q;
2551  reg [31:0]       last_pc;
2552  reg [HB-1:0]     beat_timer;
2553  reg              beat;
2554  wire host_moved = (hr_valid && hr_ready) || (hw_valid && hw_ready);
2555  assign cmd_ready = !busy;
2556  assign st_valid = busy && (reported || beat);
2557  assign st_data = reported ? {cycles - started, result0, {tag, code}, seq + 1'b1}
2558                            : {cycles - started, eng_pc, progress[31:0], {tag, RUNNING}, seq};
2559
2560  wire [BB-1:0]  ch_sel = bank[BB-1:0];
2561  wire [DWM-1:0] host_src = mr_data[ch_sel*DWM +: DWM];
2562  // Into a channel: a line leaves the register once it holds one, a host line comes in when its words fit beside what
2563  // stays. The host lines asked for are the fewest that hold the channel lines, so the last channel line takes the last
2564  // of them.
2565  wire       pin_emit = busy && packed_in && mw_valid[ch_sel] && mw_ready[ch_sel];
2566  wire [7:0] pin_gn1 = pin_emit ? gn - LWD8 : gn;
2567  wire       pin_take = busy && packed_in && hr_valid && pin_gn1 + HWD8 <= GW8;
2568  // Out of a channel: a host line leaves once the register holds one, or holds the last channel line's last words;
2569  // a channel line comes in when its words fit beside what stays.
2570  wire       pout_valid = busy && packed_out && (gn >= HWD8 || (seen[ch_sel] == lines && gn != 0));
2571  wire       pout_emit = pout_valid && hw_ready;
2572  wire [7:0] pout_gn1 = pout_emit ? (gn > HWD8 ? gn - HWD8 : 8'd0) : gn;
2573  wire       pout_take = busy && packed_out && mr_valid[ch_sel] && seen[ch_sel] < lines && pout_gn1 + LWD8 <= GW8;
2574  genvar b;
2575  generate for (b = 0; b < NB; b = b + 1) begin : ch
2576    wire use_this = busy && mask[b];
2577    wire mem_side = (op == HOST_TO_MEM || op == MEM_TO_HOST || packed_in || packed_out) ? bank == b : 1'b1;
2578    assign md_valid[b] = use_this && need_desc[b];
2579    assign md_data[b*(AW+LW+1) +: AW+LW+1] = {to_mem, base, lines};
2580    assign mw_valid[b] = use_this && to_mem && sent[b] < lines &&
2581                         (packed_in ? (mem_side && gn >= LWD8) : from_host ? (mem_side && hr_valid) : 1'b1);
2582    assign mw_data[b*DWM +: DWM] = packed_in ? g[DWM-1:0] : from_host ? {{(DWM-DWH){1'b0}}, hr_data}
2583                                                                     : pattern(seed, line_of(base, sent[b]));
2584    assign mw_flip[b*21 +: 21] = {ECC != 0 && flips_left[b] != 0, flip_bits[b]};
2585    assign mr_ready[b] = use_this && !to_mem && (packed_out ? (mem_side && pout_take) : to_host ? (mem_side && hw_ready) : 1'b1);
2586    assign mdone_ready[b] = 1'b1;
2587  end endgenerate
2588
2589  assign hr_ready = packed_in ? pin_take : eng_table ? busy && taken < lines
2590                                                     : busy && from_host && mw_ready[ch_sel] && sent[ch_sel] < lines;
2591  assign hw_valid = packed_out ? pout_valid : store_read ? busy && sent_beats < lines && beat_ready
2592                                                         : busy && to_host && mr_valid[ch_sel];
2593  assign hw_data = packed_out ? g[DWH-1:0] : store_read ? eng_host_rdata : host_src[DWH-1:0];
2594  assign hrd_valid = busy && (from_host || eng_table) && need_hdesc;
2595  assign hrd_data = {hbase, hlines};
2596  assign hwd_valid = busy && (to_host || store_read) && need_hdesc;
2597  assign hwd_data = {hbase, hlines};
2598  assign hack_ready = 1'b1;
2599  assign eng_host_wr = busy && eng_table && hr_valid && hr_ready;
2600  assign eng_host_addr = tbase + taken;
2601  assign eng_host_wdata = hr_data;
2602  assign eng_host_raddr = sbase + sent_beats;
2603
2604  integer i;
2605  reg [63:0] total;
2606  always @(*) begin
2607    total = 0;
2608    for (i = 0; i < NB; i = i + 1) total = total + wrong[i];
2609  end
2610
2611  always @(posedge clk or negedge rst_n) begin
2612    if (!rst_n) begin
2613      busy <= 1'b0; reported <= 1'b0; op <= 0; tag <= 0; code <= 0; seq <= 0; cycles <= 0; started <= 0;
2614      result0 <= 0; mask <= 0; need_desc <= 0; wrote <= 0; done <= 0; base <= 0; lines <= 0; seed <= 0;
2615      hbase <= 0; bank <= 0; need_hdesc <= 1'b0; acks <= 0; bursts <= 0; g <= 0; gn <= 0; hlines <= 0;
2616      for (i = 0; i < NB; i = i + 1) begin
2617        sent[i] <= 0; seen[i] <= 0; wrong[i] <= 0; flips_left[i] <= 0; flip_bits[i] <= 0; fixed[i] <= 0; lost[i] <= 0;
2618      end
2619      card_mode <= 1'b0; eng_start <= 1'b0; eng_job_at <= 0; eng_job_end <= 0; eng_rows <= 0; eng_knobs <= 0;
2620      eng_host_table <= 0; port_space <= 0; port_base <= 0; port_granule_log2 <= 0; port_clear <= 1'b0;
2621      tbase <= 0; taken <= 0; sbase <= 0; sent_beats <= 0; beat_ready <= 1'b0; run_hold <= 0;
2622      progress <= 0; told <= 0; last_pc <= 0; beat_timer <= 0; beat <= 1'b0; moved_q <= 1'b0;
2623    end else if (ce) begin
2624      cycles <= cycles + 1'b1;
2625      moved_q <= moved || host_moved || eng_pc != last_pc;
2626      progress <= progress + {63'd0, moved_q};
2627      last_pc <= eng_pc;
2628      eng_start <= 1'b0;
2629      port_clear <= 1'b0;
2630      if (!busy) begin
2631        if (cmd_valid) begin
2632          busy <= 1'b1;
2633          reported <= 1'b0;
2634          beat <= 1'b0;
2635          beat_timer <= 0;
2636          told <= progress;
2637          op <= cmd_data[7:0];
2638          tag <= cmd_data[63:8];
2639          started <= cycles;
2640          code <= 8'd0;
2641          result0 <= 0;
2642          seed <= cmd_data[255:224];
2643          lines <= cmd_data[192+LW-1:192];
2644          need_hdesc <= cmd_data[7:0] == HOST_TO_MEM || cmd_data[7:0] == MEM_TO_HOST ||
2645                        cmd_data[7:0] == HOST_TO_MEM_PACKED || cmd_data[7:0] == MEM_TO_HOST_PACKED ||
2646                        (ENGINE != 0 && (cmd_data[7:0] == ENGINE_TABLE || cmd_data[7:0] == STORE_READ));
2647          hbase <= cmd_data[64+HAW-1:64];
2648          taken <= 0;
2649          sent_beats <= 0;
2650          beat_ready <= 1'b0;
2651          acks <= 0;
2652          hlines <= cmd_hlines;
2653          bursts <= (cmd_hlines + HMAXL - 1'b1) >> HMAXB_LOG2;
2654          g <= 0;
2655          gn <= 0;
2656          for (i = 0; i < NB; i = i + 1) begin sent[i] <= 0; seen[i] <= 0; wrong[i] <= 0; end
2657          done <= 0;
2658          case (cmd_data[7:0])
2659            FILL, CHECK: begin
2660              mask <= cmd_data[64+NB-1:64];
2661              need_desc <= cmd_data[64+NB-1:64];
2662              base <= cmd_data[128+AW-1:128];
2663            end
2664            HOST_TO_MEM, MEM_TO_HOST, HOST_TO_MEM_PACKED, MEM_TO_HOST_PACKED: begin
2665              hbase <= cmd_data[64+HAW-1:64];
2666              bank <= cmd_data[191:184];
2667              mask <= {{(NB-1){1'b0}}, 1'b1} << cmd_data[184+BB-1:184];
2668              need_desc <= {{(NB-1){1'b0}}, 1'b1} << cmd_data[184+BB-1:184];
2669              base <= cmd_data[128+AW-1:128];
2670            end
2671            NOP: begin mask <= 0; need_desc <= 0; reported <= 1'b1; end
2672            INJECT: begin
2673              mask <= 0; need_desc <= 0; reported <= 1'b1;
2674              if (ECC == 0) code <= UNKNOWN_OP;
2675              else for (i = 0; i < NB; i = i + 1) if (cmd_data[64 + i]) begin
2676                flips_left[i] <= cmd_data[192 +: 32];
2677                flip_bits[i] <= {cmd_data[144 +: 10], cmd_data[128 +: 10]};
2678              end
2679            end
2680            ECC_COUNTS: begin
2681              mask <= 0; need_desc <= 0; reported <= 1'b1;
2682              if (ECC == 0) code <= UNKNOWN_OP;
2683              else result0 <= {lost[cmd_data[64 +: BB]], fixed[cmd_data[64 +: BB]]};
2684            end
2685            ENGINE_CONFIG, ENGINE_TABLE, ENGINE_RUN, STORE_READ: begin
2686              mask <= 0; need_desc <= 0;
2687              if (ENGINE == 0) begin
2688                code <= UNKNOWN_OP; reported <= 1'b1;
2689              end else case (cmd_data[7:0])
2690                ENGINE_CONFIG: begin
2691                  eng_rows <= cmd_data[64 +: 16];
2692                  eng_knobs <= cmd_data[80 +: 4];
2693                  port_space <= cmd_data[128 +: 32];
2694                  port_base <= cmd_data[192 +: AW];
2695                  port_granule_log2 <= cmd_data[248 +: 5];
2696                  reported <= 1'b1;
2697                end
2698                ENGINE_TABLE: begin
2699                  tbase <= cmd_data[128 +: 32];
2700                  eng_host_table <= cmd_data[160 +: 3];
2701                end
2702                ENGINE_RUN: begin
2703                  eng_job_at <= cmd_data[64 +: 32];
2704                  eng_job_end <= cmd_data[128 +: 32];
2705                  card_mode <= 1'b1;
2706                  port_clear <= 1'b1;
2707                  eng_start <= 1'b1;
2708                  run_hold <= 2'd2;
2709                end
2710                default: sbase <= cmd_data[128 +: 32];
2711              endcase
2712            end
2713            default: begin mask <= 0; need_desc <= 0; code <= UNKNOWN_OP; reported <= 1'b1; end
2714          endcase
2715        end
2716      end else if (reported) begin
2717        beat <= 1'b0;
2718        if (st_ready) begin
2719          busy <= 1'b0;
2720          reported <= 1'b0;
2721          seq <= seq + 1'b1;
2722        end
2723      end else begin
2724        beat_timer <= beat_timer + 1'b1;
2725        if (beat) begin
2726          if (st_ready) begin beat <= 1'b0; told <= progress; end
2727        end else if (&beat_timer && progress != told) beat <= 1'b1;
2728        for (i = 0; i < NB; i = i + 1) begin
2729          if (md_valid[i] && md_ready[i]) need_desc[i] <= 1'b0;
2730          if (mw_valid[i] && mw_ready[i]) begin
2731            sent[i] <= sent[i] + 1'b1;
2732            if (flips_left[i] != 0) flips_left[i] <= flips_left[i] - 1'b1;
2733          end
2734          if (mr_valid[i] && mr_ready[i]) begin
2735            seen[i] <= seen[i] + 1'b1;
2736            if (op == CHECK && mr_data[i*DWM +: DWM] !== pattern(seed, line_of(base, seen[i]))) wrong[i] <= wrong[i] + 1'b1;
2737          end
2738          if (mr_valid[i] && mr_taken[i]) begin
2739            fixed[i] <= fixed[i] + ones(mr_ecc[i*16 +: 8]);
2740            lost[i] <= lost[i] + ones(mr_ecc[i*16 + 8 +: 8]);
2741          end
2742          if (mdone_valid[i] && mask[i]) done[i] <= 1'b1;
2743        end
2744        if ((hrd_valid && hrd_ready) || (hwd_valid && hwd_ready)) need_hdesc <= 1'b0;
2745        if (hack_valid) acks <= acks + 1'b1;
2746        if (eng_host_wr) taken <= taken + 1'b1;
2747        if (store_read) begin
2748          if (hw_valid && hw_ready) begin
2749            sent_beats <= sent_beats + 1'b1;
2750            beat_ready <= 1'b0;
2751          end else beat_ready <= sent_beats < lines;
2752        end
2753        if (run_hold != 0) run_hold <= run_hold - 1'b1;
2754        if (packed_in) begin
2755          g <= (pin_emit ? g >> DWM : g) | (pin_take ? {{(64*GW-DWH){1'b0}}, hr_data} << (64 * pin_gn1) : {(64*GW){1'b0}});
2756          gn <= pin_gn1 + (pin_take ? HWD8 : 8'd0);
2757        end
2758        if (packed_out) begin
2759          g <= (pout_emit ? g >> DWH : g) | (pout_take ? {{(64*GW-DWM){1'b0}}, host_src} << (64 * pout_gn1) : {(64*GW){1'b0}});
2760          gn <= pout_gn1 + (pout_take ? LWD8 : 8'd0);
2761        end
2762        case (op)
2763          FILL, HOST_TO_MEM, HOST_TO_MEM_PACKED: if ((done | (mdone_valid & mask)) == mask && need_desc == 0) begin
2764            reported <= 1'b1;
2765          end
2766          // Every line compared, in the registers result0 sums, before the record is cut.
2767          CHECK: if (all_seen(mask) && need_desc == 0) begin
2768            reported <= 1'b1;
2769            result0 <= total;
2770          end
2771          MEM_TO_HOST, MEM_TO_HOST_PACKED, STORE_READ:
2772            if (acks + {{(LW-1){1'b0}}, hack_valid} == bursts && !need_hdesc) reported <= 1'b1;
2773          ENGINE_TABLE: if ((eng_host_wr ? taken_last : taken_all) && !need_hdesc) reported <= 1'b1;
2774          ENGINE_RUN: if (run_hold == 0 && !eng_busy) begin
2775            reported <= 1'b1;
2776            card_mode <= 1'b0;
2777            result0 <= {eng_pc, 30'd0, port_fault, eng_fault};
2778          end
2779          default: reported <= 1'b1;
2780        endcase
2781      end
2782    end
2783  end
2784
2785  function all_seen(input [NB-1:0] m);
2786    integer j;
2787    begin
2788      all_seen = 1'b1;
2789      for (j = 0; j < NB; j = j + 1) if (m[j] && seen[j] < lines) all_seen = 1'b0;
2790    end
2791  endfunction
2792endmodule
2793"##;
2794
2795const SHELL: &str = r##"// One memory channel as the core sees it: descriptors and lines to write (with ECC the bus bits each flips) crossing
2796// into the channel's clock through FIFOs, lines read (with ECC their words' verdicts) and write-done tokens crossing
2797// out of it, the channel's stream engine on the bank's Avalon-MM port. A read-data FIFO is deep enough to hold
2798// a whole run of bursts the channel has in flight. The FIFOs' sides toward the core step at its step (uce).
2799module logos_d5005_channel #(
2800  parameter AW = 27,           // channel line-address bits
2801  parameter DWM = 512,         // channel bus bits a line
2802  parameter ECC = 0,           // 1: each line's eight words SECDED(72,64)-coded in the bus's ECC lanes
2803  parameter MBW = 7,           // channel burstcount bits
2804  parameter LW = 32,
2805  parameter MAXB = 64,         // lines a burst
2806  parameter RAW = 9,           // log2 of the read-data FIFO's lines
2807  parameter WAW = 6,           // log2 of the write-data FIFO's lines
2808  parameter DWL = ECC ? DWM / 9 * 8 : DWM   // line bits the core moves
2809) (
2810  input              uclk,
2811  input              uce,
2812  input              uclk_rst_n,
2813  input              mem_clk,
2814  input              mem_rst_n,
2815  input              md_valid,
2816  output             md_ready,
2817  input  [AW+LW:0]   md_data,
2818  output             mr_valid,
2819  input              mr_ready,
2820  output [DWL-1:0]   mr_data,
2821  output [15:0]      mr_ecc,
2822  input              mw_valid,
2823  output             mw_ready,
2824  input  [DWL-1:0]   mw_data,
2825  input  [20:0]      mw_flip,
2826  output             mdone_valid,
2827  input              mdone_ready,
2828  output [AW-1:0]    mem_address,
2829  output             mem_read,
2830  output             mem_write,
2831  output [MBW-1:0]   mem_burstcount,
2832  output [DWM-1:0]   mem_writedata,
2833  output [DWM/8-1:0] mem_byteenable,
2834  input              mem_waitrequest,
2835  input  [DWM-1:0]   mem_readdata,
2836  input              mem_readdatavalid
2837);
2838  localparam DLW = AW + LW + 1;
2839  localparam RFW = ECC ? DWL + 16 : DWL;     // a read-FIFO word: the line, with ECC its words' verdicts
2840  localparam WFW = ECC ? DWL + 21 : DWL;     // a write-FIFO word: the line, with ECC the bus bits it flips
2841  wire           d_valid, d_ready, r_push, w_valid, w_ready, done;
2842  wire [DLW-1:0] d_data;
2843  wire [DWL-1:0] r_data, w_data;
2844  wire [15:0]    r_ecc;
2845  wire [20:0]    w_flip;
2846  wire [RFW-1:0] r_word, mr_word;
2847  wire [WFW-1:0] w_word, mw_word;
2848  wire [RAW:0]   r_room, r_used;
2849  wire [2:0]     d_wfree, d_rused, done_wfree, done_rused;
2850  wire [WAW:0]   w_wfree, w_rused;
2851  wire           r_wready, done_wready, done_token;
2852  generate if (ECC) begin : coded
2853    assign mw_word = {mw_flip, mw_data};
2854    assign {w_flip, w_data} = w_word;
2855    assign r_word = {r_ecc, r_data};
2856    assign {mr_ecc, mr_data} = mr_word;
2857  end else begin : plain
2858    assign mw_word = mw_data;
2859    assign w_data = w_word;
2860    assign w_flip = 21'h0;
2861    assign r_word = r_data;
2862    assign mr_data = mr_word;
2863    assign mr_ecc = 16'h0;
2864  end endgenerate
2865  logos_dcfifo_mlab #(.W(DLW), .AW(2), .HOLDS(1)) desc_fifo(.wclk(uclk), .wce(uce), .wrst_n(uclk_rst_n), .wvalid(md_valid),
2866    .wready(md_ready), .wdata(md_data), .wfree(d_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n), .rvalid(d_valid),
2867    .rready(d_ready), .rdata(d_data), .rused(d_rused));
2868  logos_dcfifo_m20k #(.W(WFW), .AW(WAW), .HOLDS(1)) write_fifo(.wclk(uclk), .wce(uce), .wrst_n(uclk_rst_n), .wvalid(mw_valid),
2869    .wready(mw_ready), .wdata(mw_word), .wfree(w_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n), .rvalid(w_valid),
2870    .rready(w_ready), .rdata(w_word), .rused(w_rused));
2871  logos_dcfifo_m20k #(.W(RFW), .AW(RAW), .HOLDS(1)) read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(r_push),
2872    .wready(r_wready), .wdata(r_word), .wfree(r_room), .rclk(uclk), .rce(uce), .rrst_n(uclk_rst_n), .rvalid(mr_valid),
2873    .rready(mr_ready), .rdata(mr_word), .rused(r_used));
2874  logos_dcfifo_mlab #(.W(1), .AW(2), .TOKENS(1)) done_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(done),
2875    .wready(done_wready), .wdata(1'b1), .wfree(done_wfree), .rclk(uclk), .rce(uce), .rrst_n(uclk_rst_n),
2876    .rvalid(mdone_valid), .rready(mdone_ready), .rdata(done_token), .rused(done_rused));
2877  wire [RAW:0] owed;
2878  wire         busy;
2879  logos_stream_avmm #(.AW(AW), .DW(DWM), .BW(MBW), .LW(LW), .MAXB(MAXB), .RW(RAW+1), .ECC(ECC)) stream(
2880    .clk(mem_clk), .rst_n(mem_rst_n), .d_valid(d_valid), .d_ready(d_ready), .d_data(d_data),
2881    .r_valid(r_push), .r_data(r_data), .r_ecc(r_ecc), .r_room(r_room), .w_valid(w_valid), .w_ready(w_ready),
2882    .w_data(w_data), .w_flip(w_flip), .avm_address(mem_address), .avm_read(mem_read), .avm_write(mem_write),
2883    .avm_burstcount(mem_burstcount), .avm_writedata(mem_writedata), .avm_byteenable(mem_byteenable),
2884    .avm_waitrequest(mem_waitrequest), .avm_readdata(mem_readdata), .avm_readdatavalid(mem_readdatavalid),
2885    .owed(owed), .busy(busy), .wdone(done));
2886endmodule
2887
2888// The engine's shell on the D5005: every port the OFS platform gives the accelerator, flat, each in its own clock,
2889// and the only paths between those clocks the dual-clock FIFOs.
2890//
2891//   pclk     the host channel: MMIO into the registers, host memory through a reader and a writer;
2892//   mem_clk  one clock a memory channel: its stream engine on the channel's Avalon-MM port;
2893//   uclk     the user clock: the core, the engine and every FIFO's side toward them.
2894//
2895// The shell makes no clock: its compute side — the core, an engine build's card port and every FIFO's side toward
2896// them — steps in the user clock at cce, high in the user cycles that end with the rising edges of the user clock
2897// divided by the build's divide (every cycle in an undivided build), from crst_n, a reset released at the second.
2898// The core sends descriptors and the lines it writes through FIFOs into each channel's clock and takes the lines
2899// read back through FIFOs out of it (logos_d5005_channel); commands and status records cross between the registers and
2900// the core the same way. With ECC set the channels' lines are SECDED-coded at each bank's port.
2901module logos_d5005_shell #(
2902  parameter NB = 4,            // memory channels
2903  parameter AW = 27,           // channel line-address bits
2904  parameter DWM = 512,         // channel bus bits a line
2905  parameter ECC = 0,           // 1: each line's eight words SECDED(72,64)-coded in the bus's ECC lanes
2906  parameter MBW = 7,           // channel burstcount bits
2907  parameter HAW = 51,          // host line-address bits
2908  parameter DWH = 512,         // host line bits
2909  parameter HBW = 7,           // host burstcount bits
2910  parameter MAW = 15,          // MMIO word-address bits
2911  parameter MAXB = 64,         // lines a burst
2912  parameter LW = 32,
2913  parameter RAW = 9,           // log2 of a read-data FIFO's lines
2914  parameter WAW = 6,           // log2 of a write-data FIFO's lines
2915  parameter [63:0] AFU_ID_L = 64'h0,
2916  parameter [63:0] AFU_ID_H = 64'h0,
2917  parameter [63:0] BUILD = 64'h0,
2918  parameter HB = 16            // log2 of the core cycles between a running command's progress records, well inside a
2919                               // host's wait: 42 ms of the engine build's 1.5625 MHz core against the card's minute of
2920                               // reads, some 2.6M reads against 2^24 in a simulation of the divided clock
2921) (
2922  input                  pclk,
2923  input                  pclk_rst_n,
2924  input                  uclk,
2925  input                  uclk_rst_n,
2926  // MMIO, a 64-bit Avalon-MM slave.
2927  input  [MAW-1:0]       mmio_address,
2928  input                  mmio_read,
2929  input                  mmio_write,
2930  input  [63:0]          mmio_writedata,
2931  input  [7:0]           mmio_byteenable,
2932  output                 mmio_waitrequest,
2933  output [63:0]          mmio_readdata,
2934  output                 mmio_readdatavalid,
2935  // Host memory, an Avalon-MM read/write master.
2936  output [HAW-1:0]       host_rd_address,
2937  output                 host_rd_read,
2938  output [HBW-1:0]       host_rd_burstcount,
2939  input                  host_rd_waitrequest,
2940  input  [DWH-1:0]       host_rd_readdata,
2941  input                  host_rd_readdatavalid,
2942  output [HAW-1:0]       host_wr_address,
2943  output                 host_wr_write,
2944  output [HBW-1:0]       host_wr_burstcount,
2945  output [DWH-1:0]       host_wr_writedata,
2946  output [DWH/8-1:0]     host_wr_byteenable,
2947  input                  host_wr_waitrequest,
2948  input                  host_wr_writeresponsevalid,
2949  // The memory channels, each an Avalon-MM master in its own clock.
2950  input  [NB-1:0]        mem_clk,
2951  input  [NB-1:0]        mem_rst_n,
2952  output [NB*AW-1:0]     mem_address,
2953  output [NB-1:0]        mem_read,
2954  output [NB-1:0]        mem_write,
2955  output [NB*MBW-1:0]    mem_burstcount,
2956  output [NB*DWM-1:0]    mem_writedata,
2957  output [NB*DWM/8-1:0]  mem_byteenable,
2958  input  [NB-1:0]        mem_waitrequest,
2959  input  [NB*DWM-1:0]    mem_readdata,
2960  input  [NB-1:0]        mem_readdatavalid
2961);
2962  localparam DLW = AW + LW + 1;
2963  localparam HDW = HAW + LW;
2964  localparam DWL = ECC ? DWM / 9 * 8 : DWM;  // line bits the core moves
2965/*ENGINE_PARAM*/
2966/*COMPUTE*/
2967
2968  // Registers <-> core.
2969  wire         cmd_wvalid, cmd_wready, cmd_rvalid, cmd_rready;
2970  wire [255:0] cmd_wdata, cmd_rdata;
2971  wire [4:0]   cmd_wfree, cmd_rused;
2972  wire         st_wvalid, st_wready, st_rvalid, st_rready;
2973  wire [255:0] st_wdata, st_rdata;
2974  logos_csr #(.MAW(MAW), .AFU_ID_L(AFU_ID_L), .AFU_ID_H(AFU_ID_H), .BUILD(BUILD), .CW(5)) csr(
2975    .clk(pclk), .rst_n(pclk_rst_n), .mmio_address(mmio_address), .mmio_read(mmio_read), .mmio_write(mmio_write),
2976    .mmio_writedata(mmio_writedata), .mmio_byteenable(mmio_byteenable), .mmio_waitrequest(mmio_waitrequest),
2977    .mmio_readdata(mmio_readdata), .mmio_readdatavalid(mmio_readdatavalid), .cmd_valid(cmd_wvalid),
2978    .cmd_ready(cmd_wready), .cmd_data(cmd_wdata), .cmd_space(cmd_wfree), .st_valid(st_rvalid), .st_ready(st_rready),
2979    .st_data(st_rdata));
2980  logos_dcfifo_mlab #(.W(256), .AW(4)) cmd_fifo(.wclk(pclk), .wce(1'b1), .wrst_n(pclk_rst_n), .wvalid(cmd_wvalid),
2981    .wready(cmd_wready), .wdata(cmd_wdata), .wfree(cmd_wfree), .rclk(uclk), .rce(cce), .rrst_n(crst_n), .rvalid(cmd_rvalid),
2982    .rready(cmd_rready), .rdata(cmd_rdata), .rused(cmd_rused));
2983  wire [4:0] st_wfree, st_rused;
2984  logos_dcfifo_mlab #(.W(256), .AW(4)) st_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(st_wvalid), .wready(st_wready),
2985    .wdata(st_wdata), .wfree(st_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(st_rvalid),
2986    .rready(st_rready), .rdata(st_rdata), .rused(st_rused));
2987
2988  // The core's side of every channel FIFO.
2989  wire [NB-1:0]     md_valid, md_ready, mr_valid, mr_ready, mw_valid, mw_ready, mdone_valid, mdone_ready;
2990  wire [NB*DLW-1:0] md_data;
2991  wire [NB*DWL-1:0] mr_data, mw_data;
2992  wire [NB*16-1:0]  mr_ecc;
2993  wire [NB*21-1:0]  mw_flip;
2994
2995  genvar b;
2996  generate for (b = 0; b < NB; b = b + 1) begin : ch
2997    logos_d5005_channel #(.AW(AW), .DWM(DWM), .ECC(ECC), .MBW(MBW), .LW(LW), .MAXB(MAXB), .RAW(RAW), .WAW(WAW)) channel(
2998      .uclk(uclk), .uce(cce), .uclk_rst_n(crst_n), .mem_clk(mem_clk[b]), .mem_rst_n(mem_rst_n[b]),
2999      .md_valid(md_valid[b]), .md_ready(md_ready[b]), .md_data(md_data[b*DLW +: DLW]),
3000      .mr_valid(mr_valid[b]), .mr_ready(mr_ready[b]), .mr_data(mr_data[b*DWL +: DWL]), .mr_ecc(mr_ecc[b*16 +: 16]),
3001      .mw_valid(mw_valid[b]), .mw_ready(mw_ready[b]), .mw_data(mw_data[b*DWL +: DWL]), .mw_flip(mw_flip[b*21 +: 21]),
3002      .mdone_valid(mdone_valid[b]), .mdone_ready(mdone_ready[b]),
3003      .mem_address(mem_address[b*AW +: AW]), .mem_read(mem_read[b]), .mem_write(mem_write[b]),
3004      .mem_burstcount(mem_burstcount[b*MBW +: MBW]), .mem_writedata(mem_writedata[b*DWM +: DWM]),
3005      .mem_byteenable(mem_byteenable[b*DWM/8 +: DWM/8]), .mem_waitrequest(mem_waitrequest[b]),
3006      .mem_readdata(mem_readdata[b*DWM +: DWM]), .mem_readdatavalid(mem_readdatavalid[b]));
3007  end endgenerate
3008
3009  // Host memory.
3010  wire           hrd_valid, hrd_ready, hwd_valid, hwd_ready, hr_valid, hr_ready, hw_valid, hw_ready, hack_valid, hack_ready;
3011  wire [HDW-1:0] hrd_data, hwd_data;
3012  wire [DWH-1:0] hr_data, hw_data;
3013  wire           phrd_valid, phrd_ready, phwd_valid, phwd_ready, phr_push, phr_wready, phw_valid, phw_ready, pack_wready;
3014  wire [HDW-1:0] phrd_data, phwd_data;
3015  wire [DWH-1:0] phr_data, phw_data;
3016  wire [2:0]     hrd_wfree, hrd_rused, hwd_wfree, hwd_rused;
3017  wire [RAW:0]   hr_room, hr_used;
3018  wire [WAW:0]   hw_wfree, hw_rused;
3019  wire [4:0]     hack_wfree, hack_rused;
3020  wire           hack_token;
3021  logos_dcfifo_mlab #(.W(HDW), .AW(2), .HOLDS(1)) hrd_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(hrd_valid), .wready(hrd_ready),
3022    .wdata(hrd_data), .wfree(hrd_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(phrd_valid),
3023    .rready(phrd_ready), .rdata(phrd_data), .rused(hrd_rused));
3024  logos_dcfifo_mlab #(.W(HDW), .AW(2), .HOLDS(1)) hwd_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(hwd_valid), .wready(hwd_ready),
3025    .wdata(hwd_data), .wfree(hwd_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(phwd_valid),
3026    .rready(phwd_ready), .rdata(phwd_data), .rused(hwd_rused));
3027  logos_dcfifo_m20k #(.W(DWH), .AW(RAW), .HOLDS(1)) hr_fifo(.wclk(pclk), .wce(1'b1), .wrst_n(pclk_rst_n), .wvalid(phr_push),
3028    .wready(phr_wready), .wdata(phr_data), .wfree(hr_room), .rclk(uclk), .rce(cce), .rrst_n(crst_n), .rvalid(hr_valid),
3029    .rready(hr_ready), .rdata(hr_data), .rused(hr_used));
3030  logos_dcfifo_m20k #(.W(DWH), .AW(WAW), .HOLDS(1)) hw_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(hw_valid), .wready(hw_ready),
3031    .wdata(hw_data), .wfree(hw_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(phw_valid),
3032    .rready(phw_ready), .rdata(phw_data), .rused(hw_rused));
3033  // A host write response cannot wait: a token, counted on the FIFO's write side until it is taken.
3034  logos_dcfifo_mlab #(.W(1), .AW(4), .TOKENS(1)) hack_fifo(.wclk(pclk), .wce(1'b1), .wrst_n(pclk_rst_n), .wvalid(host_wr_writeresponsevalid),
3035    .wready(pack_wready), .wdata(1'b1), .wfree(hack_wfree), .rclk(uclk), .rce(cce), .rrst_n(crst_n), .rvalid(hack_valid),
3036    .rready(hack_ready), .rdata(hack_token), .rused(hack_rused));
3037  wire [RAW:0] howed;
3038  wire         hrd_active, hwd_active, hw_done;
3039  logos_stream_reader #(.AW(HAW), .DW(DWH), .BW(HBW), .LW(LW), .MAXB(MAXB), .RW(RAW+1)) host_reader(
3040    .clk(pclk), .rst_n(pclk_rst_n), .d_valid(phrd_valid), .d_ready(phrd_ready), .d_data(phrd_data), .r_valid(phr_push),
3041    .r_data(phr_data), .r_room(hr_room), .avm_address(host_rd_address), .avm_read(host_rd_read),
3042    .avm_burstcount(host_rd_burstcount), .avm_waitrequest(host_rd_waitrequest), .avm_readdata(host_rd_readdata),
3043    .avm_readdatavalid(host_rd_readdatavalid), .owed(howed), .active(hrd_active));
3044  logos_stream_writer #(.AW(HAW), .DW(DWH), .BW(HBW), .LW(LW), .MAXB(MAXB)) host_writer(
3045    .clk(pclk), .rst_n(pclk_rst_n), .d_valid(phwd_valid), .d_ready(phwd_ready), .d_data(phwd_data), .w_valid(phw_valid),
3046    .w_ready(phw_ready), .w_data(phw_data), .avm_address(host_wr_address), .avm_write(host_wr_write),
3047    .avm_burstcount(host_wr_burstcount), .avm_writedata(host_wr_writedata), .avm_byteenable(host_wr_byteenable),
3048    .avm_waitrequest(host_wr_waitrequest), .active(hwd_active), .done(hw_done));
3049
3050  // The channels are the core's, or the card port's while the core runs the engine's step.
3051  wire              card_mode;
3052  wire [NB-1:0]     c_md_valid, c_mr_ready, c_mw_valid, c_mdone_ready, p_md_valid, p_mr_ready, p_mw_valid, p_mdone_ready;
3053  wire [NB*DLW-1:0] c_md_data, p_md_data;
3054  wire [NB*DWL-1:0] c_mw_data, p_mw_data;
3055  wire [NB*21-1:0]  c_mw_flip;
3056  assign md_valid = card_mode ? p_md_valid : c_md_valid;
3057  assign md_data = card_mode ? p_md_data : c_md_data;
3058  assign mr_ready = card_mode ? p_mr_ready : c_mr_ready;
3059  assign mw_valid = card_mode ? p_mw_valid : c_mw_valid;
3060  assign mw_data = card_mode ? p_mw_data : c_mw_data;
3061  assign mw_flip = card_mode ? {(NB*21){1'b0}} : c_mw_flip;
3062  assign mdone_ready = card_mode ? p_mdone_ready : c_mdone_ready;
3063
3064  wire          eng_start, eng_busy, eng_fault, eng_host_wr, port_clear, port_fault;
3065  wire [31:0]   eng_job_at, eng_job_end, eng_pc, eng_host_addr, eng_host_raddr, port_space;
3066  wire [15:0]   eng_rows;
3067  wire [3:0]    eng_knobs;
3068  wire [2:0]    eng_host_table;
3069  wire [DWH-1:0] eng_host_wdata, eng_host_rdata;
3070  wire [AW-1:0] port_base;
3071  wire [4:0]    port_granule_log2;
3072  wire moved = |(mr_valid & mr_ready) || |(mw_valid & mw_ready);
3073  logos_bringup_core #(.NB(NB), .AW(AW), .DWM(DWL), .ECC(ECC), .ENGINE(ENGINE), .HAW(HAW), .DWH(DWH), .LW(LW),
3074                       .HMAXB(MAXB), .HB(HB)) core(
3075    .clk(uclk), .ce(cce), .rst_n(crst_n), .cmd_valid(cmd_rvalid), .cmd_ready(cmd_rready), .cmd_data(cmd_rdata),
3076    .st_valid(st_wvalid), .st_ready(st_wready), .st_data(st_wdata), .md_valid(c_md_valid), .md_ready(md_ready),
3077    .md_data(c_md_data), .mr_valid(mr_valid), .mr_ready(c_mr_ready), .mr_data(mr_data), .mr_ecc(mr_ecc),
3078    .mw_valid(c_mw_valid), .mw_ready(mw_ready), .mw_data(c_mw_data), .mw_flip(c_mw_flip), .mdone_valid(mdone_valid),
3079    .mdone_ready(c_mdone_ready),
3080    .hrd_valid(hrd_valid), .hrd_ready(hrd_ready), .hrd_data(hrd_data), .hr_valid(hr_valid), .hr_ready(hr_ready),
3081    .hr_data(hr_data), .hwd_valid(hwd_valid), .hwd_ready(hwd_ready), .hwd_data(hwd_data), .hw_valid(hw_valid),
3082    .hw_ready(hw_ready), .hw_data(hw_data), .hack_valid(hack_valid), .hack_ready(hack_ready), .mr_taken(mr_ready),
3083    .moved(moved),
3084    .card_mode(card_mode), .eng_start(eng_start), .eng_job_at(eng_job_at), .eng_job_end(eng_job_end),
3085    .eng_rows(eng_rows), .eng_knobs(eng_knobs), .eng_busy(eng_busy), .eng_fault(eng_fault), .eng_pc(eng_pc),
3086    .eng_host_wr(eng_host_wr), .eng_host_table(eng_host_table), .eng_host_addr(eng_host_addr),
3087    .eng_host_wdata(eng_host_wdata), .eng_host_raddr(eng_host_raddr), .eng_host_rdata(eng_host_rdata),
3088    .port_space(port_space), .port_base(port_base), .port_granule_log2(port_granule_log2), .port_clear(port_clear),
3089    .port_fault(port_fault));
3090/*ENGINE*/
3091endmodule
3092"##;
3093
3094/// The shell's engine with its card port, between the core's engine ports and the channels' card side.
3095const SHELL_ENGINE: &str = r##"
3096  // The engine, its memory port the card port on the channels.
3097  wire          e_rd, e_rd_valid, e_wr, e_wr_ready;
3098  wire [31:0]   e_rd_line, e_wr_line;
3099  wire [511:0]  e_rd_data, e_wr_data;
3100  wire [63:0]   e_wr_mask;
3101  logos_engine engine(.clk(uclk), .rst(!crst_n), .start(eng_start), .job_at(eng_job_at), .job_end(eng_job_end),
3102    .rows(eng_rows), .state_f16(eng_knobs[0]), .cache_int8(eng_knobs[1]), .two_words(eng_knobs[2]),
3103    .per_block(eng_knobs[3]), .busy(eng_busy), .fault(eng_fault), .pc(eng_pc), .host_wr(eng_host_wr),
3104    .host_table(eng_host_table), .host_addr(eng_host_addr), .host_wdata(eng_host_wdata), .host_raddr(eng_host_raddr),
3105    .host_rdata(eng_host_rdata), .mem_rd(e_rd), .mem_rd_line(e_rd_line), .mem_rd_valid(e_rd_valid),
3106    .mem_rd_data(e_rd_data), .mem_wr(e_wr), .mem_wr_line(e_wr_line), .mem_wr_data(e_wr_data), .mem_wr_mask(e_wr_mask),
3107    .mem_wr_ready(e_wr_ready));
3108  logos_card_port #(.NB(NB), .AW(AW), .LW(LW), .DL(DWL)) port(.clk(uclk), .ce(cce), .rst_n(crst_n), .space(port_space),
3109    .base(port_base), .granule_log2(port_granule_log2), .clear(port_clear), .fault(port_fault), .mem_rd(e_rd),
3110    .mem_rd_line(e_rd_line), .mem_rd_valid(e_rd_valid), .mem_rd_data(e_rd_data), .mem_wr(e_wr),
3111    .mem_wr_line(e_wr_line), .mem_wr_data(e_wr_data), .mem_wr_mask(e_wr_mask), .mem_wr_ready(e_wr_ready),
3112    .md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data), .mr_valid(mr_valid), .mr_ready(p_mr_ready),
3113    .mr_data(mr_data), .mr_ecc(mr_ecc), .mw_valid(p_mw_valid), .mw_ready(mw_ready), .mw_data(p_mw_data),
3114    .mdone_valid(mdone_valid), .mdone_ready(p_mdone_ready));
3115"##;
3116
3117/// A shell without an engine: the engine's ports idle, the card side of the channels never asking.
3118const SHELL_NO_ENGINE: &str = r##"
3119  assign eng_busy = 1'b0;
3120  assign eng_fault = 1'b0;
3121  assign eng_pc = 32'd0;
3122  assign eng_host_rdata = {DWH{1'b0}};
3123  assign port_fault = 1'b0;
3124  assign p_md_valid = {NB{1'b0}};
3125  assign p_md_data = {(NB*DLW){1'b0}};
3126  assign p_mr_ready = {NB{1'b0}};
3127  assign p_mw_valid = {NB{1'b0}};
3128  assign p_mw_data = {(NB*DWL){1'b0}};
3129  assign p_mdone_ready = {NB{1'b0}};
3130"##;
3131
3132const SDC: &str = r##"# The shell's dual-clock FIFOs, constrained as the D5005 FIM constrains its own (syn/setup/dcfifo.sdc): each Gray
3133# pointer's bits cross with their skew under 0.8 of the source clock's period and their net delay under 0.8 of the
3134# destination's, setup and hold relaxed on those paths; the net between a synchronizer's two registers held to 0.8 of
3135# its clock; and a RAM word read on the other side, written two destination edges before its pointer arrived, held to
3136# the pointer's net delay.
3137
3138proc logos_crossing {from to} {
3139  if {[get_collection_size $from] == 0 || [get_collection_size $to] == 0} { return }
3140  set_max_skew -from $from -to $to -get_skew_value_from_clock_period src_clock_period -skew_value_multiplier 0.8
3141  if { ![string equal "quartus_syn" $::TimeQuestInfo(nameofexecutable)] } {
3142    set_net_delay -from $from -to $to -max -get_value_from_clock_period dst_clock_period -value_multiplier 0.8
3143  }
3144  set_max_delay -from $from -to $to 100
3145  set_min_delay -from $from -to $to -100
3146}
3147
3148proc logos_synchronizer {from to} {
3149  if {[get_collection_size $from] == 0 || [get_collection_size $to] == 0} { return }
3150  if { ![string equal "quartus_syn" $::TimeQuestInfo(nameofexecutable)] } {
3151    set_net_delay -from $from -to $to -max -get_value_from_clock_period dst_clock_period -value_multiplier 0.8
3152  }
3153}
3154
3155foreach entity {logos_dcfifo_mlab logos_dcfifo_m20k} {
3156  foreach inst [get_entity_instances $entity] {
3157    logos_crossing [get_keepers $inst|wgray[*]] [get_keepers $inst|wgray_s1[*]]
3158    logos_crossing [get_keepers $inst|rgray[*]] [get_keepers $inst|rgray_s1[*]]
3159    logos_synchronizer [get_keepers $inst|wgray_s1[*]] [get_keepers $inst|wgray_s2[*]]
3160    logos_synchronizer [get_keepers $inst|rgray_s1[*]] [get_keepers $inst|rgray_s2[*]]
3161    logos_crossing [get_keepers $inst|mem*] [get_keepers $inst|rdata_q[*]]
3162  }
3163}
3164"##;
3165
3166const HAL_H: &str = r##"/* The device a host program drives the D5005 accelerator through: its MMIO words, buffers the accelerator reads and
3167   writes by I/O address, and its user clock. OPAE implements it on the card (logos_hal_opae.c), the card's PCI function
3168   driven directly implements it on a host whose devices reach memory at physical addresses (logos_hal_pci.c), and a
3169   simulation of the accelerator's RTL implements it through its simulator's bridge: Icarus Verilog's VPI
3170   (logos_hal_vpi.c) or Verilator's DPI-C (logos_hal_dpi.c). */
3171#ifndef LOGOS_HAL_H
3172#define LOGOS_HAL_H
3173
3174#include <stddef.h>
3175#include <stdint.h>
3176
3177typedef struct logos_dev logos_dev;
3178
3179/* Open the accelerator whose AFU ID is `afu_id` (the UUID text); NULL with a message on stderr if there is none. */
3180logos_dev *logos_open(const char *afu_id);
3181void logos_close(logos_dev *dev);
3182
3183uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset);
3184void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value);
3185
3186/* A buffer of `bytes` the accelerator can read and write, 64-byte lines at the I/O address `*io_address`; NULL with a
3187   message on stderr if it cannot be had. */
3188void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address);
3189void logos_buffer_free(logos_dev *dev, void *buffer);
3190
3191/* The clock the accelerator's core runs at, in Hz: the user clock, divided by the build's compute divide. */
3192double logos_user_clock_hz(logos_dev *dev);
3193
3194#endif
3195"##;
3196
3197const HAL_OPAE: &str = r##"/* The D5005 accelerator through OPAE 2.12 (libopae-c): the accelerator found by its AFU ID through whichever plugin
3198   serves it — the PF's port under the DFL driver, or on the OFS FIM PF0's VF1 bound to vfio-pci
3199   (scripts/d5005/afu-vf.sh) — MMIO region 0, buffers prepared and pinned by the driver or the IOMMU, the user clock as
3200   the FIM's user-clock PLL reports it, or for a port with no user-clock interface (a VF under VFIO) the one it was set
3201   to before it was handed over, LOGOS_USER_CLOCK_MHZ. */
3202#include "logos_d5005.h"
3203#include "logos_hal.h"
3204
3205#include <opae/fpga.h>
3206#include <stdio.h>
3207#include <stdlib.h>
3208#include <string.h>
3209
3210#define LOGOS_MAX_BUFFERS 64
3211
3212struct logos_dev {
3213    fpga_token token;
3214    fpga_handle handle;
3215    struct { void *ptr; uint64_t wsid; } buffers[LOGOS_MAX_BUFFERS];
3216};
3217
3218static int parse_guid(const char *text, fpga_guid guid) {
3219    int n = 0;
3220    for (const char *p = text; *p && n < 32; ++p) {
3221        int v;
3222        if (*p == '-') continue;
3223        if (*p >= '0' && *p <= '9') v = *p - '0';
3224        else if (*p >= 'a' && *p <= 'f') v = *p - 'a' + 10;
3225        else if (*p >= 'A' && *p <= 'F') v = *p - 'A' + 10;
3226        else return -1;
3227        if (n % 2 == 0) guid[n / 2] = (uint8_t)(v << 4);
3228        else guid[n / 2] |= (uint8_t)v;
3229        ++n;
3230    }
3231    return n == 32 ? 0 : -1;
3232}
3233
3234logos_dev *logos_open(const char *afu_id) {
3235    fpga_properties filter = NULL;
3236    fpga_guid guid;
3237    uint32_t found = 0;
3238    fpga_result r;
3239    logos_dev *dev = calloc(1, sizeof *dev);
3240    if (!dev) return NULL;
3241    if (parse_guid(afu_id, guid) != 0) {
3242        fprintf(stderr, "logos_open: %s is not a UUID\n", afu_id);
3243        free(dev);
3244        return NULL;
3245    }
3246    if ((r = fpgaGetProperties(NULL, &filter)) != FPGA_OK ||
3247        (r = fpgaPropertiesSetObjectType(filter, FPGA_ACCELERATOR)) != FPGA_OK ||
3248        (r = fpgaPropertiesSetGUID(filter, guid)) != FPGA_OK ||
3249        (r = fpgaEnumerate(&filter, 1, &dev->token, 1, &found)) != FPGA_OK) {
3250        fprintf(stderr, "logos_open: enumerating accelerators: %s\n", fpgaErrStr(r));
3251        fpgaDestroyProperties(&filter);
3252        free(dev);
3253        return NULL;
3254    }
3255    fpgaDestroyProperties(&filter);
3256    if (found == 0) {
3257        fprintf(stderr, "logos_open: no accelerator %s: is its image loaded (fpgaconf, or the FIM over JTAG) and its port on a "
3258                        "function this user opens (the PF's port, or a VF bound to vfio-pci)?\n", afu_id);
3259        free(dev);
3260        return NULL;
3261    }
3262    if ((r = fpgaOpen(dev->token, &dev->handle, 0)) != FPGA_OK || (r = fpgaMapMMIO(dev->handle, 0, NULL)) != FPGA_OK) {
3263        fprintf(stderr, "logos_open: opening %s: %s\n", afu_id, fpgaErrStr(r));
3264        fpgaDestroyToken(&dev->token);
3265        free(dev);
3266        return NULL;
3267    }
3268    return dev;
3269}
3270
3271void logos_close(logos_dev *dev) {
3272    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i)
3273        if (dev->buffers[i].ptr) fpgaReleaseBuffer(dev->handle, dev->buffers[i].wsid);
3274    fpgaUnmapMMIO(dev->handle, 0);
3275    fpgaClose(dev->handle);
3276    fpgaDestroyToken(&dev->token);
3277    free(dev);
3278}
3279
3280uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset) {
3281    uint64_t v = 0;
3282    fpga_result r = fpgaReadMMIO64(dev->handle, 0, byte_offset, &v);
3283    if (r != FPGA_OK) {
3284        fprintf(stderr, "logos_mmio_read 0x%x: %s\n", byte_offset, fpgaErrStr(r));
3285        exit(3);
3286    }
3287    return v;
3288}
3289
3290void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value) {
3291    fpga_result r = fpgaWriteMMIO64(dev->handle, 0, byte_offset, value);
3292    if (r != FPGA_OK) {
3293        fprintf(stderr, "logos_mmio_write 0x%x: %s\n", byte_offset, fpgaErrStr(r));
3294        exit(3);
3295    }
3296}
3297
3298void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address) {
3299    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
3300        if (dev->buffers[i].ptr) continue;
3301        void *ptr = NULL;
3302        uint64_t wsid = 0;
3303        fpga_result r = fpgaPrepareBuffer(dev->handle, bytes, &ptr, &wsid, 0);
3304        if (r == FPGA_OK) r = fpgaGetIOAddress(dev->handle, wsid, io_address);
3305        if (r != FPGA_OK) {
3306            fprintf(stderr, "logos_buffer %zu bytes: %s\n", bytes, fpgaErrStr(r));
3307            if (ptr) fpgaReleaseBuffer(dev->handle, wsid);
3308            return NULL;
3309        }
3310        dev->buffers[i].ptr = ptr;
3311        dev->buffers[i].wsid = wsid;
3312        return ptr;
3313    }
3314    fprintf(stderr, "logos_buffer: more than %d buffers\n", LOGOS_MAX_BUFFERS);
3315    return NULL;
3316}
3317
3318void logos_buffer_free(logos_dev *dev, void *buffer) {
3319    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
3320        if (dev->buffers[i].ptr == buffer) {
3321            fpgaReleaseBuffer(dev->handle, dev->buffers[i].wsid);
3322            dev->buffers[i].ptr = NULL;
3323        }
3324    }
3325}
3326
3327double logos_user_clock_hz(logos_dev *dev) {
3328    uint64_t high = 0, low = 0;
3329    fpga_result r = fpgaGetUserClock(dev->handle, &high, &low, 0);
3330    if (r == FPGA_OK) return (double)high * 1e6 / LOGOS_D5005_COMPUTE_DIVIDE;
3331    const char *mhz = getenv("LOGOS_USER_CLOCK_MHZ");
3332    if (mhz && *mhz) return strtod(mhz, NULL) * 1e6 / LOGOS_D5005_COMPUTE_DIVIDE;
3333    fprintf(stderr, "logos_user_clock_hz: %s, and LOGOS_USER_CLOCK_MHZ is not set\n", fpgaErrStr(r));
3334    exit(3);
3335}
3336"##;
3337
3338const HAL_PCI: &str = r##"/* The D5005 accelerator driven through its PCI function directly, on a host whose devices reach its memory at
3339   physical addresses (no IOMMU, or the function in an identity domain), with no driver and no OPAE. The PR region's
3340   AFU is a VF of the FIM's PF (8086:bccf, PF0's VF1; scripts/d5005/afu-vf.sh attach enables the VFs and leaves them to
3341   no driver), found among the functions of LOGOS_PCI_DEVICES (default /sys/bus/pci/devices), or only LOGOS_D5005_VF
3342   when that is set, by the AFU ID its MMIO words 1 and 2 carry. BAR 0 is mapped from the function's resource0. Its command word decodes memory
3343   and masters the bus only while it is open, and gets back the value it had on close, at exit, and on SIGINT, SIGTERM
3344   and SIGHUP, so the accelerator never writes into memory the program no longer holds. Each buffer is one huge page
3345   (2 MiB, or 1 GiB past that, from the pages reserved in /sys/kernel/mm/hugepages); its I/O address is the page's
3346   physical address, read from /proc/self/pagemap and checked contiguous, and a buffer whose frames cannot be read is
3347   refused. The user clock is LOGOS_USER_CLOCK_MHZ, the one the port ran at before it was released. The BAR, the command
3348   word and the physical frames are root's. */
3349#define _GNU_SOURCE
3350#include "logos_d5005.h"
3351#include "logos_hal.h"
3352
3353#include <dirent.h>
3354#include <errno.h>
3355#include <fcntl.h>
3356#include <signal.h>
3357#include <stdio.h>
3358#include <stdlib.h>
3359#include <string.h>
3360#include <sys/mman.h>
3361#include <sys/stat.h>
3362#include <unistd.h>
3363
3364#ifndef MAP_HUGE_SHIFT
3365#define MAP_HUGE_SHIFT 26
3366#endif
3367
3368#define LOGOS_MAX_BUFFERS 64
3369#define LOGOS_PCI_COMMAND 4
3370#define LOGOS_PCI_MEMORY 0x0002u
3371#define LOGOS_PCI_BUS_MASTER 0x0004u
3372#define LOGOS_FRAME 4096ull
3373#define LOGOS_FRAME_PRESENT (1ull << 63)
3374#define LOGOS_FRAME_NUMBER ((1ull << 55) - 1)
3375
3376struct logos_dev {
3377    int config, resource;
3378    uint16_t command;
3379    volatile uint64_t *bar;
3380    size_t bar_bytes;
3381    struct { void *ptr; size_t bytes; } buffers[LOGOS_MAX_BUFFERS];
3382};
3383
3384/* The open function's configuration space and the command word it had, given back at exit or on a signal. */
3385static volatile sig_atomic_t held_config = -1;
3386static volatile sig_atomic_t held_command;
3387
3388static int command_read(int config, uint16_t *word) {
3389    uint8_t b[2];
3390    if (pread(config, b, 2, LOGOS_PCI_COMMAND) != 2) return -1;
3391    *word = (uint16_t)(b[0] | b[1] << 8);
3392    return 0;
3393}
3394
3395static int command_write(int config, uint16_t word) {
3396    uint8_t b[2] = {(uint8_t)word, (uint8_t)(word >> 8)};
3397    return pwrite(config, b, 2, LOGOS_PCI_COMMAND) == 2 ? 0 : -1;
3398}
3399
3400static void give_back(void) {
3401    int config = held_config;
3402    if (config < 0) return;
3403    held_config = -1;
3404    uint8_t b[2] = {(uint8_t)held_command, (uint8_t)(held_command >> 8)};
3405    ssize_t n = pwrite(config, b, 2, LOGOS_PCI_COMMAND);
3406    (void)n;
3407}
3408
3409static void on_signal(int sig) {
3410    give_back();
3411    raise(sig);
3412}
3413
3414static void hold(int config, uint16_t command) {
3415    static int armed = 0;
3416    held_command = command;
3417    held_config = config;
3418    if (armed) return;
3419    armed = 1;
3420    atexit(give_back);
3421    struct sigaction sa;
3422    memset(&sa, 0, sizeof sa);
3423    sa.sa_handler = on_signal;
3424    sa.sa_flags = SA_RESETHAND;
3425    sigemptyset(&sa.sa_mask);
3426    sigaction(SIGINT, &sa, NULL);
3427    sigaction(SIGTERM, &sa, NULL);
3428    sigaction(SIGHUP, &sa, NULL);
3429}
3430
3431static int parse_id(const char *text, uint64_t *high, uint64_t *low) {
3432    int n = 0;
3433    *high = *low = 0;
3434    for (const char *p = text; *p; ++p) {
3435        uint64_t v;
3436        if (*p == '-') continue;
3437        if (*p >= '0' && *p <= '9') v = (uint64_t)(*p - '0');
3438        else if (*p >= 'a' && *p <= 'f') v = (uint64_t)(*p - 'a' + 10);
3439        else if (*p >= 'A' && *p <= 'F') v = (uint64_t)(*p - 'A' + 10);
3440        else return -1;
3441        if (n == 32) return -1;
3442        if (n < 16) *high = *high << 4 | v;
3443        else *low = *low << 4 | v;
3444        ++n;
3445    }
3446    return n == 32 ? 0 : -1;
3447}
3448
3449/* `<dir>/<name>` into `out`; -1 if it does not fit. */
3450static int join(char *out, size_t size, const char *dir, const char *name) {
3451    int n = snprintf(out, size, "%s/%s", dir, name);
3452    return n >= 0 && (size_t)n < size ? 0 : -1;
3453}
3454
3455/* The text of `<dir>/<name>`, trailing whitespace dropped; -1 if there is no such file. */
3456static int read_text(const char *dir, const char *name, char *out, size_t size) {
3457    char path[4096];
3458    if (join(path, sizeof path, dir, name) != 0) return -1;
3459    FILE *f = fopen(path, "r");
3460    if (!f) return -1;
3461    size_t n = fread(out, 1, size - 1, f);
3462    fclose(f);
3463    while (n > 0 && (out[n - 1] == '\n' || out[n - 1] == ' ')) --n;
3464    out[n] = 0;
3465    return 0;
3466}
3467
3468/* Open `function` under `devices` as the accelerator if it is one this backend may drive and its MMIO words 1 and 2
3469   are the AFU ID: 1 open, 0 not the accelerator (its command word given back), -1 refused with a message. */
3470static int try_function(logos_dev *dev, const char *devices, const char *function, int pinned, uint64_t high, uint64_t low) {
3471    char dir[4096], text[256], path[4096];
3472    if (join(dir, sizeof dir, devices, function) != 0) return 0;
3473    if (!pinned) {
3474        char vendor[32], device[32];
3475        if (read_text(dir, "vendor", vendor, sizeof vendor) || read_text(dir, "device", device, sizeof device)) return 0;
3476        if (strcmp(vendor, "0x8086") != 0 || strcmp(device, "0xbccf") != 0) return 0;
3477    }
3478    char driver[4096];
3479    if (join(path, sizeof path, dir, "driver") != 0) return 0;
3480    ssize_t k = readlink(path, driver, sizeof driver - 1);
3481    if (k >= 0) {
3482        driver[k] = 0;
3483        const char *name = strrchr(driver, '/');
3484        fprintf(stderr, "logos_open: %s has the driver %s bound; this backend drives a function with none\n", function, name ? name + 1 : driver);
3485        return -1;
3486    }
3487    if (join(path, sizeof path, dir, "iommu_group") != 0) return 0;
3488    if (access(path, F_OK) == 0) {
3489        if (read_text(dir, "iommu_group/type", text, sizeof text) != 0) strcpy(text, "unreadable");
3490        if (strcmp(text, "identity") != 0) {
3491            fprintf(stderr, "logos_open: %s is behind an IOMMU that translates its addresses (its group's domain is %s): "
3492                            "open it through VFIO (sw/logos_hal_opae.c)\n", function, text);
3493            return -1;
3494        }
3495    }
3496    if (join(path, sizeof path, dir, "config") != 0) return 0;
3497    int config = open(path, O_RDWR | O_CLOEXEC);
3498    uint16_t command = 0;
3499    if (config < 0 || command_read(config, &command) != 0) {
3500        fprintf(stderr, "logos_open: %s: %s (the command word is root's)\n", path, strerror(errno));
3501        if (config >= 0) close(config);
3502        return -1;
3503    }
3504    if (join(path, sizeof path, dir, "resource0") != 0) {
3505        close(config);
3506        return 0;
3507    }
3508    int resource = open(path, O_RDWR | O_SYNC | O_CLOEXEC);
3509    struct stat st;
3510    void *bar = MAP_FAILED;
3511    if (resource >= 0 && fstat(resource, &st) == 0 && st.st_size >= 24)
3512        bar = mmap(NULL, (size_t)st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, resource, 0);
3513    if (bar == MAP_FAILED) {
3514        fprintf(stderr, "logos_open: %s: %s (BAR 0 is root's)\n", path, strerror(errno));
3515        if (resource >= 0) close(resource);
3516        close(config);
3517        return -1;
3518    }
3519    if (command_write(config, (uint16_t)(command | LOGOS_PCI_MEMORY)) != 0) {
3520        fprintf(stderr, "logos_open: %s: writing the command word: %s\n", function, strerror(errno));
3521        munmap(bar, (size_t)st.st_size);
3522        close(resource);
3523        close(config);
3524        return -1;
3525    }
3526    volatile uint64_t *words = bar;
3527    if (words[2] != high || words[1] != low) {
3528        command_write(config, command);
3529        munmap(bar, (size_t)st.st_size);
3530        close(resource);
3531        close(config);
3532        return 0;
3533    }
3534    hold(config, command);
3535    if (command_write(config, (uint16_t)(command | LOGOS_PCI_MEMORY | LOGOS_PCI_BUS_MASTER)) != 0) {
3536        fprintf(stderr, "logos_open: %s: writing the command word: %s\n", function, strerror(errno));
3537        give_back();
3538        munmap(bar, (size_t)st.st_size);
3539        close(resource);
3540        close(config);
3541        return -1;
3542    }
3543    dev->config = config;
3544    dev->resource = resource;
3545    dev->command = command;
3546    dev->bar = words;
3547    dev->bar_bytes = (size_t)st.st_size;
3548    return 1;
3549}
3550
3551logos_dev *logos_open(const char *afu_id) {
3552    uint64_t high, low;
3553    if (parse_id(afu_id, &high, &low) != 0) {
3554        fprintf(stderr, "logos_open: %s is not a UUID\n", afu_id);
3555        return NULL;
3556    }
3557    logos_dev *dev = calloc(1, sizeof *dev);
3558    if (!dev) return NULL;
3559    const char *devices = getenv("LOGOS_PCI_DEVICES");
3560    if (!devices || !*devices) devices = "/sys/bus/pci/devices";
3561    const char *pinned = getenv("LOGOS_D5005_VF");
3562    if (pinned && *pinned) {
3563        if (try_function(dev, devices, pinned, 1, high, low) == 1) return dev;
3564        fprintf(stderr, "logos_open: no accelerator %s at %s\n", afu_id, pinned);
3565        free(dev);
3566        return NULL;
3567    }
3568    DIR *d = opendir(devices);
3569    if (!d) {
3570        fprintf(stderr, "logos_open: %s: %s\n", devices, strerror(errno));
3571        free(dev);
3572        return NULL;
3573    }
3574    for (struct dirent *e; (e = readdir(d)) != NULL;) {
3575        if (e->d_name[0] == '.') continue;
3576        if (try_function(dev, devices, e->d_name, 0, high, low) == 1) {
3577            closedir(d);
3578            return dev;
3579        }
3580    }
3581    closedir(d);
3582    fprintf(stderr, "logos_open: no accelerator %s on a VF of the FIM's PF (8086:bccf) with no driver bound: is its image "
3583                    "configured, and the PF's VFs enabled (scripts/d5005/afu-vf.sh attach)?\n", afu_id);
3584    free(dev);
3585    return NULL;
3586}
3587
3588void logos_close(logos_dev *dev) {
3589    give_back();
3590    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i)
3591        if (dev->buffers[i].ptr) munmap(dev->buffers[i].ptr, dev->buffers[i].bytes);
3592    munmap((void *)dev->bar, dev->bar_bytes);
3593    close(dev->resource);
3594    close(dev->config);
3595    free(dev);
3596}
3597
3598static size_t word_of(logos_dev *dev, uint32_t byte_offset, const char *what) {
3599    if (byte_offset % 8 != 0 || (size_t)byte_offset + 8 > dev->bar_bytes) {
3600        fprintf(stderr, "%s 0x%x: not a word of the %zu-byte BAR\n", what, byte_offset, dev->bar_bytes);
3601        exit(3);
3602    }
3603    return byte_offset / 8;
3604}
3605
3606uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset) {
3607    return dev->bar[word_of(dev, byte_offset, "logos_mmio_read")];
3608}
3609
3610void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value) {
3611    dev->bar[word_of(dev, byte_offset, "logos_mmio_write")] = value;
3612}
3613
3614/* The physical address of the `bytes` at `ptr`, frame by frame from the page map `pagemap` (an entry of 64 bits a
3615   4 KiB page of the address space: bit 63 present, bits 54..0 the frame): 0 if every frame is present, read and the
3616   one after the frame before it. */
3617static int frames(int pagemap, const void *ptr, size_t bytes, uint64_t *address) {
3618    uint64_t first = 0;
3619    for (size_t at = 0; at < bytes; at += LOGOS_FRAME) {
3620        uint64_t entry = 0;
3621        off_t where = (off_t)(((uintptr_t)ptr + at) / LOGOS_FRAME * 8);
3622        if (pread(pagemap, &entry, 8, where) != 8 || !(entry & LOGOS_FRAME_PRESENT) || (entry & LOGOS_FRAME_NUMBER) == 0) {
3623            fprintf(stderr, "logos_buffer: the page map gives no physical frame for this user (root reads them)\n");
3624            return -1;
3625        }
3626        if (at == 0) {
3627            first = entry & LOGOS_FRAME_NUMBER;
3628        } else if ((entry & LOGOS_FRAME_NUMBER) != first + at / LOGOS_FRAME) {
3629            fprintf(stderr, "logos_buffer: the huge page's frames are not contiguous\n");
3630            return -1;
3631        }
3632    }
3633    *address = first * LOGOS_FRAME;
3634    return 0;
3635}
3636
3637static int physical(const void *ptr, size_t bytes, uint64_t *address) {
3638    int fd = open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC);
3639    if (fd < 0) {
3640        fprintf(stderr, "logos_buffer: /proc/self/pagemap: %s\n", strerror(errno));
3641        return -1;
3642    }
3643    int r = frames(fd, ptr, bytes, address);
3644    close(fd);
3645    return r;
3646}
3647
3648void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address) {
3649    const size_t small = (size_t)2 << 20, large = (size_t)1 << 30;
3650    *io_address = 0;
3651    if (bytes == 0 || bytes > large) {
3652        fprintf(stderr, "logos_buffer %zu bytes: a buffer is one huge page, at most 1 GiB\n", bytes);
3653        return NULL;
3654    }
3655    int slot = -1;
3656    for (int i = 0; i < LOGOS_MAX_BUFFERS && slot < 0; ++i)
3657        if (!dev->buffers[i].ptr) slot = i;
3658    if (slot < 0) {
3659        fprintf(stderr, "logos_buffer: more than %d buffers\n", LOGOS_MAX_BUFFERS);
3660        return NULL;
3661    }
3662    size_t page = bytes <= small ? small : large;
3663    int shift = page == small ? 21 : 30;
3664    void *ptr = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE | shift << MAP_HUGE_SHIFT,
3665                     -1, 0);
3666    if (ptr == MAP_FAILED) {
3667        fprintf(stderr, "logos_buffer %zu bytes: no free %zu kB huge page (%s): reserve them in "
3668                        "/sys/kernel/mm/hugepages/hugepages-%zukB/nr_hugepages\n", bytes, page >> 10, strerror(errno), page >> 10);
3669        return NULL;
3670    }
3671    uint64_t address = 0;
3672    if (physical(ptr, page, &address) != 0) {
3673        fprintf(stderr, "logos_buffer %zu bytes: refused, its I/O address unknown\n", bytes);
3674        munmap(ptr, page);
3675        return NULL;
3676    }
3677    dev->buffers[slot].ptr = ptr;
3678    dev->buffers[slot].bytes = page;
3679    *io_address = address;
3680    return ptr;
3681}
3682
3683void logos_buffer_free(logos_dev *dev, void *buffer) {
3684    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
3685        if (dev->buffers[i].ptr == buffer) {
3686            munmap(buffer, dev->buffers[i].bytes);
3687            dev->buffers[i].ptr = NULL;
3688        }
3689    }
3690}
3691
3692double logos_user_clock_hz(logos_dev *dev) {
3693    (void)dev;
3694    const char *mhz = getenv("LOGOS_USER_CLOCK_MHZ");
3695    if (mhz && *mhz) return strtod(mhz, NULL) * 1e6 / LOGOS_D5005_COMPUTE_DIVIDE;
3696    fprintf(stderr, "logos_user_clock_hz: a VF has no user-clock interface, and LOGOS_USER_CLOCK_MHZ is not set\n");
3697    exit(3);
3698}
3699"##;
3700
3701const CORE_H: &str = r##"/* The bring-up core's commands as a host program rings them: the arguments, the doorbell, and the status record the core
3702   cuts when the command completes. A wait is bounded by the command's progress, not its length: the core reports a
3703   running command's progress while it moves (STATUS {tag, LOGOS_D5005_STATUS_RUNNING}, RESULT0 the count), and once
3704   LOGOS_CORE_MAX_POLLS register reads pass with no newer progress of this command than the wait last saw, the command
3705   is given up, its record {status 0xff, no tag} with the results of the last progress record it cut (none if it cut
3706   none: an engine's carries each context's job word), so a core that stops moving fails the program rather than
3707   hanging it, however long a moving command takes, and says where it stood. */
3708#ifndef LOGOS_CORE_H
3709#define LOGOS_CORE_H
3710
3711#include "logos_d5005.h"
3712#include "logos_hal.h"
3713
3714#ifndef LOGOS_CORE_MAX_POLLS
3715#define LOGOS_CORE_MAX_POLLS (1ull << 26)
3716#endif
3717
3718typedef struct {
3719    uint64_t status, result0, result1;
3720} logos_record;
3721
3722/* Ring `op` tagged `tag` with its three arguments once the command FIFO has room, and wait for its status record. */
3723static inline logos_record logos_core_run(logos_dev *dev, uint8_t op, uint64_t tag, uint64_t a0, uint64_t a1, uint64_t a2) {
3724    logos_record r = {0xff, 0, 0};
3725    uint64_t seq = logos_mmio_read(dev, LOGOS_D5005_REG_STATUS_SEQ), polls = 0, seen = 0;
3726    while (logos_mmio_read(dev, LOGOS_D5005_REG_CMD_SPACE) == 0) {
3727        if (++polls > LOGOS_CORE_MAX_POLLS) return r;
3728    }
3729    logos_mmio_write(dev, LOGOS_D5005_REG_ARG0, a0);
3730    logos_mmio_write(dev, LOGOS_D5005_REG_ARG1, a1);
3731    logos_mmio_write(dev, LOGOS_D5005_REG_ARG2, a2);
3732    logos_mmio_write(dev, LOGOS_D5005_REG_DOORBELL, (tag << 8) | op);
3733    polls = 0;
3734    while (logos_mmio_read(dev, LOGOS_D5005_REG_STATUS_SEQ) == seq) {
3735        if (++polls <= LOGOS_CORE_MAX_POLLS) continue;
3736        uint64_t status = logos_mmio_read(dev, LOGOS_D5005_REG_STATUS), at = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT0);
3737        if (logos_mmio_read(dev, LOGOS_D5005_REG_STATUS_SEQ) != seq) break;
3738        if ((status & 0xff) != LOGOS_D5005_STATUS_RUNNING || status >> 8 != (tag & ((1ull << 56) - 1))) return r;
3739        if (at == seen) {
3740            r.result0 = at;
3741            r.result1 = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT1);
3742            return r;
3743        }
3744        seen = at;
3745        polls = 0;
3746    }
3747    r.status = logos_mmio_read(dev, LOGOS_D5005_REG_STATUS);
3748    r.result0 = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT0);
3749    r.result1 = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT1);
3750    return r;
3751}
3752
3753#endif
3754"##;
3755
3756const LOAD: &str = r##"/* Load a weight image into the D5005's memory channels and prove it is there: the directory the image writer filled
3757   (`channel<k>.bin`, a channel's lines; `tiles.bin`, the tile table of decode_engine::image::Image::tile_table). Each
3758   channel is copied in through a host buffer at the channel's full line width, then read back and compared with its
3759   file line for line, and where the layout carries check words each tile's CRC-32C is checked as its lines come back.
3760   With --self-test one line of a tile is then damaged on the card, the verify must name that tile and no other, the
3761   tile is loaded again from its file, and the verify must pass again. One JSON object on stdout; the exit status is 0
3762   only when every check held, 2 when the image or the card cannot be had.
3763
3764     logos_load <image directory> [--self-test]
3765
3766   Built against the OPAE backend it runs on the card; against the VPI backend, inside a simulation of the RTL. */
3767#include "logos_core.h"
3768
3769#include <inttypes.h>
3770#include <stdio.h>
3771#include <stdlib.h>
3772#include <string.h>
3773
3774#ifndef LOGOS_PROGRAM_MAIN
3775#define LOGOS_PROGRAM_MAIN main
3776#endif
3777
3778/* Channel lines one copy moves. */
3779#define LOGOS_LOAD_CHUNK 4096u
3780#define LOGOS_MAX_BAD 64
3781
3782typedef struct {
3783    uint64_t channel, line, lines;
3784} tile;
3785
3786typedef struct {
3787    uint64_t line_bytes, check, channels, tiles;
3788    uint64_t *channel_lines;
3789    tile *tile;
3790} table;
3791
3792typedef struct {
3793    uint64_t bad[LOGOS_MAX_BAD], crc[LOGOS_MAX_BAD];
3794    int bad_n, crc_n;
3795    uint64_t cycles[LOGOS_D5005_BANKS];
3796} verdict;
3797
3798static int failures;
3799static uint64_t tag = 1;
3800
3801static void expect(int ok, const char *what) {
3802    if (!ok) {
3803        ++failures;
3804        fprintf(stderr, "logos_load: %s\n", what);
3805    }
3806}
3807
3808static logos_record run(logos_dev *dev, uint8_t op, uint64_t a0, uint64_t a1, uint64_t a2) {
3809    uint64_t t = tag++;
3810    logos_record r = logos_core_run(dev, op, t, a0, a1, a2);
3811    expect((r.status >> 8) == t && (r.status & 0xff) == LOGOS_D5005_STATUS_OK, "a copy does not complete");
3812    return r;
3813}
3814
3815/* CRC-32C (Castagnoli), the register carried across calls without its inversions. */
3816static uint32_t crc32c_update(uint32_t c, const uint8_t *p, size_t n) {
3817    for (size_t i = 0; i < n; ++i) {
3818        c ^= p[i];
3819        for (int k = 0; k < 8; ++k) c = (c >> 1) ^ (0x82F63B78u & (0u - (c & 1u)));
3820    }
3821    return c;
3822}
3823
3824static int read_words(FILE *f, uint64_t *w, size_t n) {
3825    uint8_t b[8];
3826    for (size_t i = 0; i < n; ++i) {
3827        if (fread(b, 1, 8, f) != 8) return -1;
3828        w[i] = 0;
3829        for (int k = 7; k >= 0; --k) w[i] = (w[i] << 8) | b[k];
3830    }
3831    return 0;
3832}
3833
3834static int read_table(const char *dir, table *t) {
3835    char path[4096], magic[8];
3836    uint64_t head[4];
3837    snprintf(path, sizeof path, "%s/tiles.bin", dir);
3838    FILE *f = fopen(path, "rb");
3839    if (!f) {
3840        fprintf(stderr, "logos_load: no %s\n", path);
3841        return -1;
3842    }
3843    int ok = fread(magic, 1, 8, f) == 8 && memcmp(magic, "LOGOSIMG", 8) == 0 && read_words(f, head, 4) == 0 && head[0] == 1;
3844    if (ok) {
3845        t->line_bytes = head[1];
3846        t->check = head[2];
3847        t->channels = head[3];
3848        t->channel_lines = calloc(t->channels, sizeof *t->channel_lines);
3849        ok = t->channel_lines && read_words(f, t->channel_lines, t->channels) == 0 && read_words(f, &t->tiles, 1) == 0;
3850    }
3851    if (ok) {
3852        t->tile = calloc(t->tiles ? t->tiles : 1, sizeof *t->tile);
3853        ok = t->tile != NULL;
3854        for (uint64_t i = 0; ok && i < t->tiles; ++i) ok = read_words(f, &t->tile[i].channel, 3) == 0;
3855    }
3856    fclose(f);
3857    if (!ok) fprintf(stderr, "logos_load: %s is not a tile table\n", path);
3858    return ok ? 0 : -1;
3859}
3860
3861static FILE *open_channel(const char *dir, uint64_t k) {
3862    char path[4096];
3863    snprintf(path, sizeof path, "%s/channel%" PRIu64 ".bin", dir, k);
3864    FILE *f = fopen(path, "rb");
3865    if (!f) fprintf(stderr, "logos_load: no %s\n", path);
3866    return f;
3867}
3868
3869/* Host lines `lines` channel lines take packed end to end. */
3870static uint64_t host_lines(uint64_t lines) {
3871    return (lines * LOGOS_D5005_MEM_LINE_BYTES + LOGOS_D5005_HOST_LINE_BYTES - 1) / LOGOS_D5005_HOST_LINE_BYTES;
3872}
3873
3874/* Copy channel `k`'s lines [first, first + lines) from its file onto the card; the cycles the copies took. */
3875static uint64_t load_lines(logos_dev *dev, uint8_t *buf, uint64_t io, FILE *f, uint64_t k, uint64_t first, uint64_t lines) {
3876    uint64_t cycles = 0;
3877    for (uint64_t at = first; at < first + lines; at += LOGOS_LOAD_CHUNK) {
3878        uint64_t n = first + lines - at < LOGOS_LOAD_CHUNK ? first + lines - at : LOGOS_LOAD_CHUNK;
3879        size_t bytes = (size_t)(n * LOGOS_D5005_MEM_LINE_BYTES);
3880        memset(buf, 0, (size_t)host_lines(n) * LOGOS_D5005_HOST_LINE_BYTES);
3881        expect(fseek(f, (long)(at * LOGOS_D5005_MEM_LINE_BYTES), SEEK_SET) == 0 && fread(buf, 1, bytes, f) == bytes,
3882               "a channel's file is short of its lines");
3883        cycles += run(dev, LOGOS_D5005_OP_HOST_TO_MEM_PACKED, io >> 6, k << 56 | at, n).result1;
3884    }
3885    return cycles;
3886}
3887
3888static void add(uint64_t *list, int *n, uint64_t v) {
3889    if (*n > 0 && list[*n - 1] == v) return;
3890    if (*n < LOGOS_MAX_BAD) list[(*n)++] = v;
3891}
3892
3893/* Read every channel back and hold it to its file and its tiles to their check words. */
3894static void verify(logos_dev *dev, const table *t, const char *dir, uint8_t *buf, uint64_t io, uint8_t *file, verdict *v) {
3895    memset(v, 0, sizeof *v);
3896    const uint64_t lb = LOGOS_D5005_MEM_LINE_BYTES;
3897    for (uint64_t k = 0; k < t->channels; ++k) {
3898        FILE *f = open_channel(dir, k);
3899        if (!f) {
3900            ++failures;
3901            continue;
3902        }
3903        uint64_t ti = 0, tile_end = 0;
3904        uint32_t crc = 0;
3905        int tile_bad = 0;
3906        while (ti < t->tiles && t->tile[ti].channel != k) ++ti;
3907        for (uint64_t at = 0; at < t->channel_lines[k]; at += LOGOS_LOAD_CHUNK) {
3908            uint64_t n = t->channel_lines[k] - at < LOGOS_LOAD_CHUNK ? t->channel_lines[k] - at : LOGOS_LOAD_CHUNK;
3909            v->cycles[k] += run(dev, LOGOS_D5005_OP_MEM_TO_HOST_PACKED, io >> 6, k << 56 | at, n).result1;
3910            expect(fseek(f, (long)(at * lb), SEEK_SET) == 0 && fread(file, 1, (size_t)(n * lb), f) == n * lb,
3911                   "a channel's file is short of its lines");
3912            for (uint64_t i = 0; i < n; ++i) {
3913                uint64_t line = at + i;
3914                const uint8_t *got = buf + i * lb;
3915                if (ti < t->tiles && line == t->tile[ti].line) {
3916                    tile_end = line + t->tile[ti].lines;
3917                    crc = 0xFFFFFFFFu;
3918                    tile_bad = 0;
3919                }
3920                if (memcmp(got, file + i * lb, (size_t)lb) != 0 && !tile_bad) {
3921                    tile_bad = 1;
3922                    add(v->bad, &v->bad_n, ti);
3923                }
3924                if (line + 1 < tile_end) {
3925                    crc = crc32c_update(crc, got, (size_t)lb);
3926                } else if (ti < t->tiles && line + 1 == tile_end) {
3927                    if (t->check) {
3928                        crc = ~crc32c_update(crc, got, (size_t)(lb - 4));
3929                        uint32_t word = (uint32_t)got[lb - 4] | (uint32_t)got[lb - 3] << 8 | (uint32_t)got[lb - 2] << 16 |
3930                                        (uint32_t)got[lb - 1] << 24;
3931                        if (word != crc) add(v->crc, &v->crc_n, ti);
3932                    }
3933                    do ++ti;
3934                    while (ti < t->tiles && t->tile[ti].channel != k);
3935                }
3936            }
3937        }
3938        fclose(f);
3939    }
3940}
3941
3942static void print_list(const char *name, const uint64_t *list, int n) {
3943    printf("\"%s\": [", name);
3944    for (int i = 0; i < n; ++i) printf("%s%" PRIu64, i ? ", " : "", list[i]);
3945    printf("]");
3946}
3947
3948int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
3949    if (argc < 2) {
3950        fprintf(stderr, "usage: logos_load <image directory> [--self-test]\n");
3951        return 2;
3952    }
3953    const char *dir = argv[1];
3954    int self_test = argc > 2 && strcmp(argv[2], "--self-test") == 0;
3955    table t;
3956    if (read_table(dir, &t) != 0) return 2;
3957    if (t.line_bytes != LOGOS_D5005_MEM_LINE_BYTES || t.channels != LOGOS_D5005_BANKS) {
3958        fprintf(stderr, "logos_load: the image is %" PRIu64 " channels of %" PRIu64 "-byte lines, the accelerator %d of %d\n",
3959                t.channels, t.line_bytes, LOGOS_D5005_BANKS, LOGOS_D5005_MEM_LINE_BYTES);
3960        return 2;
3961    }
3962    logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
3963    if (!dev) return 2;
3964    uint64_t io = 0;
3965    size_t bytes = (size_t)host_lines(LOGOS_LOAD_CHUNK) * LOGOS_D5005_HOST_LINE_BYTES;
3966    uint8_t *buf = logos_buffer(dev, bytes, &io), *file = malloc(bytes);
3967    if (!buf || !file) return 2;
3968
3969    printf("{\"image\": \"%s\", \"line_bytes\": %" PRIu64 ", \"check\": %s, \"channels\": [", dir, t.line_bytes,
3970           t.check ? "true" : "false");
3971    uint64_t load_cycles[LOGOS_D5005_BANKS] = {0};
3972    for (uint64_t k = 0; k < t.channels; ++k) {
3973        FILE *f = open_channel(dir, k);
3974        if (!f) return 2;
3975        load_cycles[k] = load_lines(dev, buf, io, f, k, 0, t.channel_lines[k]);
3976        fclose(f);
3977    }
3978    verdict v;
3979    verify(dev, &t, dir, buf, io, file, &v);
3980    for (uint64_t k = 0; k < t.channels; ++k)
3981        printf("%s{\"lines\": %" PRIu64 ", \"load_cycles\": %" PRIu64 ", \"verify_cycles\": %" PRIu64 "}", k ? ", " : "",
3982               t.channel_lines[k], load_cycles[k], v.cycles[k]);
3983    printf("], ");
3984    print_list("bad_tiles", v.bad, v.bad_n);
3985    printf(", ");
3986    print_list("crc_errors", v.crc, v.crc_n);
3987    expect(v.bad_n == 0 && v.crc_n == 0, "the image read back is not the image");
3988
3989    if (self_test && t.tiles > 0) {
3990        uint64_t victim = t.tiles / 2;
3991        const tile *x = &t.tile[victim];
3992        uint64_t line = x->line + x->lines / 2;
3993        run(dev, LOGOS_D5005_OP_MEM_TO_HOST_PACKED, io >> 6, x->channel << 56 | line, 1);
3994        buf[(line * 7) % LOGOS_D5005_MEM_LINE_BYTES] ^= 0x10;
3995        run(dev, LOGOS_D5005_OP_HOST_TO_MEM_PACKED, io >> 6, x->channel << 56 | line, 1);
3996        verdict damaged, reloaded;
3997        verify(dev, &t, dir, buf, io, file, &damaged);
3998        FILE *f = open_channel(dir, x->channel);
3999        if (!f) return 2;
4000        load_lines(dev, buf, io, f, x->channel, x->line, x->lines);
4001        fclose(f);
4002        verify(dev, &t, dir, buf, io, file, &reloaded);
4003        printf(", \"self_test\": {\"tile\": %" PRIu64 ", ", victim);
4004        print_list("bad_tiles", damaged.bad, damaged.bad_n);
4005        printf(", ");
4006        print_list("crc_errors", damaged.crc, damaged.crc_n);
4007        printf(", ");
4008        print_list("after_reload", reloaded.bad, reloaded.bad_n);
4009        printf("}");
4010        expect(damaged.bad_n == 1 && damaged.bad[0] == victim, "the damaged tile is not the one the verify names");
4011        expect(!t.check || (damaged.crc_n == 1 && damaged.crc[0] == victim), "the damaged tile's check word does not catch it");
4012        expect(reloaded.bad_n == 0 && reloaded.crc_n == 0, "the tile loaded again does not verify");
4013    }
4014    printf(", \"checks_failed\": %d}\n", failures);
4015    fflush(stdout);
4016    logos_buffer_free(dev, buf);
4017    free(file);
4018    logos_close(dev);
4019    return failures ? 1 : 0;
4020}
4021"##;
4022
4023const STEP: &str = r##"/* Run a model on the decode engine: the program `ofs::write_engine_program` wrote — the memory image, the engine's
4024   configuration and tables, the embedding table — loaded onto the card, then greedy decoding one token a step from a
4025   prompt, for each of up to the program's sequences at once, their prompts apart by `--`: each step every sequence's
4026   row of the row table (its region, its position, its ring row) and its token's embedding written into the engine,
4027   the step run over every row, and each sequence once past its prompt reading its own row's logits back, their
4028   arg-max its next token; a greedy program's engine takes the arg-max itself, and the row's read-back is that one beat.
4029   The sequences step together, so each is at the same position; one whose prompt and tokens are done runs on with the
4030   rest. Each step's position, first sequence's token and cycles on stderr as it completes; one JSON object on stdout
4031   once decoding ends: the first sequence's tokens, every sequence's, each step's cycles and the engine's and card
4032   port's faults. The exit status is 0 only when every step ran without one, 2 when the program or the card cannot be
4033   had, a program for more rows or a larger store than the build's among them.
4034
4035     logos_step [--inject] <program directory> <tokens to generate> <prompt token>... [-- <prompt token>...]...
4036
4037   With --inject, on SECDED lines, every line the image load writes carries one flipped bit, which every read of it
4038   corrects; the record then carries each channel's words corrected and lost. With LOGOS_PROFILE naming a job list
4039   (`d5005_afu --trace-store`'s jobs.txt, a "job <j> word <w> <kind>" line a job), the prompt's last position runs a job
4040   at a time, each job from its word to the next job's alone, its cycles on stdout as "<j> <kind> <cycles>" before the
4041   JSON; the step's record is its last job's. Built against the OPAE backend it runs on the card; against a simulation
4042   backend, inside the RTL. */
4043#include "logos_core.h"
4044
4045#include <inttypes.h>
4046#include <math.h>
4047#include <stdio.h>
4048#include <stdlib.h>
4049#include <string.h>
4050#include <fcntl.h>
4051#include <sys/mman.h>
4052#include <sys/stat.h>
4053#include <unistd.h>
4054
4055#ifndef LOGOS_PROGRAM_MAIN
4056#define LOGOS_PROGRAM_MAIN main
4057#endif
4058
4059/* Host lines one copy moves. */
4060#define LOGOS_STEP_CHUNK 4096u
4061/* The most sequences one run decodes. */
4062#define LOGOS_STEP_SEQUENCES 64
4063/* The sequencer contexts of the build: sequence s runs in context s mod the contexts, as its row s / the contexts. */
4064#ifndef LOGOS_D5005_CONTEXTS
4065#define LOGOS_D5005_CONTEXTS 1
4066#endif
4067/* Whether the build's f32 products are the card's floating-point DSP blocks'. */
4068#ifndef LOGOS_D5005_FP_HARD
4069#define LOGOS_D5005_FP_HARD 0
4070#endif
4071
4072typedef struct {
4073    uint64_t knobs, space, base, granule_log2, job_words, residual_beat, residual_beats, logits_beat, logits_beats;
4074    uint64_t tokens, hidden, vocab, ring_rows, regions, region, sequences, ctx, store_beats, greedy, contexts, fp_hard;
4075    uint64_t channel_lines[LOGOS_D5005_BANKS];
4076    char streaming[256];
4077} program;
4078
4079static int failures;
4080static uint64_t tag = 1;
4081
4082static void expect(int ok, const char *what) {
4083    if (!ok) {
4084        ++failures;
4085        fprintf(stderr, "logos_step: %s\n", what);
4086    }
4087}
4088
4089static logos_record run(logos_dev *dev, uint8_t op, uint64_t a0, uint64_t a1, uint64_t a2) {
4090    uint64_t t = tag++;
4091    logos_record r = logos_core_run(dev, op, t, a0, a1, a2);
4092    expect((r.status >> 8) == t && (r.status & 0xff) == LOGOS_D5005_STATUS_OK, "a command does not complete");
4093    return r;
4094}
4095
4096static int read_program(const char *dir, program *p) {
4097    char path[4096], key[64];
4098    snprintf(path, sizeof path, "%s/engine.txt", dir);
4099    FILE *f = fopen(path, "r");
4100    if (!f) return -1;
4101    memset(p, 0, sizeof *p);
4102    int fields = 0;
4103    while (fscanf(f, "%63s", key) == 1) {
4104        uint64_t *one = NULL, *two = NULL;
4105        if (!strcmp(key, "knobs")) one = &p->knobs;
4106        else if (!strcmp(key, "space")) one = &p->space;
4107        else if (!strcmp(key, "base")) one = &p->base;
4108        else if (!strcmp(key, "granule_log2")) one = &p->granule_log2;
4109        else if (!strcmp(key, "job_words")) one = &p->job_words;
4110        else if (!strcmp(key, "residual")) { one = &p->residual_beat; two = &p->residual_beats; }
4111        else if (!strcmp(key, "logits")) { one = &p->logits_beat; two = &p->logits_beats; }
4112        else if (!strcmp(key, "tokens")) one = &p->tokens;
4113        else if (!strcmp(key, "hidden")) one = &p->hidden;
4114        else if (!strcmp(key, "vocab")) one = &p->vocab;
4115        else if (!strcmp(key, "ring_rows")) one = &p->ring_rows;
4116        else if (!strcmp(key, "regions")) one = &p->regions;
4117        else if (!strcmp(key, "region")) one = &p->region;
4118        else if (!strcmp(key, "sequences")) one = &p->sequences;
4119        else if (!strcmp(key, "ctx")) one = &p->ctx;
4120        else if (!strcmp(key, "store")) one = &p->store_beats;
4121        else if (!strcmp(key, "greedy")) {
4122            if (fscanf(f, "%" SCNu64, &p->greedy) != 1 || p->greedy != 1) { fclose(f); return -1; }
4123            continue;
4124        } else if (!strcmp(key, "fp")) {
4125            char unit[8];
4126            if (fscanf(f, "%7s", unit) != 1 || strcmp(unit, "hard")) { fclose(f); return -1; }
4127            p->fp_hard = 1;
4128            continue;
4129        } else if (!strcmp(key, "contexts")) {
4130            if (fscanf(f, "%" SCNu64, &p->contexts) != 1 || p->contexts < 2) { fclose(f); return -1; }
4131            continue;
4132        } else if (!strcmp(key, "channels")) {
4133            for (int k = 0; k < LOGOS_D5005_BANKS; ++k)
4134                if (fscanf(f, "%" SCNu64, &p->channel_lines[k]) != 1) { fclose(f); return -1; }
4135            ++fields;
4136            continue;
4137        } else if (!strcmp(key, "streaming")) {
4138            if (fscanf(f, "%255s", p->streaming) != 1) { fclose(f); return -1; }
4139            continue;
4140        } else { fclose(f); return -1; }
4141        if (fscanf(f, "%" SCNu64, one) != 1 || (two && fscanf(f, "%" SCNu64, two) != 1)) { fclose(f); return -1; }
4142        ++fields;
4143    }
4144    fclose(f);
4145    if (!p->contexts) p->contexts = 1;
4146    return fields == 17 && p->ring_rows > 0 && p->hidden <= p->residual_beats * 16 && p->logits_beats >= 1 &&
4147                   (p->greedy || p->vocab <= p->logits_beats * 16)
4148               ? 0
4149               : -1;
4150}
4151
4152/* A file's bytes, its length in `n`. */
4153static uint8_t *slurp(const char *dir, const char *name, size_t *n) {
4154    char path[4096];
4155    snprintf(path, sizeof path, "%s/%s", dir, name);
4156    FILE *f = fopen(path, "rb");
4157    if (!f) return NULL;
4158    fseek(f, 0, SEEK_END);
4159    long len = ftell(f);
4160    fseek(f, 0, SEEK_SET);
4161    uint8_t *b = malloc(len > 0 ? (size_t)len : 1);
4162    *n = len > 0 && b && fread(b, 1, (size_t)len, f) == (size_t)len ? (size_t)len : 0;
4163    fclose(f);
4164    if (len > 0 && *n == 0) { free(b); return NULL; }
4165    return b;
4166}
4167
4168/* A file mapped read-only, its length in `n`: the memory image and the embeddings are larger than a host's memory may
4169   be, so they are read as the card takes them. */
4170static const uint8_t *map_file(const char *dir, const char *name, size_t *n) {
4171    char path[4096];
4172    snprintf(path, sizeof path, "%s/%s", dir, name);
4173    *n = 0;
4174    int fd = open(path, O_RDONLY);
4175    if (fd < 0) return NULL;
4176    struct stat st;
4177    if (fstat(fd, &st) || st.st_size <= 0) {
4178        close(fd);
4179        return NULL;
4180    }
4181    void *m = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
4182    close(fd);
4183    if (m == MAP_FAILED) return NULL;
4184    *n = (size_t)st.st_size;
4185    return m;
4186}
4187
4188/* `lines` host lines of `bytes` into the engine's table `table` from entry `first`, through the host buffer. */
4189static void table_write(logos_dev *dev, uint8_t *buf, uint64_t io, int table, uint64_t first, const uint8_t *bytes, uint64_t lines) {
4190    for (uint64_t at = 0; at < lines; at += LOGOS_STEP_CHUNK) {
4191        uint64_t n = lines - at < LOGOS_STEP_CHUNK ? lines - at : LOGOS_STEP_CHUNK;
4192        memcpy(buf, bytes + at * 64, (size_t)n * 64);
4193        run(dev, LOGOS_D5005_OP_ENGINE_TABLE, io >> 6, (uint64_t)table << 32 | (first + at), n);
4194    }
4195}
4196
4197/* The prompt's last position run a job at a time from the job list at `path`: each job from its word to the next's
4198   runs alone and its cycles go to stdout. */
4199static logos_record profiled(logos_dev *dev, const char *path, uint64_t job_words) {
4200    FILE *f = fopen(path, "r");
4201    if (!f) {
4202        fprintf(stderr, "logos_step: no job list %s\n", path);
4203        exit(2);
4204    }
4205    static uint64_t word[4096];
4206    static char kind[4096][24];
4207    uint64_t n = 0, j, w;
4208    char line[65536], k[24];
4209    while (fgets(line, sizeof line, f) && n < 4096)
4210        if (sscanf(line, "job %" SCNu64 " word %" SCNu64 " %23[A-Za-z]", &j, &w, k) == 3) {
4211            word[n] = w;
4212            strcpy(kind[n], k);
4213            ++n;
4214        }
4215    fclose(f);
4216    logos_record r = {0, 0, 0};
4217    for (uint64_t i = 1; i < n; ++i) {
4218        uint64_t to = i + 1 < n ? word[i + 1] : job_words;
4219        if (to <= word[i]) continue;
4220        r = run(dev, LOGOS_D5005_OP_ENGINE_RUN, word[i], to, 0);
4221        if (r.result0 & 3) fprintf(stderr, "logos_step: a fault at job %" PRIu64 "\n", i);
4222        printf("%" PRIu64 " %s %" PRIu64 "\n", i, kind[i], r.result1);
4223    }
4224    fflush(stdout);
4225    return r;
4226}
4227
4228/* The first strictly largest logit that is a number, as numerics::argmax picks it. */
4229static uint64_t argmax(const float *x, uint64_t n) {
4230    uint64_t best = 0;
4231    int found = 0;
4232    for (uint64_t i = 0; i < n; ++i)
4233        if (!isnan(x[i]) && (!found || x[i] > x[best])) { best = i; found = 1; }
4234    return best;
4235}
4236
4237int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
4238    int inject = argc > 1 && !strcmp(argv[1], "--inject");
4239    if (inject) {
4240        --argc;
4241        ++argv;
4242    }
4243    if (argc < 4 || (inject && !LOGOS_D5005_SECDED)) {
4244        fprintf(stderr, "usage: logos_step [--inject] <program directory> <tokens to generate> <prompt token>... [-- <prompt token>...]..., --inject on SECDED lines\n");
4245        return 2;
4246    }
4247    const char *dir = argv[1];
4248    uint64_t generate = strtoull(argv[2], NULL, 0);
4249    /* The sequences' prompts: each from argv[first[s]], prompt_n[s] tokens. */
4250    uint64_t seqs = 0, first[LOGOS_STEP_SEQUENCES], prompt_n[LOGOS_STEP_SEQUENCES];
4251    for (int a = 3; a <= argc; ++a) {
4252        if (a == argc || !strcmp(argv[a], "--")) {
4253            uint64_t start = seqs ? first[seqs - 1] + prompt_n[seqs - 1] + 1 : 3;
4254            if (seqs == LOGOS_STEP_SEQUENCES || a == (int)start) {
4255                fprintf(stderr, "logos_step: every sequence needs a prompt, at most %d sequences\n", LOGOS_STEP_SEQUENCES);
4256                return 2;
4257            }
4258            first[seqs] = start;
4259            prompt_n[seqs] = (uint64_t)a - start;
4260            ++seqs;
4261        }
4262    }
4263    program p;
4264    if (read_program(dir, &p)) {
4265        fprintf(stderr, "logos_step: %s/engine.txt is no engine program\n", dir);
4266        return 2;
4267    }
4268    if (strcmp(p.streaming, LOGOS_D5005_STREAMING)) {
4269        fprintf(stderr, "logos_step: %s is a program for the streaming unit \"%s\", this build's is \"%s\"\n", dir, p.streaming,
4270                LOGOS_D5005_STREAMING);
4271        return 2;
4272    }
4273    uint64_t longest = 0;
4274    for (uint64_t s = 0; s < seqs; ++s) longest = prompt_n[s] > longest ? prompt_n[s] : longest;
4275    if (longest + generate > p.ctx) {
4276        fprintf(stderr, "logos_step: %" PRIu64 " tokens past the %" PRIu64 " positions a sequence reaches\n", longest + generate, p.ctx);
4277        return 2;
4278    }
4279    if (seqs > p.sequences) {
4280        fprintf(stderr, "logos_step: %" PRIu64 " sequences past the program's %" PRIu64 "\n", seqs, p.sequences);
4281        return 2;
4282    }
4283    if (p.contexts != LOGOS_D5005_CONTEXTS) {
4284        fprintf(stderr, "logos_step: %s is a program of %" PRIu64 " contexts, this build runs %u\n", dir, p.contexts,
4285                (unsigned)LOGOS_D5005_CONTEXTS);
4286        return 2;
4287    }
4288    if (p.fp_hard != LOGOS_D5005_FP_HARD) {
4289        fprintf(stderr, "logos_step: %s is a program for %s products, this build's are %s\n", dir,
4290                p.fp_hard ? "the card's floating-point blocks'" : "the engine's own", LOGOS_D5005_FP_HARD ? "the card's floating-point blocks'" : "the engine's own");
4291        return 2;
4292    }
4293    const uint64_t C = LOGOS_D5005_CONTEXTS;
4294    if ((p.sequences + C - 1) / C > LOGOS_D5005_ROWS || p.store_beats > LOGOS_D5005_STORE_BEATS) {
4295        fprintf(stderr, "logos_step: %s steps %" PRIu64 " rows over a store of %" PRIu64 " beats, past this build's %u rows and %u beats\n",
4296                dir, (p.sequences + C - 1) / C, p.store_beats, LOGOS_D5005_ROWS, LOGOS_D5005_STORE_BEATS);
4297        return 2;
4298    }
4299    size_t embed_n = 0;
4300    const uint8_t *embed = map_file(dir, "embed.bin", &embed_n);
4301    if (!embed || embed_n != p.tokens * p.hidden * 4) {
4302        fprintf(stderr, "logos_step: embed.bin is not %" PRIu64 " embeddings of %" PRIu64 " elements\n", p.tokens, p.hidden);
4303        return 2;
4304    }
4305    logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
4306    if (!dev) return 2;
4307    uint64_t io = 0, buf_lines = LOGOS_STEP_CHUNK > p.logits_beats ? LOGOS_STEP_CHUNK : p.logits_beats;
4308    uint8_t *buf = logos_buffer(dev, (size_t)buf_lines * 64, &io);
4309    if (!buf) return 2;
4310
4311    /* Context c's rows, the sequences s with s mod C = c: context 0's at arg0[15:0], context c's at arg0[32 + 16(c - 1) +: 16]. */
4312    uint64_t config = p.knobs << 16;
4313    for (uint64_t c = 0; c < C; ++c) config |= ((seqs + C - 1 - c) / C) << (c ? 32 + 16 * (c - 1) : 0);
4314    run(dev, LOGOS_D5005_OP_ENGINE_CONFIG, config, p.space, p.base | p.granule_log2 << 56);
4315    static const struct { int table; const char *file; } tables[] = {
4316        {0, "table0.bin"}, {2, "table2.bin"}, {3, "table3.bin"}, {4, "table4.bin"}, {5, "table5.bin"}};
4317    for (size_t k = 0; k < sizeof tables / sizeof tables[0]; ++k) {
4318        size_t n = 0;
4319        uint8_t *bytes = slurp(dir, tables[k].file, &n);
4320        if (!bytes || n % 64) {
4321            fprintf(stderr, "logos_step: %s is not whole 64-byte entries\n", tables[k].file);
4322            return 2;
4323        }
4324        table_write(dev, buf, io, tables[k].table, 0, bytes, n / 64);
4325        free(bytes);
4326    }
4327    if (inject)
4328        for (int k = 0; k < LOGOS_D5005_BANKS; ++k)
4329            run(dev, LOGOS_D5005_OP_INJECT, (uint64_t)1 << k, 5u << 16 | 5u, p.channel_lines[k]);
4330    for (int k = 0; k < LOGOS_D5005_BANKS; ++k) {
4331        char name[32];
4332        size_t n = 0;
4333        snprintf(name, sizeof name, "channel%d.bin", k);
4334        const uint8_t *bytes = p.channel_lines[k] ? map_file(dir, name, &n) : NULL;
4335        if (n != p.channel_lines[k] * LOGOS_D5005_MEM_LINE_BYTES || (n && !bytes)) {
4336            fprintf(stderr, "logos_step: %s is not %" PRIu64 " lines\n", name, p.channel_lines[k]);
4337            return 2;
4338        }
4339        for (uint64_t at = 0; at < p.channel_lines[k]; at += LOGOS_STEP_CHUNK) {
4340            uint64_t lines = p.channel_lines[k] - at < LOGOS_STEP_CHUNK ? p.channel_lines[k] - at : LOGOS_STEP_CHUNK;
4341            memcpy(buf, bytes + at * LOGOS_D5005_MEM_LINE_BYTES, (size_t)lines * LOGOS_D5005_MEM_LINE_BYTES);
4342            run(dev, LOGOS_D5005_OP_HOST_TO_MEM, io >> 6, (uint64_t)k << 56 | at, lines);
4343        }
4344        if (bytes) munmap((void *)bytes, n);
4345    }
4346
4347    uint64_t total = longest + generate, ran = 0;
4348    uint64_t *cycles = calloc((size_t)total, sizeof *cycles), *tokens = calloc((size_t)(seqs * total), sizeof *tokens);
4349    uint64_t token[LOGOS_STEP_SEQUENCES];
4350    for (uint64_t s = 0; s < seqs; ++s) token[s] = strtoull(argv[first[s]], NULL, 0);
4351    uint8_t *rows = calloc((size_t)seqs, 64), *e = calloc((size_t)p.residual_beats, 64);
4352    int faults = 0;
4353    for (uint64_t pos = 0; pos < total; ++pos) {
4354        for (uint64_t s = 0; s < seqs; ++s) {
4355            if (token[s] >= p.tokens) {
4356                fprintf(stderr, "logos_step: token %" PRIu64 " past the embedding table\n", token[s]);
4357                return 2;
4358            }
4359            tokens[s * total + pos] = token[s];
4360            /* Each context's rows in its own run of the buffer: sequence s at row s / C of context s mod C. */
4361            uint64_t region = p.regions + s * p.region, ring = pos % p.ring_rows, at = (s % C) * ((seqs + C - 1) / C) + s / C;
4362            memcpy(rows + 64 * at, &region, 4);
4363            memcpy(rows + 64 * at + 4, &pos, 4);
4364            rows[64 * at + 8] = (uint8_t)ring;
4365        }
4366        ran = pos + 1;
4367        for (uint64_t c = 0; c < C; ++c)
4368            if ((seqs + C - 1 - c) / C)
4369                table_write(dev, buf, io, 1, c << 56, rows + 64 * c * ((seqs + C - 1) / C), (seqs + C - 1 - c) / C);
4370        for (uint64_t s = 0; s < seqs; ++s) {
4371            memset(e, 0, (size_t)p.residual_beats * 64);
4372            memcpy(e, embed + token[s] * p.hidden * 4, (size_t)p.hidden * 4);
4373            table_write(dev, buf, io, 6, (s % C) << 56 | (p.residual_beat + s / C * p.residual_beats), e, p.residual_beats);
4374        }
4375        const char *profile = getenv("LOGOS_PROFILE");
4376        logos_record step = profile && pos + 1 == longest ? profiled(dev, profile, p.job_words)
4377                                                          : run(dev, LOGOS_D5005_OP_ENGINE_RUN, 0, p.job_words, 0);
4378        cycles[pos] = step.result1;
4379        fprintf(stderr, "logos_step: position %" PRIu64 " of %" PRIu64 ", token %" PRIu64 ": %" PRIu64 " cycles\n", pos, total,
4380                token[0], step.result1);
4381        if ((step.status & 0xff) == 0xff) {
4382            ++faults;
4383            if (C == 1)
4384                fprintf(stderr, "logos_step: position %" PRIu64 ": the engine stopped moving at word %" PRIu64 "\n", pos, step.result0 >> 32);
4385            else
4386                for (uint64_t c = 0; c < C; ++c)
4387                    fprintf(stderr, "logos_step: position %" PRIu64 ": the engine stopped moving; context %" PRIu64 " at word %" PRIu64 "\n", pos, c,
4388                            step.result0 >> (32 + 16 * c) & 0xffff);
4389            break;
4390        }
4391        if (step.result0 & 3) {
4392            ++faults;
4393            if (C == 1)
4394                fprintf(stderr, "logos_step: position %" PRIu64 ": the %s faulted at word %" PRIu64 "\n", pos,
4395                        step.result0 & 1 ? "engine" : "card port", step.result0 >> 32);
4396            else
4397                for (uint64_t c = 0; c < C; ++c)
4398                    fprintf(stderr, "logos_step: position %" PRIu64 ": the %s faulted; context %" PRIu64 " reached word %" PRIu64 "\n", pos,
4399                            step.result0 & 1 ? "engine" : "card port", c, step.result0 >> (32 + 16 * c) & 0xffff);
4400            break;
4401        }
4402        for (uint64_t s = 0; s < seqs; ++s) {
4403            if (pos + 1 < prompt_n[s]) {
4404                token[s] = strtoull(argv[first[s] + pos + 1], NULL, 0);
4405            } else if (pos + 1 < total) {
4406                run(dev, LOGOS_D5005_OP_STORE_READ, io >> 6, (s % C) << 56 | (p.logits_beat + s / C * p.logits_beats), p.greedy ? 1 : p.logits_beats);
4407                token[s] = p.greedy ? ((const uint32_t *)buf)[0] : argmax((const float *)buf, p.vocab);
4408            }
4409        }
4410    }
4411    printf("{\"tokens\": [");
4412    for (uint64_t pos = 0; pos < ran && pos < prompt_n[0] + generate; ++pos) printf("%s%" PRIu64, pos ? ", " : "", tokens[pos]);
4413    printf("], \"sequences\": [");
4414    for (uint64_t s = 0; s < seqs; ++s) {
4415        printf("%s[", s ? ", " : "");
4416        for (uint64_t pos = 0; pos < ran && pos < prompt_n[s] + generate; ++pos) printf("%s%" PRIu64, pos ? ", " : "", tokens[s * total + pos]);
4417        printf("]");
4418    }
4419    printf("], \"prompt\": %" PRIu64 ", \"cycles\": [", prompt_n[0]);
4420    for (uint64_t pos = 0; pos < total; ++pos) printf("%s%" PRIu64, pos ? ", " : "", cycles[pos]);
4421    printf("], \"faults\": %d", faults);
4422    if (inject) {
4423        printf(", \"secded\": [");
4424        for (int k = 0; k < LOGOS_D5005_BANKS; ++k) {
4425            logos_record c = run(dev, LOGOS_D5005_OP_ECC_COUNTS, (uint64_t)k, 0, 0);
4426            printf("%s{\"corrected\": %" PRIu64 ", \"uncorrectable\": %" PRIu64 "}", k ? ", " : "", c.result0 & 0xffffffffu, c.result0 >> 32);
4427        }
4428        printf("]");
4429    }
4430    printf(", \"checks_failed\": %d}\n", failures);
4431    fflush(stdout);
4432    free(cycles);
4433    free(tokens);
4434    free(rows);
4435    free(e);
4436    munmap((void *)embed, embed_n);
4437    logos_buffer_free(dev, buf);
4438    logos_close(dev);
4439    return failures || faults ? 1 : 0;
4440}
4441"##;
4442
4443const RECEIPT: &str = r##"/* The bring-up receipt of the D5005 accelerator: the accelerator identifies itself, every memory channel is filled and
4444   checked alone and all four at once (each at the core's cycle count, so its bandwidth), a flipped seed is seen, and a
4445   buffer of host memory makes the round trip through a channel unchanged. On SECDED lines every channel then proves its
4446   code: no word corrected or lost before a flip is injected, a flipped data bit and a flipped check bit corrected on
4447   every line, two flips in one word found uncorrectable on every line, a flip in each of two words both corrected, and
4448   a clean fill clean again; without SECDED the core refuses the code's two ops, and without the engine the engine's
4449   four. The receipt is one JSON object on stdout; the exit status is 0 only when every check held.
4450
4451     logos_receipt [lines per channel] [host lines]
4452
4453   Built against the OPAE backend it runs on the card; against the VPI backend, inside a simulation of the RTL. */
4454#include "logos_core.h"
4455
4456#include <inttypes.h>
4457#include <stdio.h>
4458#include <stdlib.h>
4459#include <string.h>
4460
4461#ifndef LOGOS_PROGRAM_MAIN
4462#define LOGOS_PROGRAM_MAIN main
4463#endif
4464
4465typedef logos_record record;
4466
4467static int failures;
4468
4469static void expect(int ok, const char *what) {
4470    if (!ok) {
4471        ++failures;
4472        fprintf(stderr, "logos_receipt: %s\n", what);
4473    }
4474}
4475
4476/* Ring `op` with its arguments and wait for its status record. */
4477static record run(logos_dev *dev, uint8_t op, uint64_t tag, uint64_t a0, uint64_t a1, uint64_t a2) {
4478    record r = logos_core_run(dev, op, tag, a0, a1, a2);
4479    expect((r.status >> 8) == tag, "a status record of another command");
4480    return r;
4481}
4482
4483static double gbps(uint64_t lines, uint64_t cycles, double hz) {
4484    return cycles ? (double)lines * LOGOS_D5005_MEM_LINE_BYTES * hz / (double)cycles / 1e9 : 0.0;
4485}
4486
4487#if LOGOS_D5005_SECDED
4488/* Channel `b`'s words corrected and words found uncorrectable since reset. */
4489static void secded_counts(logos_dev *dev, uint64_t *tag, int b, uint64_t *fixed, uint64_t *lost) {
4490    record r = run(dev, LOGOS_D5005_OP_ECC_COUNTS, (*tag)++, (uint64_t)b, 0, 0);
4491    expect((r.status & 0xff) == LOGOS_D5005_STATUS_OK, "the SECDED counts cannot be read");
4492    *fixed = r.result0 & UINT64_C(0xffffffff);
4493    *lost = r.result0 >> 32;
4494}
4495
4496/* The code on channel `b`: `n` lines filled with bus bits `a` and `c` flipped (none when `a` is negative), checked,
4497   and the lines that differ and the words corrected and lost since the last case held to what the case expects of
4498   every line. */
4499static void secded_case(logos_dev *dev, uint64_t *tag, int b, uint64_t n, const char *name, int a, int c, uint64_t wrong,
4500                        uint64_t fixed, uint64_t lost, uint64_t *fixed_at, uint64_t *lost_at) {
4501    uint64_t size = ((uint64_t)0x5ec << 32) | n, f, l;
4502    if (a >= 0) {
4503        record inject = run(dev, LOGOS_D5005_OP_INJECT, (*tag)++, (uint64_t)1 << b, (uint64_t)c << 16 | (uint64_t)a, n);
4504        expect((inject.status & 0xff) == LOGOS_D5005_STATUS_OK, "a flip cannot be injected");
4505    }
4506    record fill = run(dev, LOGOS_D5005_OP_FILL, (*tag)++, (uint64_t)1 << b, 0, size);
4507    record check = run(dev, LOGOS_D5005_OP_CHECK, (*tag)++, (uint64_t)1 << b, 0, size);
4508    expect((fill.status & 0xff) == LOGOS_D5005_STATUS_OK && (check.status & 0xff) == LOGOS_D5005_STATUS_OK, "a SECDED fill or check fails");
4509    secded_counts(dev, tag, b, &f, &l);
4510    char what[96];
4511    snprintf(what, sizeof what, "channel %d, %s: the lines that differ", b, name);
4512    expect(check.result0 == wrong * n, what);
4513    snprintf(what, sizeof what, "channel %d, %s: the words corrected and lost", b, name);
4514    expect(f - *fixed_at == fixed * n && l - *lost_at == lost * n, what);
4515    printf(", \"%s\": {\"mismatches\": %" PRIu64 ", \"corrected\": %" PRIu64 ", \"uncorrectable\": %" PRIu64 "}", name,
4516           check.result0, f - *fixed_at, l - *lost_at);
4517    *fixed_at = f;
4518    *lost_at = l;
4519}
4520#endif
4521
4522int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
4523    uint64_t lines = argc > 1 ? strtoull(argv[1], NULL, 0) : 1u << 20;
4524    uint64_t host_lines = argc > 2 ? strtoull(argv[2], NULL, 0) : 1u << 12;
4525    uint64_t tag = 1;
4526    logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
4527    if (!dev) return 2;
4528    double hz = logos_user_clock_hz(dev);
4529    uint64_t dfh = logos_mmio_read(dev, LOGOS_D5005_REG_DFH);
4530    uint64_t id_l = logos_mmio_read(dev, LOGOS_D5005_REG_ID_L), id_h = logos_mmio_read(dev, LOGOS_D5005_REG_ID_H);
4531    char id[40];
4532    snprintf(id, sizeof id, "%08" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%012" PRIx64, id_h >> 32,
4533             (id_h >> 16) & 0xffff, id_h & 0xffff, id_l >> 48, id_l & UINT64_C(0xffffffffffff));
4534    expect((dfh >> 60) == 1 && ((dfh >> 40) & 1), "the feature header is no AFU's at the end of the list");
4535    expect(strcmp(id, LOGOS_D5005_AFU_ID) == 0, "the AFU ID is not the one opened");
4536    logos_mmio_write(dev, LOGOS_D5005_REG_SCRATCH, 0x5a5aa5a5f00dcafeull);
4537    expect(logos_mmio_read(dev, LOGOS_D5005_REG_SCRATCH) == 0x5a5aa5a5f00dcafeull, "the scratch register does not hold");
4538    record nop = run(dev, LOGOS_D5005_OP_NOP, tag++, 0, 0, 0);
4539    expect((nop.status & 0xff) == LOGOS_D5005_STATUS_OK, "a NOP does not complete");
4540
4541    printf("{\"afu_id\": \"%s\", \"build\": %" PRIu64 ", \"user_clock_hz\": %.0f, \"line_bytes\": %d, \"channels\": [",
4542           id, logos_mmio_read(dev, LOGOS_D5005_REG_BUILD_ID), hz, LOGOS_D5005_MEM_LINE_BYTES);
4543    for (int b = 0; b <= LOGOS_D5005_BANKS; ++b) {
4544        uint64_t mask = b < LOGOS_D5005_BANKS ? 1u << b : (1u << LOGOS_D5005_BANKS) - 1;
4545        uint64_t seed = 0x1000 + (uint64_t)b, size = (seed << 32) | lines;
4546        record fill = run(dev, LOGOS_D5005_OP_FILL, tag++, mask, 0, size);
4547        record check = run(dev, LOGOS_D5005_OP_CHECK, tag++, mask, 0, size);
4548        record wrong = run(dev, LOGOS_D5005_OP_CHECK, tag++, mask, 0, ((seed + 1) << 32) | lines);
4549        int channels = b < LOGOS_D5005_BANKS ? 1 : LOGOS_D5005_BANKS;
4550        expect((fill.status & 0xff) == LOGOS_D5005_STATUS_OK && (check.status & 0xff) == LOGOS_D5005_STATUS_OK, "a fill or check fails");
4551        expect(check.result0 == 0, "lines read back differ from those written");
4552        expect(wrong.result0 == lines * (uint64_t)channels, "a check under another seed does not differ on every line");
4553        printf("%s{\"mask\": %" PRIu64 ", \"lines\": %" PRIu64 ", \"fill_cycles\": %" PRIu64 ", \"check_cycles\": %" PRIu64
4554               ", \"mismatches\": %" PRIu64 ", \"write_gb_per_s\": %.3f, \"read_gb_per_s\": %.3f}",
4555               b ? ", " : "", mask, lines, fill.result1, check.result1, check.result0,
4556               gbps(lines * (uint64_t)channels, fill.result1, hz), gbps(lines * (uint64_t)channels, check.result1, hz));
4557    }
4558    printf("]");
4559
4560#if LOGOS_D5005_SECDED
4561    printf(", \"secded\": [");
4562    for (int b = 0; b < LOGOS_D5005_BANKS; ++b) {
4563        uint64_t n = lines < 64 ? lines : 64, fixed, lost;
4564        int data = 64 * 3 + 5, other = 64 * 3 + 58, check_bit = (int)LOGOS_D5005_SECDED_CHECK_BIT(3, 3), far = 64 * 6 + 1;
4565        secded_counts(dev, &tag, b, &fixed, &lost);
4566        expect(fixed == 0 && lost == 0, "a channel corrected or lost words before a flip was injected");
4567        printf("%s{\"channel\": %d, \"lines\": %" PRIu64 ", \"before\": {\"corrected\": %" PRIu64 ", \"uncorrectable\": %" PRIu64 "}",
4568               b ? ", " : "", b, n, fixed, lost);
4569        secded_case(dev, &tag, b, n, "data_bit", data, data, 0, 1, 0, &fixed, &lost);
4570        secded_case(dev, &tag, b, n, "check_bit", check_bit, check_bit, 0, 1, 0, &fixed, &lost);
4571        secded_case(dev, &tag, b, n, "two_bits_one_word", data, other, 1, 0, 1, &fixed, &lost);
4572        secded_case(dev, &tag, b, n, "two_words", data, far, 0, 2, 0, &fixed, &lost);
4573        secded_case(dev, &tag, b, n, "clean", -1, -1, 0, 0, 0, &fixed, &lost);
4574        printf("}");
4575    }
4576    printf("]");
4577#else
4578    record inject = run(dev, LOGOS_D5005_OP_INJECT, tag++, 0, 0, 0);
4579    record counts = run(dev, LOGOS_D5005_OP_ECC_COUNTS, tag++, 0, 0, 0);
4580    expect((inject.status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP && (counts.status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP,
4581           "a core without SECDED lines runs the code's ops");
4582#endif
4583#if !LOGOS_D5005_ENGINE
4584    {
4585        static const uint8_t engine_ops[] = {LOGOS_D5005_OP_ENGINE_CONFIG, LOGOS_D5005_OP_ENGINE_TABLE, LOGOS_D5005_OP_ENGINE_RUN,
4586                                             LOGOS_D5005_OP_STORE_READ};
4587        for (size_t k = 0; k < sizeof engine_ops; ++k)
4588            expect((run(dev, engine_ops[k], tag++, 0, 0, 0).status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP,
4589                   "a core without the engine runs the engine's ops");
4590    }
4591#endif
4592
4593    uint64_t io_in = 0, io_out = 0;
4594    size_t bytes = (size_t)host_lines * LOGOS_D5005_HOST_LINE_BYTES;
4595    uint8_t *in = logos_buffer(dev, bytes, &io_in), *out = logos_buffer(dev, bytes, &io_out);
4596    if (!in || !out) return 2;
4597    for (size_t i = 0; i < bytes; ++i) in[i] = (uint8_t)(i * 2654435761u >> 13);
4598    memset(out, 0, bytes);
4599    uint64_t at = (uint64_t)1 << 56 | (lines & ((1u << 27) - 1));
4600    record to_mem = run(dev, LOGOS_D5005_OP_HOST_TO_MEM, tag++, io_in >> 6, at, host_lines);
4601    record to_host = run(dev, LOGOS_D5005_OP_MEM_TO_HOST, tag++, io_out >> 6, at, host_lines);
4602    int same = memcmp(in, out, bytes) == 0;
4603    expect((to_mem.status & 0xff) == LOGOS_D5005_STATUS_OK && (to_host.status & 0xff) == LOGOS_D5005_STATUS_OK, "a host copy fails");
4604    expect(same, "host lines do not come back unchanged through a channel");
4605    printf(", \"host\": {\"lines\": %" PRIu64 ", \"to_mem_cycles\": %" PRIu64 ", \"to_host_cycles\": %" PRIu64
4606           ", \"round_trip\": %s}", host_lines, to_mem.result1, to_host.result1, same ? "true" : "false");
4607    record unknown = run(dev, 0x7f, tag++, 0, 0, 0);
4608    expect((unknown.status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP, "an unknown op is not refused");
4609    expect(logos_mmio_read(dev, LOGOS_D5005_REG_ERRORS) == 0, "the accelerator reports an error");
4610    printf(", \"checks_failed\": %d}\n", failures);
4611    fflush(stdout);
4612    logos_buffer_free(dev, in);
4613    logos_buffer_free(dev, out);
4614    logos_close(dev);
4615    return failures ? 1 : 0;
4616}
4617"##;
4618
4619const HAL_SIM: &str = r##"/* The D5005 accelerator's RTL as a logos_dev, the part every simulator shares: the host program runs in a thread of
4620   its own, each of its MMIO accesses carried out on the shell's MMIO port at the falling edges of pClk (`sim_mmio`),
4621   and the simulation's host memory the program's own buffers at the I/O addresses `logos_buffer` gave them
4622   (`host_bytes`). The simulation advances only while the program waits on an MMIO access, so a run's every cycle
4623   count is the same on every machine. A backend includes this once and calls `sim_start`, `sim_mmio` and
4624   `host_bytes` from its simulator's hooks: logos_hal_vpi.c for Icarus Verilog, logos_hal_dpi.c for Verilator. */
4625#ifndef LOGOS_HAL_SIM_H
4626#define LOGOS_HAL_SIM_H
4627
4628#include "logos_hal.h"
4629
4630#include <pthread.h>
4631#include <stdio.h>
4632#include <stdlib.h>
4633#include <string.h>
4634
4635#define LOGOS_MAX_BUFFERS 64
4636#define LOGOS_IO_BASE (1ull << 32)
4637#define LOGOS_IO_ALIGN (1ull << 21)
4638
4639int logos_program_main(int argc, char **argv);
4640
4641enum { IDLE, WANT_READ, WANT_WRITE, READ_ISSUED, AWAIT_DATA, WRITE_ISSUED, DONE };
4642
4643static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
4644static pthread_cond_t wake = PTHREAD_COND_INITIALIZER;
4645static int state = IDLE, finished, exit_status, started;
4646static uint32_t req_word;
4647static uint64_t req_value;
4648static double user_clock_hz = 450e6;
4649
4650static int program_argc;
4651static char **program_argv;
4652
4653static struct { uint64_t io; size_t bytes; uint8_t *ptr; } buffers[LOGOS_MAX_BUFFERS];
4654static uint64_t next_io = LOGOS_IO_BASE;
4655
4656struct logos_dev { int open; };
4657static struct logos_dev the_device;
4658
4659/* What the MMIO port carries until the next falling edge. */
4660static struct { uint32_t address; int read, write; uint64_t wdata; } bus;
4661
4662logos_dev *logos_open(const char *afu_id) {
4663    (void)afu_id;
4664    the_device.open = 1;
4665    return &the_device;
4666}
4667
4668void logos_close(logos_dev *dev) { dev->open = 0; }
4669
4670static uint64_t bus_access(int want, uint32_t byte_offset, uint64_t value) {
4671    pthread_mutex_lock(&lock);
4672    req_word = byte_offset / 8;
4673    req_value = value;
4674    state = want;
4675    pthread_cond_broadcast(&wake);
4676    while (state != DONE) pthread_cond_wait(&wake, &lock);
4677    value = req_value;
4678    state = IDLE;
4679    pthread_cond_broadcast(&wake);
4680    pthread_mutex_unlock(&lock);
4681    return value;
4682}
4683
4684uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset) {
4685    (void)dev;
4686    return bus_access(WANT_READ, byte_offset, 0);
4687}
4688
4689void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value) {
4690    (void)dev;
4691    bus_access(WANT_WRITE, byte_offset, value);
4692}
4693
4694void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address) {
4695    (void)dev;
4696    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
4697        if (buffers[i].ptr) continue;
4698        size_t rounded = (bytes + 4095) & ~(size_t)4095;
4699        buffers[i].ptr = aligned_alloc(4096, rounded);
4700        if (!buffers[i].ptr) return NULL;
4701        buffers[i].io = next_io;
4702        buffers[i].bytes = bytes;
4703        next_io += (rounded + LOGOS_IO_ALIGN - 1) & ~(LOGOS_IO_ALIGN - 1);
4704        *io_address = buffers[i].io;
4705        return buffers[i].ptr;
4706    }
4707    fprintf(stderr, "logos_buffer: more than %d buffers\n", LOGOS_MAX_BUFFERS);
4708    return NULL;
4709}
4710
4711void logos_buffer_free(logos_dev *dev, void *buffer) {
4712    (void)dev;
4713    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
4714        if (buffers[i].ptr == buffer) {
4715            free(buffers[i].ptr);
4716            buffers[i].ptr = NULL;
4717        }
4718    }
4719}
4720
4721double logos_user_clock_hz(logos_dev *dev) {
4722    (void)dev;
4723    return user_clock_hz;
4724}
4725
4726/* The byte of host memory at I/O address `io`, `len` bytes of it inside one buffer; NULL for an address no buffer
4727   holds, which the simulation stops on, as an IOMMU fault would stop the card. */
4728static uint8_t *host_bytes(uint64_t io, size_t len) {
4729    for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i)
4730        if (buffers[i].ptr && io >= buffers[i].io && io + len <= buffers[i].io + buffers[i].bytes) return buffers[i].ptr + (io - buffers[i].io);
4731    return NULL;
4732}
4733
4734static void *program(void *unused) {
4735    (void)unused;
4736    int status = logos_program_main(program_argc, program_argv);
4737    fflush(stdout);
4738    pthread_mutex_lock(&lock);
4739    finished = 1;
4740    exit_status = status;
4741    pthread_cond_broadcast(&wake);
4742    pthread_mutex_unlock(&lock);
4743    return NULL;
4744}
4745
4746/* Start the host program, the core at `core_period_ps`, its arguments those of `argv` that are +arg=<argument>, every
4747   one of them in order. */
4748static void sim_start(double core_period_ps, int argc, char **argv) {
4749    user_clock_hz = 1e12 / core_period_ps;
4750    int given = 0;
4751    for (int i = 0; i < argc; ++i) given += strncmp(argv[i], "+arg=", 5) == 0;
4752    program_argv = calloc((size_t)given + 2, sizeof *program_argv);
4753    if (!program_argv) {
4754        fprintf(stderr, "logos_hal_sim: no memory for the program's %d arguments\n", given);
4755        exit(2);
4756    }
4757    program_argc = 0;
4758    program_argv[program_argc++] = "logos_program";
4759    for (int i = 0; i < argc; ++i)
4760        if (strncmp(argv[i], "+arg=", 5) == 0) program_argv[program_argc++] = argv[i] + 5;
4761    program_argv[program_argc] = NULL;
4762    pthread_t t;
4763    started = 1;
4764    pthread_create(&t, NULL, program, NULL);
4765    pthread_detach(t);
4766}
4767
4768/* This process's command line, the whole of /proc/self/cmdline however long, split into its arguments; `*argc` their
4769   count. A simulator that keeps no argument list of its own hands the program its arguments from it. */
4770char **sim_cmdline(int *argc) {
4771    FILE *f = fopen("/proc/self/cmdline", "rb");
4772    size_t cap = 1 << 12, n = 0, got;
4773    char *text = malloc(cap + 1);
4774    if (!f || !text) {
4775        perror("logos_hal_sim: /proc/self/cmdline");
4776        exit(2);
4777    }
4778    while ((got = fread(text + n, 1, cap - n, f)) > 0) {
4779        n += got;
4780        if (n == cap && !(text = realloc(text, (cap *= 2) + 1))) {
4781            perror("logos_hal_sim: /proc/self/cmdline");
4782            exit(2);
4783        }
4784    }
4785    fclose(f);
4786    text[n] = 0;
4787    int count = 0;
4788    for (size_t i = 0; i < n; i += strlen(text + i) + 1) ++count;
4789    char **all = calloc((size_t)count + 1, sizeof *all);
4790    if (!all) {
4791        perror("logos_hal_sim: /proc/self/cmdline");
4792        exit(2);
4793    }
4794    count = 0;
4795    for (size_t i = 0; i < n; i += strlen(text + i) + 1) all[count++] = text + i;
4796    *argc = count;
4797    return all;
4798}
4799
4800/* At a falling edge of pClk, the read data the port shows `valid`/`data`: finish the access on the bus, then wait for
4801   the program's next one (or its end) and set `bus` to put it on the bus. 1 once the program has exited with no
4802   access outstanding. */
4803static int sim_mmio(int valid, uint64_t data) {
4804    int over = 0;
4805    pthread_mutex_lock(&lock);
4806    if (state == READ_ISSUED) {
4807        bus.read = 0;
4808        state = AWAIT_DATA;
4809    }
4810    if (state == WRITE_ISSUED) {
4811        bus.write = 0;
4812        state = DONE;
4813        pthread_cond_broadcast(&wake);
4814    }
4815    if (state == AWAIT_DATA && valid) {
4816        req_value = data;
4817        state = DONE;
4818        pthread_cond_broadcast(&wake);
4819    }
4820    while ((state == IDLE || state == DONE) && !finished) pthread_cond_wait(&wake, &lock);
4821    if (state == WANT_READ) {
4822        bus.address = req_word;
4823        bus.read = 1;
4824        state = READ_ISSUED;
4825    } else if (state == WANT_WRITE) {
4826        bus.address = req_word;
4827        bus.wdata = req_value;
4828        bus.write = 1;
4829        state = WRITE_ISSUED;
4830    } else if (finished && state == IDLE) {
4831        over = 1;
4832    }
4833    pthread_mutex_unlock(&lock);
4834    return over;
4835}
4836
4837#endif
4838"##;
4839
4840const HAL_VPI: &str = r##"/* The D5005 accelerator's RTL as a logos_dev, for Icarus Verilog's VPI (logos_hal_sim.h the rest): cosim's
4841   `$logos_program` starts the program, `$logos_mmio` carries its accesses on the MMIO port at every falling edge of
4842   pClk, and `$logos_host_read` and `$logos_host_write` are host memory.
4843
4844   Build: iverilog-vpi --name=logos_cosim -DLOGOS_PROGRAM_MAIN=logos_program_main logos_hal_vpi.c <program>.c */
4845#include "logos_hal_sim.h"
4846
4847#include <vpi_user.h>
4848
4849static vpiHandle arg(vpiHandle *it) { return vpi_scan(*it); }
4850
4851static uint64_t get_u64(vpiHandle h) {
4852    s_vpi_value v = {vpiVectorVal, {0}};
4853    vpi_get_value(h, &v);
4854    int words = (vpi_get(vpiSize, h) + 31) / 32;
4855    uint64_t lo = (uint32_t)v.value.vector[0].aval;
4856    uint64_t hi = words > 1 ? (uint32_t)v.value.vector[1].aval : 0;
4857    return lo | hi << 32;
4858}
4859
4860static void put_u64(vpiHandle h, uint64_t x) {
4861    s_vpi_vecval vec[2] = {{(PLI_INT32)(uint32_t)x, 0}, {(PLI_INT32)(uint32_t)(x >> 32), 0}};
4862    s_vpi_value v;
4863    v.format = vpiVectorVal;
4864    v.value.vector = vec;
4865    vpi_put_value(h, &v, NULL, vpiNoDelay);
4866}
4867
4868/* $logos_program(core_period_ps): start the host program, its arguments the plusargs +arg=<argument> in order. */
4869static PLI_INT32 program_calltf(PLI_BYTE8 *unused) {
4870    (void)unused;
4871    vpiHandle call = vpi_handle(vpiSysTfCall, NULL), it = vpi_iterate(vpiArgument, call);
4872    s_vpi_vlog_info info;
4873    double period = (double)get_u64(arg(&it));
4874    vpi_free_object(it);
4875    vpi_get_vlog_info(&info);
4876    sim_start(period, info.argc, info.argv);
4877    return 0;
4878}
4879
4880/* $logos_mmio(readdatavalid, readdata, address, read, write, writedata), at every falling edge of pClk. */
4881static PLI_INT32 mmio_calltf(PLI_BYTE8 *unused) {
4882    (void)unused;
4883    vpiHandle call = vpi_handle(vpiSysTfCall, NULL), it = vpi_iterate(vpiArgument, call);
4884    vpiHandle valid = arg(&it), data = arg(&it), address = arg(&it), read = arg(&it), write = arg(&it), wdata = arg(&it);
4885    vpi_free_object(it);
4886    int over = sim_mmio((int)get_u64(valid), get_u64(data));
4887    put_u64(address, bus.address);
4888    put_u64(read, (uint64_t)bus.read);
4889    put_u64(write, (uint64_t)bus.write);
4890    put_u64(wdata, bus.wdata);
4891    if (over) {
4892        vpi_printf("logos_cosim: the program exited %d\n", exit_status);
4893        vpi_control(vpiFinish, 1);
4894    }
4895    return 0;
4896}
4897
4898/* $logos_host_read(line, data) and $logos_host_write(line, data): one 64-byte line of host memory. */
4899static PLI_INT32 host_calltf(PLI_BYTE8 *write) {
4900    vpiHandle call = vpi_handle(vpiSysTfCall, NULL), it = vpi_iterate(vpiArgument, call);
4901    vpiHandle line = arg(&it), data = arg(&it);
4902    vpi_free_object(it);
4903    uint64_t io = get_u64(line) << 6;
4904    uint8_t *bytes = host_bytes(io, 64);
4905    if (!bytes) {
4906        vpi_printf("logos_cosim: host %s of I/O address 0x%llx, which no buffer holds\n", write ? "write" : "read", (unsigned long long)io);
4907        vpi_control(vpiFinish, 1);
4908        return 0;
4909    }
4910    s_vpi_vecval vec[16];
4911    s_vpi_value v;
4912    v.format = vpiVectorVal;
4913    if (write) {
4914        v.value.vector = NULL;
4915        vpi_get_value(data, &v);
4916        for (int w = 0; w < 16; ++w) {
4917            uint32_t x = (uint32_t)v.value.vector[w].aval;
4918            memcpy(bytes + 4 * w, &x, 4);
4919        }
4920    } else {
4921        for (int w = 0; w < 16; ++w) {
4922            uint32_t x;
4923            memcpy(&x, bytes + 4 * w, 4);
4924            vec[w].aval = (PLI_INT32)x;
4925            vec[w].bval = 0;
4926        }
4927        v.value.vector = vec;
4928        vpi_put_value(data, &v, NULL, vpiNoDelay);
4929    }
4930    return 0;
4931}
4932
4933static void register_task(const char *name, PLI_INT32 (*calltf)(PLI_BYTE8 *), PLI_BYTE8 *data) {
4934    s_vpi_systf_data tf = {vpiSysTask, 0, (PLI_BYTE8 *)name, calltf, NULL, NULL, data};
4935    vpi_register_systf(&tf);
4936}
4937
4938static void register_all(void) {
4939    register_task("$logos_program", program_calltf, NULL);
4940    register_task("$logos_mmio", mmio_calltf, NULL);
4941    register_task("$logos_host_read", host_calltf, NULL);
4942    register_task("$logos_host_write", host_calltf, (PLI_BYTE8 *)"w");
4943}
4944
4945void (*vlog_startup_routines[])(void) = {register_all, NULL};
4946"##;
4947
4948const HAL_DPI: &str = r##"/* The D5005 accelerator's RTL as a logos_dev, for Verilator's DPI-C (logos_hal_sim.h the rest): cosim built with
4949   LOGOS_DPI calls `logos_dpi_program` to start the program, `logos_dpi_mmio` at every falling edge of pClk to carry
4950   its accesses on the MMIO port, and `logos_dpi_host_read` and `logos_dpi_host_write` for host memory. The program's
4951   arguments are the simulation's own +arg=<argument> plusargs, read from the process's command line.
4952
4953   Build: cc -c -I<verilator root>/include/vltstd -DLOGOS_PROGRAM_MAIN=logos_program_main logos_hal_dpi.c <program>.c,
4954   the objects linked into the Verilated cosim. */
4955#include "logos_hal_sim.h"
4956
4957#include <svdpi.h>
4958
4959void logos_dpi_program(long long core_period_ps) {
4960    int argc;
4961    char **argv = sim_cmdline(&argc);
4962    sim_start((double)core_period_ps, argc, argv);
4963}
4964
4965int logos_dpi_mmio(svBit valid, long long data, int *address, svBit *read, svBit *write, long long *wdata) {
4966    int over = sim_mmio(valid, (uint64_t)data);
4967    *address = (int)bus.address;
4968    *read = (svBit)bus.read;
4969    *write = (svBit)bus.write;
4970    *wdata = (long long)bus.wdata;
4971    return over;
4972}
4973
4974int logos_dpi_exit_status(void) { return exit_status; }
4975
4976int logos_dpi_host_read(long long line, svBitVecVal *data) {
4977    uint8_t *bytes = host_bytes((uint64_t)line << 6, 64);
4978    if (!bytes) return 1;
4979    memcpy(data, bytes, 64);
4980    return 0;
4981}
4982
4983int logos_dpi_host_write(long long line, const svBitVecVal *data) {
4984    uint8_t *bytes = host_bytes((uint64_t)line << 6, 64);
4985    if (!bytes) return 1;
4986    memcpy(bytes, data, 64);
4987    return 0;
4988}
4989"##;
4990
4991/// The co-simulation top, its configured values at the `{…}` markers.
4992const COSIM_TEMPLATE: &str = r##"// The accelerator's shell with a host program attached: the program (through logos_hal_vpi under Icarus Verilog, or
4993// logos_hal_dpi under Verilator with LOGOS_DPI defined) drives the MMIO port and owns host memory, four memory
4994// channels run on four unrelated clocks near 300 MHz, the host channel at 250 MHz, the shell at the user clock, its
4995// compute side stepping at every DIVIDE-th edge and the host program told that step's period; behind
4996// each channel the AFU configuration's channel model (with DDR4_LOG set, a DDR4-timed channel logs what it schedules).
4997// Run with the VPI module, vvp -M <dir> -m logos_cosim cosim, or the Verilated binary, either with
4998// [+arg=<the program's argument>]... (scripts/d5005/run-cosim.sh).
4999`timescale 1ps/1ps
5000module logos_d5005_cosim;
5001  parameter NB = 4, AW = 27, DWM = {mem_bits}, ECC = {ecc}, MBW = 7, HAW = 51, DWH = 512, HBW = 7, MAW = 15;
5002  parameter [63:0] AFU_ID_L = 64'h{id_l}, AFU_ID_H = 64'h{id_h}, BUILD = 64'h{build};
5003  parameter UCLK_PS = {uclk_ps};
5004  parameter DIVIDE = {divide};
5005  parameter DDR4_LOG = 0;
5006  reg pclk = 0, uclk = 0, rst_n = 0;
5007  // A channel clock a register, each its own process: Verilator takes no edge of one bit of a vector.
5008  reg mem_clk0 = 0, mem_clk1 = 0, mem_clk2 = 0, mem_clk3 = 0;
5009  wire [NB-1:0] mem_clk = {mem_clk3, mem_clk2, mem_clk1, mem_clk0};
5010  always #2000 pclk = ~pclk;
5011  always #(UCLK_PS / 2) uclk = ~uclk;
5012{uclk2_clock}  always #1667 mem_clk0 = ~mem_clk0;
5013  initial begin #431 forever #1671 mem_clk1 = ~mem_clk1; end
5014  initial begin #977 forever #1663 mem_clk2 = ~mem_clk2; end
5015  initial begin #1301 forever #1669 mem_clk3 = ~mem_clk3; end
5016
5017  reg  [MAW-1:0] mmio_address = 0;
5018  reg            mmio_read = 0, mmio_write = 0;
5019  reg  [63:0]    mmio_writedata = 0;
5020  wire           mmio_waitrequest, mmio_readdatavalid;
5021  wire [63:0]    mmio_readdata;
5022  wire [HAW-1:0] host_rd_address, host_wr_address;
5023  wire           host_rd_read, host_wr_write, host_rd_waitrequest, host_rd_readdatavalid, host_wr_waitrequest, host_wr_writeresponsevalid;
5024  wire [HBW-1:0] host_rd_burstcount, host_wr_burstcount;
5025  wire [DWH-1:0] host_rd_readdata, host_wr_writedata;
5026  wire [DWH/8-1:0] host_wr_byteenable;
5027  wire [NB*AW-1:0] mem_address;
5028  wire [NB-1:0]    mem_read, mem_write, mem_waitrequest, mem_readdatavalid;
5029  wire [NB*MBW-1:0] mem_burstcount;
5030  wire [NB*DWM-1:0] mem_writedata, mem_readdata;
5031  wire [NB*DWM/8-1:0] mem_byteenable;
5032
5033  logos_d5005_shell #(.NB(NB), .AW(AW), .DWM(DWM), .ECC(ECC), .MBW(MBW), .HAW(HAW), .DWH(DWH), .HBW(HBW), .MAW(MAW),
5034                      .AFU_ID_L(AFU_ID_L), .AFU_ID_H(AFU_ID_H), .BUILD(BUILD)) shell(
5035    .pclk(pclk), .pclk_rst_n(rst_n), .uclk(uclk), .uclk_rst_n(rst_n),{uclk2_cosim}
5036    .mmio_address(mmio_address), .mmio_read(mmio_read), .mmio_write(mmio_write), .mmio_writedata(mmio_writedata),
5037    .mmio_byteenable(8'hff), .mmio_waitrequest(mmio_waitrequest), .mmio_readdata(mmio_readdata),
5038    .mmio_readdatavalid(mmio_readdatavalid),
5039    .host_rd_address(host_rd_address), .host_rd_read(host_rd_read), .host_rd_burstcount(host_rd_burstcount),
5040    .host_rd_waitrequest(host_rd_waitrequest), .host_rd_readdata(host_rd_readdata), .host_rd_readdatavalid(host_rd_readdatavalid),
5041    .host_wr_address(host_wr_address), .host_wr_write(host_wr_write), .host_wr_burstcount(host_wr_burstcount),
5042    .host_wr_writedata(host_wr_writedata), .host_wr_byteenable(host_wr_byteenable), .host_wr_waitrequest(host_wr_waitrequest),
5043    .host_wr_writeresponsevalid(host_wr_writeresponsevalid),
5044    .mem_clk(mem_clk), .mem_rst_n({NB{rst_n}}), .mem_address(mem_address), .mem_read(mem_read), .mem_write(mem_write),
5045    .mem_burstcount(mem_burstcount), .mem_writedata(mem_writedata), .mem_byteenable(mem_byteenable),
5046    .mem_waitrequest(mem_waitrequest), .mem_readdata(mem_readdata), .mem_readdatavalid(mem_readdatavalid));
5047
5048  // Each channel's lines and the host's, a toggle flipped in its own clock on each one that moves: a run whose program
5049  // waits on a command while no line of memory or of the host moves for STALL compute cycles has stalled, far past any
5050  // job's own quiet stretch, and ends saying so rather than waiting out the program's polls.
5051  parameter STALL = 1000000;
5052  wire [NB-1:0] moved;
5053  genvar b;
5054  generate for (b = 0; b < NB; b = b + 1) begin : channel
5055    {channel_model} memory(.clk(mem_clk[b]),
5056      .rst_n(rst_n), .address(mem_address[b*AW +: AW]), .read(mem_read[b]), .write(mem_write[b]),
5057      .burstcount(mem_burstcount[b*MBW +: MBW]), .writedata(mem_writedata[b*DWM +: DWM]),
5058      .byteenable(mem_byteenable[b*DWM/8 +: DWM/8]), .waitrequest(mem_waitrequest[b]), .readdata(mem_readdata[b*DWM +: DWM]),
5059      .readdatavalid(mem_readdatavalid[b]));
5060    reg flip = 1'b0;
5061    always @(posedge mem_clk[b]) if (mem_readdatavalid[b] || ((mem_read[b] || mem_write[b]) && !mem_waitrequest[b])) flip <= ~flip;
5062    assign moved[b] = flip;
5063  end endgenerate
5064  reg host_flip = 1'b0;
5065  always @(posedge pclk)
5066    if (host_rd_readdatavalid || (host_rd_read && !host_rd_waitrequest) || (host_wr_write && !host_wr_waitrequest)) host_flip <= ~host_flip;
5067  reg [NB:0] seen = 0;
5068  longint quiet = 0;
5069  always @(posedge uclk) if (rst_n) begin
5070    if ({host_flip, moved} != seen) begin
5071      seen <= {host_flip, moved};
5072      quiet <= 0;
5073    end else if (quiet == STALL * DIVIDE) begin
5074      $display("logos_cosim: stalled: no line of memory or of the host moved for %0d compute cycles", STALL);
5075      $finish;
5076    end else quiet <= quiet + 1;
5077  end
5078
5079  logos_host_memory_vpi #(.AW(HAW), .DW(DWH), .BW(HBW)) host(.clk(pclk), .rst_n(rst_n),
5080    .rd_address(host_rd_address), .rd_read(host_rd_read), .rd_burstcount(host_rd_burstcount), .rd_waitrequest(host_rd_waitrequest),
5081    .rd_readdata(host_rd_readdata), .rd_readdatavalid(host_rd_readdatavalid), .wr_address(host_wr_address), .wr_write(host_wr_write),
5082    .wr_burstcount(host_wr_burstcount), .wr_writedata(host_wr_writedata), .wr_waitrequest(host_wr_waitrequest),
5083    .wr_writeresponsevalid(host_wr_writeresponsevalid));
5084
5085`ifdef LOGOS_DPI
5086  import "DPI-C" function void logos_dpi_program(input longint core_period_ps);
5087  import "DPI-C" function int logos_dpi_mmio(input bit valid, input longint data, output int address, output bit read,
5088                                             output bit write, output longint wdata);
5089  import "DPI-C" function int logos_dpi_exit_status();
5090  int     dpi_address;
5091  bit     dpi_read, dpi_write;
5092  longint dpi_wdata;
5093  initial begin
5094    #50000 rst_n = 1;
5095    logos_dpi_program(UCLK_PS * DIVIDE);
5096  end
5097  always @(negedge pclk) if (rst_n) begin
5098    if (logos_dpi_mmio(mmio_readdatavalid, mmio_readdata, dpi_address, dpi_read, dpi_write, dpi_wdata) != 0) begin
5099      $display("logos_cosim: the program exited %0d", logos_dpi_exit_status());
5100      $finish;
5101    end
5102    mmio_address = dpi_address[MAW-1:0];
5103    mmio_read = dpi_read;
5104    mmio_write = dpi_write;
5105    mmio_writedata = dpi_wdata;
5106  end
5107`else
5108  initial begin
5109    #50000 rst_n = 1;
5110    $logos_program(UCLK_PS * DIVIDE);
5111  end
5112  always @(negedge pclk) if (rst_n) $logos_mmio(mmio_readdatavalid, mmio_readdata, mmio_address, mmio_read, mmio_write, mmio_writedata);
5113`endif
5114endmodule
5115
5116// Host memory behind the PIM's Avalon-MM read/write port, its lines the host program's buffers: waitrequest at random
5117// on each channel, read data in order after a random latency, one write response a burst.
5118module logos_host_memory_vpi #(parameter AW = 51, parameter DW = 512, parameter BW = 7, parameter SEED = 11,
5119                               parameter WAIT_PCT = 25) (
5120  input               clk,
5121  input               rst_n,
5122  input  [AW-1:0]     rd_address,
5123  input               rd_read,
5124  input  [BW-1:0]     rd_burstcount,
5125  output reg          rd_waitrequest,
5126  output reg [DW-1:0] rd_readdata,
5127  output reg          rd_readdatavalid,
5128  input  [AW-1:0]     wr_address,
5129  input               wr_write,
5130  input  [BW-1:0]     wr_burstcount,
5131  input  [DW-1:0]     wr_writedata,
5132  output reg          wr_waitrequest,
5133  output reg          wr_writeresponsevalid
5134);
5135`ifdef LOGOS_DPI
5136  import "DPI-C" function int logos_dpi_host_read(input longint line, output bit [511:0] data);
5137  import "DPI-C" function int logos_dpi_host_write(input longint line, input bit [511:0] data);
5138`endif
5139  integer seed = SEED, cycle = 0, qh = 0, qt = 0, beat = 0, rh = 0, rt = 0;
5140  reg [AW-1:0] q_addr [0:255];
5141  reg [BW-1:0] q_len [0:255];
5142  integer      q_due [0:255];
5143  integer      r_due [0:255];
5144  reg [AW-1:0] w_addr;
5145  reg [BW-1:0] w_left = 0;
5146  reg [63:0]   line;
5147  reg [DW-1:0] word;
5148  always @(posedge clk) begin
5149    cycle = cycle + 1;
5150    rd_readdatavalid <= 1'b0;
5151    wr_writeresponsevalid <= 1'b0;
5152    if (rst_n) begin
5153      if (rd_read && !rd_waitrequest) begin
5154        q_addr[qt % 256] = rd_address; q_len[qt % 256] = rd_burstcount;
5155        q_due[qt % 256] = cycle + 20 + (($random(seed) % 40 + 40) % 40); qt = qt + 1;
5156      end
5157      if (wr_write && !wr_waitrequest) begin
5158        if (w_left == 0) begin
5159          line = wr_address; w_addr = wr_address + 1; w_left = wr_burstcount - 1;
5160        end else begin
5161          line = w_addr; w_addr = w_addr + 1; w_left = w_left - 1;
5162        end
5163        word = wr_writedata;
5164`ifdef LOGOS_DPI
5165        if (logos_dpi_host_write(line, word) != 0) begin
5166          $display("logos_cosim: host write of I/O address 0x%0h, which no buffer holds", line << 6);
5167          $finish;
5168        end
5169`else
5170        $logos_host_write(line, word);
5171`endif
5172        if (w_left == 0) begin r_due[rt % 256] = cycle + 5 + (($random(seed) % 30 + 30) % 30); rt = rt + 1; end
5173      end
5174      if (qh != qt && q_due[qh % 256] <= cycle) begin
5175        line = q_addr[qh % 256] + beat;
5176`ifdef LOGOS_DPI
5177        if (logos_dpi_host_read(line, word) != 0) begin
5178          $display("logos_cosim: host read of I/O address 0x%0h, which no buffer holds", line << 6);
5179          $finish;
5180        end
5181`else
5182        $logos_host_read(line, word);
5183`endif
5184        rd_readdata <= word;
5185        rd_readdatavalid <= 1'b1;
5186        beat = beat + 1;
5187        if (beat == q_len[qh % 256]) begin beat = 0; qh = qh + 1; end
5188      end
5189      if (rh != rt && r_due[rh % 256] <= cycle) begin wr_writeresponsevalid <= 1'b1; rh = rh + 1; end
5190      rd_waitrequest <= (($random(seed) % 100 + 100) % 100) < WAIT_PCT;
5191      wr_waitrequest <= (($random(seed) % 100 + 100) % 100) < WAIT_PCT;
5192    end else begin
5193      rd_waitrequest <= 1'b1;
5194      wr_waitrequest <= 1'b1;
5195    end
5196  end
5197endmodule
5198"##;
5199
5200const MEMORY_MODEL: &str = r##"// A memory behind an Avalon-MM slave port, for simulation: waitrequest at random, a read burst's lines taken as the
5201// memory holds them when the burst is accepted and returned in order after a random latency with random gaps, so a
5202// write accepted after a read never reaches it, bursts on reads and writes, lines addressed by index.
5203module logos_avmm_memory_model #(parameter AW = 27, parameter DW = 64, parameter BW = 7, parameter LINES_LOG2 = 12,
5204                                 parameter SEED = 7, parameter WAIT_PCT = 30, parameter GAP_PCT = 20) (
5205  input               clk,
5206  input               rst_n,
5207  input  [AW-1:0]     address,
5208  input               read,
5209  input               write,
5210  input  [BW-1:0]     burstcount,
5211  input  [DW-1:0]     writedata,
5212  input  [DW/8-1:0]   byteenable,
5213  output reg          waitrequest,
5214  output reg [DW-1:0] readdata,
5215  output reg          readdatavalid
5216);
5217  reg [DW-1:0] mem [0:(1<<LINES_LOG2)-1];
5218  integer seed = SEED;
5219  // Accepted read bursts, in order: {beats, ready time}; and their lines, in order.
5220  reg [BW-1:0] q_len [0:255];
5221  integer      q_due [0:255];
5222  reg [DW-1:0] q_data [0:4095];
5223  integer qh = 0, qt = 0, lh = 0, lt = 0, cycle = 0, beat = 0;
5224  reg [AW-1:0] w_addr;
5225  reg [BW-1:0] w_left = 0;
5226  integer i;
5227  initial for (i = 0; i < (1 << LINES_LOG2); i = i + 1) mem[i] = {DW{1'bx}};
5228  always @(posedge clk) begin
5229    cycle = cycle + 1;
5230    readdatavalid <= 1'b0;
5231    if (rst_n) begin
5232      if (read && !waitrequest) begin
5233        q_len[qt % 256] = burstcount;
5234        q_due[qt % 256] = cycle + 10 + (($random(seed) % 30 + 30) % 30);
5235        qt = qt + 1;
5236        for (i = 0; i < burstcount; i = i + 1) begin
5237          q_data[lt % 4096] = mem[(address + i) % (1 << LINES_LOG2)];
5238          lt = lt + 1;
5239        end
5240      end
5241      if (write && !waitrequest) begin
5242        if (w_left == 0) begin
5243          mem[address[LINES_LOG2-1:0]] = writedata;
5244          w_addr = address + 1;
5245          w_left = burstcount - 1;
5246        end else begin
5247          mem[w_addr[LINES_LOG2-1:0]] = writedata;
5248          w_addr = w_addr + 1;
5249          w_left = w_left - 1;
5250        end
5251      end
5252      if (qh != qt && q_due[qh % 256] <= cycle && (($random(seed) % 100 + 100) % 100) >= GAP_PCT) begin
5253        readdata <= q_data[lh % 4096];
5254        readdatavalid <= 1'b1;
5255        lh = lh + 1;
5256        beat = beat + 1;
5257        if (beat == q_len[qh % 256]) begin
5258          beat = 0;
5259          qh = qh + 1;
5260        end
5261      end
5262      waitrequest <= (($random(seed) % 100 + 100) % 100) < WAIT_PCT;
5263    end else begin
5264      waitrequest <= 1'b1;
5265    end
5266  end
5267endmodule
5268"##;