Skip to main content

logicaffeine_compile/spatial/decode_engine/
physical.rs

1//! The card's memory as its four channels hold it: where every byte of a machine layout (`super::machine::Layout`)
2//! lives, and the streams a job's reads and writes of one sequence's state are on each channel.
3//!
4//! The tensors are the weight image's ([`super::image::image_of`]): each tensor's tiles dealt to the channels from line
5//! 0, streamed by tensor and row. Everything the layout places after them — the rotary tables, then every sequence's
6//! region — is one logical space of 64-byte lines, cut into granules of [`PhysicalMap::granule`] lines (the engine
7//! plan's state chunk, one value head's memory) dealt round the channels: granule `g` goes to channel `g mod 4`, at
8//! that channel's local line `(g / 4) · G` plus the line's place in the granule, from line `B`, the first line past the
9//! weight image on every channel. At 64 data bytes a beat a local line is a channel line, `B` plus the local line; at
10//! all 72 each channel's local lines are packed end to end, local line `i` at byte `72 · B + 64 · i`, nine logical
11//! lines to eight of the channel's, so a granule's first and last bytes may share a channel line with the channel's
12//! granule before or after it.
13//!
14//! A run of logical bytes is one run of channel bytes on each channel it touches ([`PhysicalMap::runs`]): the granules
15//! of one channel within the run are consecutive local lines. The four patterns a job streams of a sequence — a conv
16//! ring's row, one value head's memory, a lazily flushed memory's log entries, an attention block's cache rows of
17//! positions `0..=p` with their scales — are runs of the layout's own addresses ([`PhysicalMap::ring_row`],
18//! [`PhysicalMap::memory`], [`PhysicalMap::log`], [`PhysicalMap::cache`]), and each run a stream the DDR4 model prices
19//! ([`Run::stream`]).
20//!
21//! The engine reaches the space through two ports that place every logical line as [`PhysicalMap::locate`] does: the
22//! card port ([`card_port_verilog`]), a line a request for the engine's core, and the fast port
23//! ([`fast_port_verilog`]), whole lines in runs a descriptor for a hybrid engine's fast side.
24
25use std::ops::Range;
26
27use crate::codegen_sva::dram::StreamDesc;
28use crate::spatial::d5005::{Beat, EnginePlan};
29
30use super::image::{image_of, streamed_image_of, weight_image, Image, ImageError, ImageLayout, TensorTable};
31use super::machine::{BlockState, Layout as MachineLayout, LINE};
32use super::model::{CacheFormat, StateFormat};
33use super::rtl::streaming::{StreamEntry, STREAM_ENTRY_LINES};
34
35/// Bytes of one cached row's int8 scale.
36const SCALE_BYTES: u64 = 2;
37
38/// Logical bytes `[first, first + bytes)` of the space after the weights as one channel holds them: from channel byte
39/// `at` on.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct Run {
42    pub channel: u32,
43    pub at: u64,
44    pub bytes: u64,
45}
46
47impl Run {
48    /// The channel lines the run touches, `line_bytes` a line.
49    pub fn lines(&self, line_bytes: u64) -> Range<u64> {
50        self.at / line_bytes..(self.at + self.bytes).div_ceil(line_bytes)
51    }
52
53    /// The run as one stream of its channel: a read, or a write when `write`.
54    pub fn stream(&self, write: bool) -> StreamDesc {
55        StreamDesc { base: self.at, elem_bytes: self.bytes, stride_bytes: self.bytes, count: 1, write }
56    }
57}
58
59/// The machine's memory without the matrices its matvec jobs stream: each streamed tensor's bytes, to the next line,
60/// taken out, and every other byte moved down by the bytes taken out below it. The hybrid engine's flat space holds the
61/// memory so, and after it each streamed tensor's stream entry ([`StreamEntry`], [`STREAM_ENTRY_LINES`] lines) in the
62/// runs' order; the tables give the reference engine every address placed so, a streamed tensor's its entry's.
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct Compaction {
65    /// The runs taken out, `[start, end)` in order.
66    pub runs: Vec<(u64, u64)>,
67    /// The compacted memory's bytes to the next line: where the stream entries start.
68    pub entries: u64,
69}
70
71impl Compaction {
72    /// The runs of `machine`'s streamed tensors.
73    pub fn of(machine: &MachineLayout) -> Compaction {
74        let runs: Vec<(u64, u64)> = machine.tensors.iter().filter(|t| t.streamed).map(|t| (t.at, (t.at + t.bytes).div_ceil(LINE) * LINE)).collect();
75        let removed: u64 = runs.iter().map(|(s, e)| e - s).sum();
76        Compaction { entries: (machine.bytes - removed).div_ceil(LINE) * LINE, runs }
77    }
78
79    /// The flat space's bytes: the compacted memory and the stream entries.
80    pub fn space_bytes(&self) -> u64 {
81        self.entries + self.runs.len() as u64 * STREAM_ENTRY_LINES * LINE
82    }
83
84    /// Where the hybrid engine finds what logical byte `at` holds: its compacted place, or for a streamed tensor's
85    /// first byte the tensor's stream entry; none for the rest of a streamed tensor's bytes.
86    pub fn find(&self, at: u64) -> Option<u64> {
87        self.place(at).or_else(|| self.runs.iter().position(|&(s, _)| s == at).map(|k| self.entries + k as u64 * STREAM_ENTRY_LINES * LINE))
88    }
89
90    /// The flat space over `memory`: the memory compacted, zeros to the next line, then the streamed tensors'
91    /// `entries`, one a run in the runs' order.
92    pub fn space(&self, memory: &[u8], entries: &[StreamEntry]) -> Vec<u8> {
93        assert_eq!(entries.len(), self.runs.len(), "an entry a streamed tensor");
94        let mut out = self.bytes(memory);
95        out.resize(self.entries as usize, 0);
96        for e in entries {
97            out.extend(e.lines().concat());
98        }
99        out
100    }
101
102    /// The bytes taken out.
103    pub fn removed(&self) -> u64 {
104        self.runs.iter().map(|(s, e)| e - s).sum()
105    }
106
107    /// Where logical byte `at` lies in the compacted memory: none for a byte of a run taken out.
108    pub fn place(&self, at: u64) -> Option<u64> {
109        if self.runs.iter().any(|&(s, e)| (s..e).contains(&at)) {
110            return None;
111        }
112        Some(at - self.runs.iter().filter(|&&(_, e)| e <= at).map(|(s, e)| e - s).sum::<u64>())
113    }
114
115    /// `memory` with the runs taken out.
116    pub fn bytes(&self, memory: &[u8]) -> Vec<u8> {
117        let mut out = Vec::with_capacity(memory.len() - self.removed() as usize);
118        let mut from = 0usize;
119        for &(s, e) in &self.runs {
120            out.extend_from_slice(&memory[from..s as usize]);
121            from = e as usize;
122        }
123        out.extend_from_slice(&memory[from..]);
124        out
125    }
126}
127
128/// A machine layout on the card's channels.
129#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct PhysicalMap {
131    pub image: Image,
132    /// The layout's first byte past its tensors: where the space after the weights starts.
133    pub tensors_end: u64,
134    /// Logical lines of the space after the weights.
135    pub lines: u64,
136    pub granule: u64,
137    /// The first channel line past the weight image, the same on every channel.
138    pub base: u64,
139    /// Channel lines each channel holds in all.
140    pub channel_lines: Vec<u64>,
141}
142
143impl PhysicalMap {
144    /// `machine` on the D5005's channels at `beat`, its granule the standard engine plan's state chunk.
145    pub fn d5005(machine: &MachineLayout, beat: Beat) -> Result<PhysicalMap, ImageError> {
146        PhysicalMap::of(machine, &ImageLayout::d5005(beat), EnginePlan::standard().state_chunk_lines)
147    }
148
149    /// `machine` on the channels of `layout`, the space after the weights dealt in granules of `granule` lines.
150    pub fn of(machine: &MachineLayout, layout: &ImageLayout, granule: u64) -> Result<PhysicalMap, ImageError> {
151        let image = image_of(machine, layout)?;
152        let tensors_end = machine.rotary.first().map_or(machine.regions, |&(_, at)| at);
153        PhysicalMap::over(image, tensors_end, machine.bytes, layout, granule)
154    }
155
156    /// `machine` on the channels of `layout` with no weight image: every byte, the tensors' with the rest, dealt in
157    /// granules of `granule` lines from the channels' first line — the memory as an engine that reads its weights a
158    /// line at a time by address finds it ([`card_port_verilog`]).
159    pub fn flat(machine: &MachineLayout, layout: &ImageLayout, granule: u64) -> Result<PhysicalMap, ImageError> {
160        let image = weight_image(&TensorTable { blocks: 0, tensors: Vec::new() }, &[], layout)?;
161        PhysicalMap::over(image, 0, machine.bytes, layout, granule)
162    }
163
164    /// The hybrid engine's memory: the matrices `machine`'s matvec jobs stream in their image under `layout` (in stripes
165    /// when it has one), and the rest of the memory compacted ([`Compaction`]) with the streamed tensors' stream entries
166    /// after it, as one logical space from logical byte 0 dealt in granules of `granule` lines past the image, the space
167    /// the reference engine's card port reads; with the image's tensors' indices in the machine's and the compaction.
168    pub fn hybrid(machine: &MachineLayout, layout: &ImageLayout, granule: u64) -> Result<(PhysicalMap, Vec<usize>, Compaction), ImageError> {
169        let (image, streamed) = streamed_image_of(machine, layout)?;
170        let compaction = Compaction::of(machine);
171        let map = PhysicalMap::over(image, 0, compaction.space_bytes(), layout, granule)?;
172        Ok((map, streamed, compaction))
173    }
174
175    /// The space `[tensors_end, bytes)` dealt in granules past `image`.
176    fn over(image: Image, tensors_end: u64, bytes: u64, layout: &ImageLayout, granule: u64) -> Result<PhysicalMap, ImageError> {
177        let lines = (bytes - tensors_end).div_ceil(LINE);
178        let base = image.channel_lines.iter().copied().max().unwrap_or(0);
179        let mut map = PhysicalMap { image, tensors_end, lines, granule: granule.max(1), base, channel_lines: Vec::new() };
180        let channels = layout.channels as u64;
181        let granules = lines.div_ceil(map.granule);
182        map.channel_lines = (0..channels)
183            .map(|c| {
184                let local = (granules + channels - 1 - c) / channels * map.granule;
185                base + (local * LINE).div_ceil(layout.line_bytes)
186            })
187            .collect();
188        for (c, &l) in map.channel_lines.iter().enumerate() {
189            if l > layout.capacity_lines {
190                return Err(ImageError::TooBig { channel: c as u32, lines: l, capacity: layout.capacity_lines });
191            }
192        }
193        Ok(map)
194    }
195
196    fn channels(&self) -> u64 {
197        self.image.layout.channels as u64
198    }
199
200    /// The channel and the channel byte logical byte `at` of the space after the weights lives at.
201    pub fn locate(&self, at: u64) -> (u32, u64) {
202        let rel = at - self.tensors_end;
203        let (x, within) = (rel / LINE, rel % LINE);
204        let g = x / self.granule;
205        let local = g / self.channels() * self.granule + x % self.granule;
206        let lb = self.image.layout.line_bytes;
207        ((g % self.channels()) as u32, self.base * lb + local * LINE + within)
208    }
209
210    /// The channel runs that hold logical bytes `[at, at + bytes)` of the space after the weights, one on each channel
211    /// the bytes touch, in channel order.
212    pub fn runs(&self, at: u64, bytes: u64) -> Vec<Run> {
213        let mut runs: Vec<Run> = Vec::new();
214        let (end, granule_bytes) = (at + bytes, self.granule * LINE);
215        let mut from = at;
216        while from < end {
217            let to = end.min(self.tensors_end + ((from - self.tensors_end) / granule_bytes + 1) * granule_bytes);
218            let (channel, byte) = self.locate(from);
219            match runs.iter_mut().find(|r| r.channel == channel) {
220                Some(r) if r.at + r.bytes == byte => r.bytes += to - from,
221                Some(_) => unreachable!("a channel's granules within one run are consecutive local lines"),
222                None => runs.push(Run { channel, at: byte, bytes: to - from }),
223            }
224            from = to;
225        }
226        runs.sort_by_key(|r| r.channel);
227        runs
228    }
229
230    fn block(machine: &MachineLayout, layer: usize) -> &BlockState {
231        &machine.blocks[layer]
232    }
233
234    /// Sequence `s`'s conv-ring row for position `p` in Gated-DeltaNet layer `layer`.
235    pub fn ring_row(&self, machine: &MachineLayout, s: usize, layer: usize, p: usize) -> Vec<Run> {
236        let BlockState::Delta { ring, channels, taps, .. } = *Self::block(machine, layer) else {
237            panic!("layer {layer} is not a Gated-DeltaNet block")
238        };
239        let row = channels as u64 * state_element(machine);
240        let rows = taps.saturating_sub(1).max(1) as u64;
241        self.runs(machine.region_of(s) + ring + (p as u64 % rows) * row, row)
242    }
243
244    /// Value head `head`'s memory of sequence `s` in Gated-DeltaNet layer `layer`.
245    pub fn memory(&self, machine: &MachineLayout, s: usize, layer: usize, head: usize) -> Vec<Run> {
246        let BlockState::Delta { memory, width, .. } = *Self::block(machine, layer) else {
247            panic!("layer {layer} is not a Gated-DeltaNet block")
248        };
249        let bytes = (width * width) as u64 * state_element(machine);
250        self.runs(machine.region_of(s) + memory + head as u64 * bytes, bytes)
251    }
252
253    /// Sequence `s`'s log in Gated-DeltaNet layer `layer`: its count line and entries `entries`.
254    pub fn log(&self, machine: &MachineLayout, s: usize, layer: usize, entries: Range<usize>) -> Vec<Run> {
255        let BlockState::Delta { log: Some(log), entry, .. } = *Self::block(machine, layer) else {
256            panic!("layer {layer} keeps no log")
257        };
258        let at = machine.region_of(s) + log;
259        let mut runs = self.runs(at, LINE);
260        if !entries.is_empty() {
261            let more = self.runs(at + LINE + entries.start as u64 * entry, entries.len() as u64 * entry);
262            runs.extend(more);
263        }
264        runs
265    }
266
267    /// Sequence `s`'s cache rows of positions `0..=p` in attention layer `layer`: every cached head's key rows, value
268    /// rows and, for an int8 cache, their scales.
269    pub fn cache(&self, machine: &MachineLayout, s: usize, layer: usize, p: usize) -> Vec<Run> {
270        let BlockState::Attention { keys, values, key_scales, value_scales, kv_heads, width, value_width } = *Self::block(machine, layer) else {
271            panic!("layer {layer} is not an attention block")
272        };
273        let element = match machine.numerics.cache {
274            CacheFormat::F16 => 2,
275            CacheFormat::Int8 | CacheFormat::Int8Hadamard => 1,
276        };
277        let (key_row, value_row) = (width as u64 * element, value_width as u64 * element);
278        let region = machine.region_of(s);
279        let positions = (p + 1) as u64 * kv_heads as u64;
280        let mut runs = self.runs(region + keys, positions * key_row);
281        runs.extend(self.runs(region + values, positions * value_row));
282        for scales in [key_scales, value_scales].into_iter().flatten() {
283            runs.extend(self.runs(region + scales, positions * SCALE_BYTES));
284        }
285        runs
286    }
287
288    /// The channels' bytes of `memory`, the machine's whole memory: the weight image's from `weights` (its channels'
289    /// bytes, [`Image::fill`]) and every byte after the weights where [`PhysicalMap::locate`] puts it.
290    pub fn channel_bytes(&self, memory: &[u8], weights: &[Vec<u8>]) -> Vec<Vec<u8>> {
291        let lb = self.image.layout.line_bytes;
292        let mut out: Vec<Vec<u8>> = self.channel_lines.iter().map(|&l| vec![0u8; (l * lb) as usize]).collect();
293        for (c, w) in weights.iter().enumerate() {
294            out[c][..w.len()].copy_from_slice(w);
295        }
296        let mut from = self.tensors_end;
297        let granule_bytes = self.granule * LINE;
298        while from < memory.len() as u64 {
299            let to = (memory.len() as u64).min(self.tensors_end + ((from - self.tensors_end) / granule_bytes + 1) * granule_bytes);
300            let (c, byte) = self.locate(from);
301            out[c as usize][byte as usize..(byte + to - from) as usize].copy_from_slice(&memory[from as usize..to as usize]);
302            from = to;
303        }
304        out
305    }
306}
307
308/// Bytes of one element of the layout's recurrent state.
309fn state_element(machine: &MachineLayout) -> u64 {
310    match machine.numerics.state {
311        StateFormat::F32 => 4,
312        StateFormat::F16 => 2,
313    }
314}
315
316/// `logos_card_port.v`: the engine's memory port on the card's channels, every logical line where the map puts it.
317pub fn card_port_verilog() -> String {
318    CARD_PORT.to_string()
319}
320
321/// `logos_fast_port`, a hybrid engine's fast side's memory port on the channels' fast ports: whole logical lines read
322/// and written where the map puts them, consecutive lines of a channel one descriptor, the reads returned in order.
323pub fn fast_port_verilog() -> String {
324    FAST_PORT.to_string()
325}
326
327/// `logos_channel_mux`, several fast ports on the channels' fast ports at once: each port's descriptors and write lines
328/// through registers of its own in the order it gives them, each channel's descriptors taken from its ports in turn, each
329/// read line and write's done token given back to the port it is of, one port's write lines on a channel at a time.
330pub fn channel_mux_verilog() -> String {
331    format!("{}{CHANNEL_MUX}", super::rtl::ram::RAMSTYLE)
332}
333
334/// `logos_read_credit`, a reader's lines of one channel through the channel multiplexer asked only as a buffer of its own
335/// can hold them: its descriptors asked in runs of at most 32 lines, every line back taken at once, the reader's next line
336/// in a register. A reader whose channels step together — the streaming unit — reaches the multiplexer through one a
337/// channel, so the multiplexer never waits on it for a line.
338pub fn read_credit_verilog() -> String {
339    READ_CREDIT.to_string()
340}
341
342/// `logos_pipe`, a stream through one register for a path a floorplan stretches across the device: each word taken leaves
343/// a clock or more later, in order, a word a clock while the far side takes them, the near side's ready a register.
344pub fn pipe_verilog() -> &'static str {
345    PIPE
346}
347
348const PIPE: &str = r##"// A stream through one register, for a path a floorplan stretches across the device: each word taken from `in` leaves
349// `out` a clock or more later, in order, a word a clock while `out` takes them. `in_ready` is a register: a second
350// place holds the word taken the clock `out` stops taking, and `in` waits until it has left. The second place takes
351// `in`'s word every clock it is empty, so its load waits on no valid from either side.
352module logos_pipe #(parameter W = 32) (input clk, input rst_n, input in_valid, output reg in_ready, input [W-1:0] in_data,
353  output reg out_valid, input out_ready, output reg [W-1:0] out_data);
354  reg skid_v;
355  reg [W-1:0] skid;
356  wire take = in_valid && in_ready;
357  wire moves = !out_valid || out_ready;
358  always @(posedge clk)
359    if (!rst_n) begin
360      out_valid <= 1'b0;
361      skid_v <= 1'b0;
362      in_ready <= 1'b0;
363    end else begin
364      if (moves) out_valid <= skid_v || take;
365      skid_v <= !moves && (skid_v || take);
366      in_ready <= moves || !(skid_v || take);
367    end
368  always @(posedge clk) begin
369    if (moves) out_data <= skid_v ? skid : in_data;
370    if (!skid_v) skid <= in_data;
371  end
372endmodule
373
374// K pipes in a row, for a path across the die a pipe each part of the way: each word leaves K clocks or more after it is
375// taken, in order, a word a clock while `out` takes them; every ready a register.
376module logos_pipe_chain #(parameter W = 32, parameter K = 1) (input clk, input rst_n, input in_valid, output in_ready,
377  input [W-1:0] in_data, output out_valid, input out_ready, output [W-1:0] out_data);
378  wire [K:0] v, r;
379  wire [(K+1)*W-1:0] d;
380  assign v[0] = in_valid;
381  assign in_ready = r[0];
382  assign d[W-1:0] = in_data;
383  assign out_valid = v[K];
384  assign r[K] = out_ready;
385  assign out_data = d[K*W +: W];
386  genvar k;
387  generate for (k = 0; k < K; k = k + 1) begin : stage
388    logos_pipe #(.W(W)) p(.clk(clk), .rst_n(rst_n), .in_valid(v[k]), .in_ready(r[k]), .in_data(d[k*W +: W]), .out_valid(v[k+1]),
389      .out_ready(r[k+1]), .out_data(d[(k+1)*W +: W]));
390  end endgenerate
391endmodule
392"##;
393
394const CARD_PORT: &str = r##"// The engine's memory port on the card's channels: each request one logical line of the machine's memory, its place
395// the physical map's — logical line x of the granule space, which starts at logical line `space`, lies in granule
396// g = (x - space) / 2^granule_log2, on channel g mod NB, at channel line base + (g / NB) * 2^granule_log2 + (x - space)
397// mod 2^granule_log2. Requests are served one at a time, in the order they come: a read sends its line's descriptor and
398// returns the line; a write under a mask of every byte writes its line, under any other mask reads the line first and
399// writes it merged, and is ready to the engine only once its channel has written it, so a read after a write reads
400// what it wrote. A read pulse taken while a write is served waits its turn. A line below the space, or one past a
401// channel's 2^AW lines, faults: its read returns zeros, its write is dropped. A line read with a word its channel's
402// SECDED code finds uncorrectable faults too, its read returning the line as read and a merged write dropped, so a bad
403// word is never coded afresh. `fault` holds until `clear`. Every register steps at `ce`, the shell's compute step.
404module logos_card_port #(
405  parameter NB = 4,            // channels, a power of two
406  parameter AW = 27,           // channel line-address bits
407  parameter LW = 32,           // a descriptor's line-count bits
408  parameter DL = 512           // a line's bits
409) (
410  input                     clk,
411  input                     ce,
412  input                     rst_n,
413  input  [31:0]             space,
414  input  [AW-1:0]           base,
415  input  [4:0]              granule_log2,
416  input                     clear,
417  output reg                fault,
418  input                     mem_rd,
419  input  [31:0]             mem_rd_line,
420  output reg                mem_rd_valid,
421  output reg [DL-1:0]       mem_rd_data,
422  input                     mem_wr,
423  input  [31:0]             mem_wr_line,
424  input  [DL-1:0]           mem_wr_data,
425  input  [DL/8-1:0]         mem_wr_mask,
426  output reg                mem_wr_ready,
427  output [NB-1:0]           md_valid,
428  input  [NB-1:0]           md_ready,
429  output [NB*(AW+LW+1)-1:0] md_data,
430  input  [NB-1:0]           mr_valid,
431  output [NB-1:0]           mr_ready,
432  input  [NB*DL-1:0]        mr_data,
433  input  [NB*16-1:0]        mr_ecc,      // each line read's words' verdicts {uncorrectable, corrected}
434  output [NB-1:0]           mw_valid,
435  input  [NB-1:0]           mw_ready,
436  output [NB*DL-1:0]        mw_data,
437  input  [NB-1:0]           mdone_valid,
438  output [NB-1:0]           mdone_ready
439);
440  localparam CB = NB > 1 ? $clog2(NB) : 1;
441  localparam [2:0] IDLE = 3'd0, RD_DESC = 3'd1, RD_WAIT = 3'd2, WR_SEND = 3'd3, WR_DONE = 3'd4, ACK = 3'd5;
442  reg  [2:0]    state;
443  reg           rd_pending, merge, desc_sent, data_sent;
444  reg  [31:0]   rd_line;
445  reg  [CB-1:0] ch;
446  reg  [AW-1:0] at;
447  reg  [DL-1:0] line;
448  reg  [DL/8-1:0] mask;
449
450  // Where logical line `l` lives: its channel, its channel line, and whether it lies outside the space.
451  reg  [31:0]   x, g, in_granule, local_line;
452  reg  [32:0]   to;
453  reg  [CB-1:0] l_ch;
454  reg  [AW-1:0] l_at;
455  reg           l_bad;
456  wire [31:0]   l = mem_wr ? mem_wr_line : rd_line;
457  always @(*) begin
458    x = l - space;
459    g = x >> granule_log2;
460    in_granule = x & ((32'd1 << granule_log2) - 32'd1);
461    local_line = ((g >> CB) << granule_log2) | in_granule;
462    to = {1'b0, {(32-AW){1'b0}}, base} + {1'b0, local_line};
463    l_ch = g[CB-1:0];
464    l_at = to[AW-1:0];
465    l_bad = l < space || to[32:AW] != 0;
466  end
467
468  function [DL-1:0] merged(input [DL-1:0] old, input [DL-1:0] new_, input [DL/8-1:0] m);
469    integer i;
470    for (i = 0; i < DL/8; i = i + 1) merged[8*i +: 8] = m[i] ? new_[8*i +: 8] : old[8*i +: 8];
471  endfunction
472
473  wire writing = state == WR_SEND;
474  genvar b;
475  generate for (b = 0; b < NB; b = b + 1) begin : channel
476    wire mine = ch == b;
477    assign md_valid[b] = mine && (state == RD_DESC || (writing && !desc_sent));
478    assign md_data[b*(AW+LW+1) +: AW+LW+1] = {writing, at, {{(LW-1){1'b0}}, 1'b1}};
479    assign mr_ready[b] = mine && state == RD_WAIT;
480    assign mw_valid[b] = mine && writing && !data_sent;
481    assign mw_data[b*DL +: DL] = line;
482    assign mdone_ready[b] = mine && state == WR_DONE;
483  end endgenerate
484
485  always @(posedge clk or negedge rst_n) begin
486    if (!rst_n) begin
487      state <= IDLE; rd_pending <= 1'b0; merge <= 1'b0; desc_sent <= 1'b0; data_sent <= 1'b0; rd_line <= 0; ch <= 0;
488      at <= 0; line <= 0; mask <= 0; fault <= 1'b0; mem_rd_valid <= 1'b0; mem_rd_data <= 0; mem_wr_ready <= 1'b0;
489    end else if (ce) begin
490      mem_rd_valid <= 1'b0;
491      mem_wr_ready <= 1'b0;
492      if (clear) fault <= 1'b0;
493      if (mem_rd) begin
494        rd_pending <= 1'b1;
495        rd_line <= mem_rd_line;
496      end
497      case (state)
498        IDLE: if (mem_wr) begin
499          ch <= l_ch; at <= l_at; line <= mem_wr_data; mask <= mem_wr_mask; desc_sent <= 1'b0; data_sent <= 1'b0;
500          if (l_bad) begin
501            fault <= 1'b1; mem_wr_ready <= 1'b1; state <= ACK;
502          end else if (&mem_wr_mask) begin
503            merge <= 1'b0; state <= WR_SEND;
504          end else begin
505            merge <= 1'b1; state <= RD_DESC;
506          end
507        end else if (rd_pending) begin
508          rd_pending <= 1'b0;
509          ch <= l_ch; at <= l_at; merge <= 1'b0;
510          if (l_bad) begin
511            fault <= 1'b1; mem_rd_data <= {DL{1'b0}}; mem_rd_valid <= 1'b1;
512          end else state <= RD_DESC;
513        end
514        RD_DESC: if (md_ready[ch]) state <= RD_WAIT;
515        RD_WAIT: if (mr_valid[ch]) begin
516          if (|mr_ecc[ch*16 + 8 +: 8]) fault <= 1'b1;
517          if (merge && |mr_ecc[ch*16 + 8 +: 8]) begin
518            mem_wr_ready <= 1'b1; state <= ACK;
519          end else if (merge) begin
520            line <= merged(mr_data[ch*DL +: DL], line, mask); state <= WR_SEND;
521          end else begin
522            mem_rd_data <= mr_data[ch*DL +: DL]; mem_rd_valid <= 1'b1; state <= IDLE;
523          end
524        end
525        WR_SEND: begin
526          if (md_valid[ch] && md_ready[ch]) desc_sent <= 1'b1;
527          if (mw_valid[ch] && mw_ready[ch]) data_sent <= 1'b1;
528          if ((desc_sent || md_ready[ch]) && (data_sent || mw_ready[ch])) state <= WR_DONE;
529        end
530        WR_DONE: if (mdone_valid[ch]) begin mem_wr_ready <= 1'b1; state <= ACK; end
531        default: state <= IDLE;
532      endcase
533    end
534  end
535endmodule
536"##;
537
538const FAST_PORT: &str = r##"// The fast side's memory port on the channels' fast ports: whole logical lines of the granule space read and written,
539// each where the physical map puts it — logical line x of the space that starts at logical line `space` lies in granule
540// g = (x - space) / 2^granule_log2, on channel g mod NB, at channel line base + (g / NB) * 2^granule_log2 + (x - space)
541// mod 2^granule_log2, as logos_card_port places it. Reads of consecutive lines of one channel go out as one descriptor
542// of up to MAXL lines, and so do writes, a run closed when the next line asked is not its next or none is asked; reads
543// return in the order they were asked, whichever channels hold them, each taken the cycle its channel has it and given
544// from a register the cycle after (rs_valid takes no wait: the side asks for no more lines than it has room for), so the
545// line crosses to the side that asked from a register beside it; a write's line goes to its channel as it is taken,
546// ahead of its run's descriptor, a run closed early when its channel holds all the lines it has room for, and the
547// writes are counted out until their channels report them written (`writing`). A read asked before a write of its line
548// reads the line before the write, a read's descriptor never leaving behind a later write's; a line read while a write
549// of it is still out may read it before the write. A line outside the space, or past a channel's 2^AW lines, faults:
550// its read returns zeros, its write is dropped. `fault` holds until `clear`. A request is taken into two registers of
551// its kind, a clock before the port serves it, and its line's place comes from a register: the line after the one last
552// served is placed a clock ahead, and any other waits a clock to be placed, its run held open.
553module logos_fast_port #(parameter NB = 4, AW = 27, LW = 32, DL = 512, MAXL = 32, ORDER = 64)
554  (input clk, input rst,
555   input [31:0] space, input [AW-1:0] base, input [4:0] granule_log2, input clear, output reg fault,
556   input rq_valid, output rq_ready, input [31:0] rq_line, output rs_valid, output [DL-1:0] rs_data,
557   input wq_valid, output wq_ready, input [31:0] wq_line, input [DL-1:0] wq_data, output writing,
558   output [NB-1:0] md_valid, input [NB-1:0] md_ready, output [NB*(AW+LW+1)-1:0] md_data,
559   input [NB-1:0] mr_valid, output [NB-1:0] mr_ready, input [NB*DL-1:0] mr_data,
560   output [NB-1:0] mw_valid, input [NB-1:0] mw_ready, output [NB*DL-1:0] mw_data,
561   input [NB-1:0] mdone_valid, output [NB-1:0] mdone_ready);
562  localparam CB = NB > 1 ? $clog2(NB) : 1, OB = $clog2(ORDER), NW = $clog2(MAXL + 1);
563  localparam [NW-1:0] FULL = MAXL;
564
565  // Each kind's requests through two registers, taken while the second is empty, so neither the asking unit's address
566  // arithmetic nor the port's placement waits on the other within a clock: the port serves the oldest it holds (rqh_*,
567  // wqh_*), a request from the clock after it is taken. A write's word goes into one of two places as it is taken in
568  // (wq_d0, wq_d1, the next taken in going to wq_put's), and the head's word is the place wq_get names: the words move
569  // only on their own taking in, never on the port's serving a write, which only turns wq_get.
570  reg rqh_valid, rqt_valid, wqh_valid, wqt_valid;
571  reg [31:0] rqh_line, rqt_line, wqh_line, wqt_line;
572  reg [DL-1:0] wq_d0, wq_d1;
573  (* maxfan = 64 *) reg wq_get;
574  reg wq_put;
575  wire [DL-1:0] wqh_data = wq_get ? wq_d1 : wq_d0;
576  assign rq_ready = !rqt_valid;
577  assign wq_ready = !wqt_valid;
578  wire r_in = rq_valid && !rqt_valid, w_in = wq_valid && !wqt_valid;
579
580  // Where line `l` lives: whether it lies outside the space, its channel, its channel line. Its granule g = x >> gl
581  // (x its place in the space) lies on channel g mod NB as the channel's granule g >> CB, so its channel line's bits
582  // below gl are x's and those above are x's CB places higher: no shift by the granule but for the channel's bits.
583  function [CB+AW:0] placed(input [31:0] l, input [31:0] sp, input [AW-1:0] b, input [4:0] gl, input [31:0] mask);
584    reg [31:0] x, g, local_line;
585    reg [32:0] to;
586    begin
587      x = l - sp;
588      g = x >> gl;
589      local_line = (x & mask) | ((x >> CB) & ~mask);
590      to = {1'b0, {(32-AW){1'b0}}, b} + {1'b0, local_line};
591      placed = {l < sp || to[32:AW] != 0, g[CB-1:0], to[AW-1:0]};
592    end
593  endfunction
594  // The map, registered where it enters: it holds for the step, and every place is reckoned from the port's own copy,
595  // a granule's offset mask beside it.
596  reg [31:0] m_space, m_mask;
597  reg [AW-1:0] m_base;
598  reg [4:0] m_gl;
599  always @(posedge clk) begin
600    m_space <= space; m_base <= base; m_gl <= granule_log2; m_mask <= (32'd1 << granule_log2) - 32'd1;
601  end
602  // The place of each kind's next line: the line after the one last taken, or the line waiting; a line asked is placed
603  // once it is the one held (`r_hit`, `w_hit`). A map about to change drops both.
604  reg [31:0] rg_line, wg_line;
605  reg [CB+AW:0] rp, wp;
606  reg rg_ok, wg_ok;
607  wire r_hit = rg_ok && rqh_line == rg_line, w_hit = wg_ok && wqh_line == wg_line;
608  wire map_moves = space != m_space || base != m_base || granule_log2 != m_gl;
609  wire [CB+AW:0] r_here = placed(rqh_line, m_space, m_base, m_gl, m_mask), r_after = placed(rqh_line + 32'd1, m_space, m_base, m_gl, m_mask);
610  wire [CB+AW:0] w_here = placed(wqh_line, m_space, m_base, m_gl, m_mask), w_after = placed(wqh_line + 32'd1, m_space, m_base, m_gl, m_mask);
611  wire r_bad = rp[CB+AW], w_bad = wp[CB+AW];
612  wire [CB-1:0] r_ch = rp[CB+AW-1:AW], w_ch = wp[CB+AW-1:AW];
613  wire [AW-1:0] r_at = rp[AW-1:0], w_at = wp[AW-1:0];
614
615  // The descriptor on its way to its channel: {write, first line, lines}.
616  reg d_valid;
617  reg [CB-1:0] d_ch;
618  reg [AW+LW:0] d_data;
619  wire d_free = !d_valid || md_ready[d_ch];
620
621  // The reads in the order they were asked: each run's channel and lines, or a faulting line's zeros.
622  reg [CB+NW:0] order [0:ORDER-1];
623  reg [OB-1:0] o_wr, o_rd;
624  reg [OB:0] o_count;
625  reg [NW-1:0] o_given;
626  wire [CB+NW:0] o_head = order[o_rd];
627  wire o_zero = o_head[CB+NW], o_have = o_count != 0;
628  wire [CB-1:0] o_ch = o_head[CB+NW-1:NW];
629  wire [NW-1:0] o_n = o_head[NW-1:0];
630  localparam [OB:0] O_LAST = ORDER - 1;
631  wire o_room = o_count < O_LAST;
632  wire o_take = o_have && (o_zero || mr_valid[o_ch]);
633  wire o_pop = o_take && o_given + 1'b1 == o_n;
634  (* maxfan = 64 *) reg rs_q;
635  reg [DL-1:0] rs_d;
636  always @(posedge clk) begin
637    rs_q <= !rst && o_take;
638    rs_d <= o_zero ? {DL{1'b0}} : mr_data[o_ch*DL +: DL];
639  end
640  assign rs_valid = rs_q;
641  assign rs_data = rs_d;
642
643  // The run of reads being gathered, and of writes, each with the channel line it would take next.
644  reg r_open, w_open;
645  reg [CB-1:0] rr_ch, wr_ch;
646  reg [AW-1:0] rr_at, wr_at, rr_next, wr_next;
647  reg [NW-1:0] rr_n, wr_n;
648  wire r_joins = r_open && !r_bad && r_ch == rr_ch && r_at == rr_next && rr_n != FULL;
649  wire w_joins = w_open && !w_bad && w_ch == wr_ch && w_at == wr_next && wr_n != FULL;
650  // A run closes into the descriptor when the next line asked is not its next, or none is, or, for writes, when its
651  // channel has no room for the next line (the lines it holds leave only behind their descriptor): the reads' first.
652  wire r_close = r_open && !(rqh_valid && (!r_hit || r_joins)) && d_free && o_room;
653  wire w_close = w_open && !(wqh_valid && (!w_hit || (w_joins && mw_ready[wr_ch]))) && d_free && !r_close;
654  // A read is taken joining its run, opening one where none is open or the open one closes, or, faulting, with none
655  // open; a write joining its run, or opening one where none is open or the open one closes, its channel taking its
656  // line, or faulting.
657  wire rqh_ready = r_hit && (r_bad ? !r_open && o_room : r_joins || !r_open || r_close);
658  wire wqh_ready = w_hit && (w_bad || ((w_joins || !w_open || w_close) && mw_ready[w_ch]));
659  wire r_take = rqh_valid && rqh_ready, w_take = wqh_valid && wqh_ready;
660  wire r_zero = r_take && r_bad, w_line = w_take && !w_bad;
661
662  genvar b;
663  generate for (b = 0; b < NB; b = b + 1) begin : channel
664    assign md_valid[b] = d_valid && d_ch == b;
665    assign md_data[b*(AW+LW+1) +: AW+LW+1] = d_data;
666    assign mr_ready[b] = o_have && !o_zero && o_ch == b;
667    assign mw_valid[b] = wqh_valid && w_hit && !w_bad && w_ch == b && (w_joins || !w_open || w_close);
668    assign mw_data[b*DL +: DL] = wqh_data;
669    assign mdone_ready[b] = 1'b1;
670  end endgenerate
671
672  // Writes out: descriptors sent less writes reported written. The done tokens are counted by a continuous assignment,
673  // which a simulator evaluates from time 0 whether or not a token ever comes.
674  reg [OB:0] w_out;
675  function [CB:0] ones(input [NB-1:0] v);
676    integer i;
677    begin
678      ones = 0;
679      for (i = 0; i < NB; i = i + 1) ones = ones + {{CB{1'b0}}, v[i]};
680    end
681  endfunction
682  wire [CB:0] dones = ones(mdone_valid);
683  wire w_sent = d_valid && md_ready[d_ch] && d_data[AW+LW];
684  assign writing = wqh_valid || wqt_valid || w_open || (d_valid && d_data[AW+LW]) || w_out != 0;
685
686  always @(posedge clk) begin
687    if (rst) begin
688      rqh_valid <= 1'b0; rqt_valid <= 1'b0; wqh_valid <= 1'b0; wqt_valid <= 1'b0; wq_put <= 1'b0; wq_get <= 1'b0;
689    end else begin
690      case ({r_take, r_in})
691        2'b01: if (rqh_valid) begin rqt_valid <= 1'b1; rqt_line <= rq_line; end
692               else begin rqh_valid <= 1'b1; rqh_line <= rq_line; end
693        2'b10: begin rqh_valid <= rqt_valid; rqh_line <= rqt_line; rqt_valid <= 1'b0; end
694        2'b11: rqh_line <= rq_line;
695        default: ;
696      endcase
697      case ({w_take, w_in})
698        2'b01: if (wqh_valid) begin wqt_valid <= 1'b1; wqt_line <= wq_line; end
699               else begin wqh_valid <= 1'b1; wqh_line <= wq_line; end
700        2'b10: begin wqh_valid <= wqt_valid; wqh_line <= wqt_line; wqt_valid <= 1'b0; end
701        2'b11: wqh_line <= wq_line;
702        default: ;
703      endcase
704      if (w_in) wq_put <= !wq_put;
705      if (w_take) wq_get <= !wq_get;
706    end
707    if (r_take) begin rg_line <= rqh_line + 32'd1; rp <= r_after; end
708    else if (rqh_valid && !r_hit) begin rg_line <= rqh_line; rp <= r_here; end
709    if (w_in && !wq_put) wq_d0 <= wq_data;
710    if (w_in && wq_put) wq_d1 <= wq_data;
711    if (w_take) begin wg_line <= wqh_line + 32'd1; wp <= w_after; end
712    else if (wqh_valid && !w_hit) begin wg_line <= wqh_line; wp <= w_here; end
713    rg_ok <= !rst && !map_moves && (rg_ok || r_take || rqh_valid);
714    wg_ok <= !rst && !map_moves && (wg_ok || w_take || wqh_valid);
715    if (rst) begin
716      d_valid <= 1'b0; r_open <= 1'b0; w_open <= 1'b0; o_wr <= 0; o_rd <= 0; o_count <= 0; o_given <= 0; w_out <= 0;
717      fault <= 1'b0;
718    end else begin
719      if (clear) fault <= 1'b0;
720      if (r_zero || (w_take && w_bad)) fault <= 1'b1;
721      if (d_valid && md_ready[d_ch]) d_valid <= 1'b0;
722      if (r_close) begin
723        d_valid <= 1'b1; d_ch <= rr_ch; d_data <= {1'b0, rr_at, {(LW-NW){1'b0}}, rr_n};
724        order[o_wr] <= {1'b0, rr_ch, rr_n};
725      end else if (w_close) begin
726        d_valid <= 1'b1; d_ch <= wr_ch; d_data <= {1'b1, wr_at, {(LW-NW){1'b0}}, wr_n};
727      end
728      if (r_zero) order[o_wr] <= {1'b1, {CB{1'b0}}, {{(NW-1){1'b0}}, 1'b1}};
729      o_wr <= o_wr + (r_close || r_zero);
730      o_count <= o_count + (r_close || r_zero) - o_pop;
731      if (o_take) o_given <= o_pop ? 0 : o_given + 1'b1;
732      if (o_pop) o_rd <= o_rd + 1'b1;
733      if (r_take && !r_bad) begin
734        if (r_joins) begin rr_n <= rr_n + 1'b1; rr_next <= rr_next + 1'b1; end
735        else begin r_open <= 1'b1; rr_ch <= r_ch; rr_at <= r_at; rr_next <= r_at + 1'b1; rr_n <= 1; end
736      end else if (r_close) r_open <= 1'b0;
737      if (w_line) begin
738        if (w_joins) begin wr_n <= wr_n + 1'b1; wr_next <= wr_next + 1'b1; end
739        else begin w_open <= 1'b1; wr_ch <= w_ch; wr_at <= w_at; wr_next <= w_at + 1'b1; wr_n <= 1; end
740      end else if (w_close) w_open <= 1'b0;
741      w_out <= w_out + w_sent - dones;
742    end
743  end
744endmodule
745"##;
746
747const CHANNEL_MUX: &str = r##"// Several fast ports on the channels' fast ports at once: P ports' channel sides (logos_fast_port's md_*, mr_*, mw_*,
748// mdone_*; port p's of channel c at p·NB + c) onto the NB channels'. Each port's descriptors come in through two places
749// of the port's own, one descriptor at a time whatever channel each is for: the next waits behind the last until its
750// channel takes it, so the channels take a port's runs in the order the port gives them, whichever channels they are
751// of. (A port that offers several channels at once gives them one at a time.) Each port's write lines come in
752// through two places of the port's own likewise, a line leaving for its channel only after every line the port gave
753// before it has left for its own. Every ready a port sees is a register's. Each channel takes its descriptors from its
754// ports in turn, the choice a register a clock ahead: the port it names, if its descriptor is still held and the
755// channel takes one; a port just taken is not chosen the clock after, its next descriptor not yet seen. Each read's
756// lines go back to the port whose descriptor it was and each write's done token
757// likewise, in the order the channel took them (every port reads the channels' lines from mr_data itself). Reads need
758// no other rule so long as every port takes its runs' lines in the order it asked for them and waits on nothing else to
759// take one: each channel serves its descriptors in the order it took them, and descriptors are taken one at a time a
760// channel, so the earliest run taken that has lines still to give is first in both its port's order and its channel's,
761// and always moves. A reader that takes a channel's lines only as its other channels let it (the streaming unit, whose
762// channels' feeds step together) would deadlock a port waiting on a line of one channel behind the reader's run while
763// the reader waits on that port's run in another; such a reader reaches the multiplexer through logos_read_credit a
764// channel, which takes every line as it comes. A write's lines reach its channel ahead of its descriptor, so a channel
765// takes write lines from one port at a time: the port that holds it — the first whose next line is the channel's, from
766// the channel's turn, the clock after it is free — until every line it gave is in a descriptor the channel has taken and
767// its next line is not the channel's. A write's descriptor is taken only from that port. A port holds a channel only once
768// every line it gave before is in its own channel, so each of its earlier writes' descriptors is taken from a channel the
769// port still holds: no port's descriptor waits on a channel whose holder waits on that port's. Each channel's reads and
770// writes owed are at most DEPTH apiece: no descriptor is taken beyond them.
771module logos_channel_mux #(parameter P = 2, NB = 4, AW = 27, LW = 32, DL = 512, DEPTH = 32)
772  (input clk, input rst,
773   input [P*NB-1:0] pd_valid, output [P*NB-1:0] pd_ready, input [P*NB*(AW+LW+1)-1:0] pd_data,
774   output [P*NB-1:0] pr_valid, input [P*NB-1:0] pr_ready,
775   input [P*NB-1:0] pw_valid, output [P*NB-1:0] pw_ready, input [P*NB*DL-1:0] pw_data,
776   output [P*NB-1:0] pdone_valid, input [P*NB-1:0] pdone_ready,
777   output [NB-1:0] md_valid, input [NB-1:0] md_ready, output [NB*(AW+LW+1)-1:0] md_data,
778   input [NB-1:0] mr_valid, output [NB-1:0] mr_ready,
779   output [NB-1:0] mw_valid, input [NB-1:0] mw_ready, output [NB*DL-1:0] mw_data,
780   input [NB-1:0] mdone_valid, output [NB-1:0] mdone_ready);
781  localparam DLW = AW + LW + 1, PB = P > 1 ? $clog2(P) : 1, DB = $clog2(DEPTH);
782  localparam [PB-1:0] LAST = P - 1;
783  localparam [DB:0] ROOM = DEPTH - 2;
784  localparam CB = NB > 1 ? $clog2(NB) : 1;
785  // Inside: each port's held descriptor offered to its channel (h_dv, h_dd) and taken (h_dr), and each port's held
786  // write line offered to its channel (h_wv, h_wd) and taken (h_wr).
787  wire [P*NB-1:0] h_dv, h_dr, h_wv, h_wr;
788  wire [P*NB*DLW-1:0] h_dd;
789  wire [P*DL-1:0] h_wd;
790  // The line of the channel `on` names (one-hot) among each channel's `lines`.
791  function [DL-1:0] line_of(input [NB-1:0] on, input [NB*DL-1:0] lines);
792    integer b;
793    begin
794      line_of = {DL{1'b0}};
795      for (b = 0; b < NB; b = b + 1) if (on[b]) line_of = line_of | lines[b*DL +: DL];
796    end
797  endfunction
798  genvar c, q;
799  generate for (q = 0; q < P; q = q + 1) begin : held
800    // The port's descriptor: the lowest channel it offers one to and that channel's descriptor, taken while the port's
801    // ready is up; the place it leaves from, and a second place for the one taken the clock the first does not leave.
802    reg [CB-1:0] pick;
803    reg [DLW-1:0] offered;
804    integer pc;
805    always @* begin
806      pick = {CB{1'b0}};
807      offered = pd_data[q*NB*DLW +: DLW];
808      for (pc = NB - 1; pc >= 0; pc = pc - 1)
809        if (pd_valid[q*NB + pc]) begin pick = pc[CB-1:0]; offered = pd_data[(q*NB + pc)*DLW +: DLW]; end
810    end
811    reg d_rdy, d_v, d_sv;
812    reg [CB-1:0] d_ch, d_sch;
813    reg [DLW-1:0] d_d, d_sd;
814    wire d_take = |pd_valid[q*NB +: NB] && d_rdy;
815    wire d_moves = !d_v || h_dr[q*NB + d_ch];
816    always @(posedge clk) begin
817      if (d_moves) begin
818        d_v <= d_sv || d_take;
819        d_ch <= d_sv ? d_sch : pick;
820        d_d <= d_sv ? d_sd : offered;
821      end else if (d_take) begin
822        d_sch <= pick;
823        d_sd <= offered;
824      end
825      d_sv <= !d_moves && (d_sv || d_take);
826      d_rdy <= d_moves || !(d_sv || d_take);
827      if (rst) begin d_v <= 1'b0; d_sv <= 1'b0; d_rdy <= 1'b0; end
828    end
829    // The port's write lines, alike, through two places of their own in the order the port gives them, each line with
830    // its channel one-hot (none while the place is empty): the lowest channel the port offers a line to — a port offers
831    // a line to one channel at a time, so its ready is the places' whatever the channel. A line leaves for its channel
832    // only once every line the port gave before it has left for its own, so a port holds a channel's write lines only
833    // after its earlier runs' lines are in theirs, and never waits for a channel's lines behind a later run's. The spare
834    // place takes the port's line every clock it is empty, so a clock that fills it has given it the line taken.
835    wire [NB-1:0] w_offers = pw_valid[q*NB +: NB];
836    wire [NB-1:0] w_first = w_offers & ~(w_offers - {{(NB-1){1'b0}}, 1'b1});
837    wire [DL-1:0] w_line = line_of(w_first, pw_data[q*NB*DL +: NB*DL]);
838    reg w_rdy;
839    (* maxfan = 256 *) reg w_sv;
840    reg [NB-1:0] w_on, w_son;
841    reg [DL-1:0] w_d, w_sd;
842    wire w_take = |w_offers && w_rdy;
843    wire w_moves = (w_on & ~h_wr[q*NB +: NB]) == {NB{1'b0}};
844    always @(posedge clk) begin
845      if (w_moves) begin
846        w_on <= w_sv ? w_son : w_take ? w_first : {NB{1'b0}};
847        w_d <= w_sv ? w_sd : w_line;
848      end
849      if (!w_sv) begin w_son <= w_first; w_sd <= w_line; end
850      w_sv <= !w_moves && (w_sv || w_take);
851      w_rdy <= w_moves || !(w_sv || w_take);
852      if (rst) begin w_on <= {NB{1'b0}}; w_sv <= 1'b0; w_rdy <= 1'b0; end
853    end
854    assign h_wv[q*NB +: NB] = w_on;
855    assign h_wd[q*DL +: DL] = w_d;
856    for (c = 0; c < NB; c = c + 1) begin : lane
857      assign pd_ready[q*NB + c] = d_rdy && pick == c;
858      assign h_dv[q*NB + c] = d_v && d_ch == c;
859      assign h_dd[(q*NB + c)*DLW +: DLW] = d_d;
860      assign pw_ready[q*NB + c] = w_rdy;
861    end
862  end endgenerate
863  generate for (c = 0; c < NB; c = c + 1) begin : channel
864    // Every port's descriptor of this channel, and whether it is a write.
865    wire [P-1:0] offers, writes, lines_offered;
866    wire [DLW*P-1:0] descs;
867    for (q = 0; q < P; q = q + 1) begin : port
868      assign offers[q] = h_dv[q*NB + c];
869      assign descs[DLW*q +: DLW] = h_dd[(q*NB + c)*DLW +: DLW];
870      assign writes[q] = descs[DLW*q + DLW - 1];
871      assign lines_offered[q] = h_wv[q*NB + c];
872    end
873    // The reads owed (each its port and lines) and the writes (each its port), in the order taken.
874    `LOGOS_RAMSTYLE_ASYNC(DEPTH) reg [PB-1:0] r_port [0:DEPTH-1];
875    `LOGOS_RAMSTYLE_ASYNC(DEPTH) reg [LW-1:0] r_lines [0:DEPTH-1];
876    `LOGOS_RAMSTYLE_ASYNC(DEPTH) reg [PB-1:0] w_port [0:DEPTH-1];
877    reg [DB:0] r_n, w_n;
878    reg [DB-1:0] r_wr, r_rd, w_wr, w_rd;
879    reg [LW-1:0] r_given;
880    wire r_have = r_n != 0, w_have = w_n != 0;
881    wire [PB-1:0] r_head = r_port[r_rd], w_head = w_port[w_rd];
882    // The write lines' holder, and its lines given and not yet in a descriptor taken.
883    reg lock;
884    (* maxfan = 256 *) reg [PB-1:0] holder;
885    reg [LW:0] owed_lines;
886    // The port chosen for the next clock's descriptor.
887    reg chosen;
888    reg [PB-1:0] choice, turn;
889    wire [DLW-1:0] desc = descs[DLW*choice +: DLW];
890    wire takes = chosen && offers[choice];
891    assign md_valid[c] = takes;
892    assign md_data[c*DLW +: DLW] = desc;
893    wire taken = takes && md_ready[c];
894    wire w_taken = taken && desc[DLW-1];
895    for (q = 0; q < P; q = q + 1) begin : grant
896      assign h_dr[q*NB + c] = chosen && choice == q && md_ready[c];
897      assign pr_valid[q*NB + c] = mr_valid[c] && r_have && r_head == q;
898      assign pdone_valid[q*NB + c] = mdone_valid[c] && w_have && w_head == q;
899      assign h_wr[q*NB + c] = mw_ready[c] && lock && holder == q;
900    end
901    assign mr_ready[c] = r_have && pr_ready[r_head*NB + c];
902    assign mdone_ready[c] = w_have && pdone_ready[w_head*NB + c];
903    assign mw_valid[c] = lock && h_wv[holder*NB + c];
904    assign mw_data[c*DL +: DL] = h_wd[holder*DL +: DL];
905    wire given = mw_valid[c] && mw_ready[c];
906    wire r_back = mr_valid[c] && mr_ready[c];
907    wire r_done = r_back && r_given + 1'b1 == r_lines[r_rd];
908    wire w_back = mdone_valid[c] && mdone_ready[c];
909    wire [LW:0] owed_next = owed_lines + {{LW{1'b0}}, given} - (w_taken ? {1'b0, desc[LW-1:0]} : {(LW+1){1'b0}});
910    // The next clock's choice: from the turn, the first port offering a descriptor the channel may take — a read with a
911    // read owed's room after this clock's, a write from the holder with a write's room — other than the port taken now.
912    reg next_chosen;
913    reg [PB-1:0] next_choice, lock_pick;
914    reg lock_any;
915    integer k, j;
916    always @* begin
917      next_chosen = 1'b0;
918      next_choice = {PB{1'b0}};
919      lock_any = 1'b0;
920      lock_pick = {PB{1'b0}};
921      for (k = 0; k < P; k = k + 1) begin
922        j = k + turn;
923        if (j >= P) j = j - P;
924        if (!next_chosen && offers[j] && !(taken && choice == j[PB-1:0])
925            && (writes[j] ? lock && holder == j[PB-1:0] && w_n <= ROOM : r_n <= ROOM)) begin
926          next_chosen = 1'b1;
927          next_choice = j[PB-1:0];
928        end
929        if (!lock_any && lines_offered[j]) begin lock_any = 1'b1; lock_pick = j[PB-1:0]; end
930      end
931    end
932    always @(posedge clk) begin
933      chosen <= !rst && next_chosen;
934      choice <= next_choice;
935      if (taken) turn <= (choice == LAST) ? {PB{1'b0}} : choice + 1'b1;
936      if (taken && !desc[DLW-1]) begin r_port[r_wr] <= choice; r_lines[r_wr] <= desc[LW-1:0]; r_wr <= r_wr + 1'b1; end
937      if (w_taken) begin w_port[w_wr] <= choice; w_wr <= w_wr + 1'b1; end
938      if (r_back) r_given <= r_done ? {LW{1'b0}} : r_given + 1'b1;
939      if (r_done) r_rd <= r_rd + 1'b1;
940      if (w_back) w_rd <= w_rd + 1'b1;
941      r_n <= r_n + {{DB{1'b0}}, taken && !desc[DLW-1]} - {{DB{1'b0}}, r_done};
942      w_n <= w_n + {{DB{1'b0}}, w_taken} - {{DB{1'b0}}, w_back};
943      owed_lines <= owed_next;
944      if (!lock) begin lock <= lock_any; holder <= lock_pick; end
945      else if (owed_next == 0 && !h_wv[holder*NB + c]) lock <= 1'b0;
946      if (rst) begin
947        turn <= {PB{1'b0}}; r_wr <= 0; r_rd <= 0; w_wr <= 0; w_rd <= 0; r_n <= 0; w_n <= 0; r_given <= 0; owed_lines <= 0;
948        lock <= 1'b0;
949      end
950    end
951  end endgenerate
952endmodule
953"##;
954
955const READ_CREDIT: &str = r##"// One reader's lines of one channel through the channel multiplexer, asked only as they can be held. The reader's read
956// descriptors (a first line and a count of any size, its write bit clear) are asked of the channel in runs of at most
957// MAXL lines, a run only while the lines asked and not yet taken by the reader, the run's own included, are at most
958// 2^LA; every line back is held in a buffer of 2^LA lines the reader takes from in order, its next line in a register.
959// So the multiplexer never waits on the reader for a line (mr_ready is high), however the reader takes them: a reader
960// that takes a channel's lines only as its other channels let it — the streaming unit, whose channels' feeds step
961// together — needs this to share the channels with ports that take their lines in the order they asked for them.
962module logos_read_credit #(parameter AW = 27, LW = 32, DL = 512, LA = 9, MAXL = 32)
963  (input clk, input rst,
964   input in_valid, output in_ready, input [AW+LW:0] in_data,
965   output out_valid, input out_ready, output [DL-1:0] out_data,
966   output reg md_valid, input md_ready, output [AW+LW:0] md_data,
967   input mr_valid, output mr_ready, input [DL-1:0] mr_data);
968  localparam NW = $clog2(MAXL + 1);
969  localparam [LW-1:0] RUN = MAXL, TWO_RUNS = 2 * MAXL;
970  localparam [NW-1:0] RUN_N = MAXL;
971  localparam [LA:0] ALL = 1 << LA;
972  // The descriptor being asked: its next line, whether it has lines left, and whether more than a run's.
973  reg [AW-1:0] at;
974  reg [LW-1:0] left;
975  reg none, more;
976  wire [NW-1:0] n = more ? RUN_N : left[NW-1:0];
977  // The lines the buffer can still promise: all of it less the lines asked and not yet taken by the reader.
978  reg [LA:0] credit;
979  wire pop = out_valid && out_ready;
980  wire ask = !none && (!md_valid || md_ready) && credit >= {{(LA+1-NW){1'b0}}, n};
981  assign in_ready = none;
982  reg [AW-1:0] md_line;
983  reg [NW-1:0] md_n;
984  assign md_data = {1'b0, md_line, {(LW-NW){1'b0}}, md_n};
985  always @(posedge clk) begin
986    if (md_valid && md_ready) md_valid <= 1'b0;
987    if (ask) begin
988      md_valid <= 1'b1; md_line <= at; md_n <= n;
989      at <= at + {{(AW-NW){1'b0}}, n}; left <= left - {{(LW-NW){1'b0}}, n};
990      none <= !more; more <= more && left > TWO_RUNS;
991    end else if (in_valid && none) begin
992      at <= in_data[LW +: AW]; left <= in_data[LW-1:0];
993      none <= in_data[LW-1:0] == {LW{1'b0}}; more <= in_data[LW-1:0] > RUN;
994    end
995    credit <= credit - (ask ? {{(LA+1-NW){1'b0}}, n} : {(LA+1){1'b0}}) + {{LA{1'b0}}, pop};
996    if (rst) begin md_valid <= 1'b0; none <= 1'b1; more <= 1'b0; credit <= ALL; end
997  end
998  // The lines back, in the order they come: written into the buffer, read a clock ahead of the reader into one of two
999  // places beside it (o0 its next), so the reader's line and its valid are registers.
1000  assign mr_ready = 1'b1;
1001  (* ramstyle = "M20K" *) reg [DL-1:0] held [0:(1 << LA) - 1];
1002  reg [LA-1:0] wp, rp;
1003  reg [LA:0] stored;
1004  reg [DL-1:0] got, o0, o1;
1005  reg fetched;
1006  reg [1:0] on;
1007  wire [1:0] kept = on - {1'b0, pop};
1008  wire fetch = stored != {(LA+1){1'b0}} && kept + {1'b0, fetched} <= 2'd1;
1009  assign out_valid = on != 2'd0;
1010  assign out_data = o0;
1011  always @(posedge clk) begin
1012    if (mr_valid) held[wp] <= mr_data;
1013    if (fetch) got <= held[rp];
1014    if (pop) o0 <= o1;
1015    if (fetched) begin
1016      if (kept == 2'd0) o0 <= got;
1017      else o1 <= got;
1018    end
1019    wp <= wp + {{(LA-1){1'b0}}, mr_valid};
1020    rp <= rp + {{(LA-1){1'b0}}, fetch};
1021    stored <= stored + {{LA{1'b0}}, mr_valid} - {{LA{1'b0}}, fetch};
1022    fetched <= fetch;
1023    on <= kept + {1'b0, fetched};
1024    if (rst) begin wp <= {LA{1'b0}}; rp <= {LA{1'b0}}; stored <= {(LA+1){1'b0}}; fetched <= 1'b0; on <= 2'd0; end
1025  end
1026endmodule
1027"##;