Skip to main content

logicaffeine_compile/codegen_sva/
ddr4_jedec.rs

1//! ddr4_jedec — a DDR4 channel's timing at its clock, the JESD79-4 command rules a trace is held to, and the
2//! in-order controller that turns line streams into commands on it.
3//!
4//! [`Ddr4Grade`] is a channel as its controller is configured: its geometry (bank groups, banks, rows, columns, bus
5//! width), its latencies (CL, CWL, the command-address parity latency), its datasheet minimums in picoseconds or in
6//! clocks as the configuration states them, and how many memory clocks one controller clock spans. [`Ddr4Grade::at`]
7//! clocks it: every picosecond minimum rounds up to whole clocks at the memory clock, tRTP to no fewer than four, tRC
8//! is tRAS + tRP where the configuration names none, and tREFI rounds down.
9//!
10//! [`BusChecker`] replays a command trace against the clocked rules, one command at a time, and stops at the first
11//! violation; [`check_bus`] runs it over a whole trace. [`Scheduler`] is the controller: one column command a line in
12//! the order the lines come, each at the earliest clock its rules and the data bus allow; a line's row opened (and the
13//! bank's other row closed) at the earliest clock its rules allow once the line is within [`LOOKAHEAD`] lines of the
14//! column commands, so row commands run ahead of the data and hide behind it; one command a clock; an all-bank
15//! refresh, after precharging every open bank, as each refresh interval comes due. It releases its commands in clock
16//! order once no later decision can come before them, so a stream of any length runs through the checker in bounded
17//! memory ([`measure`]).
18//!
19//! The rules are JESD79-4's for one rank, a fixed burst of eight, additive latency 0 and a one-clock write preamble
20//! (the D5005 controller's MEM_DDR4_WRITE_PREAMBLE 1), and agree with Ramulator 2's DDR4 constraint table: read
21//! latency CL + PL, write latency CWL + PL; column to column tCCD_L within a bank group and tCCD_S across; WRITE to
22//! READ WL + BL/2 + tWTR_L within a bank group and tWTR_S across; READ to WRITE RL + BL/2 − WL + 2; READ to PRECHARGE
23//! tRTP; WRITE to PRECHARGE WL + BL/2 + tWR; ACT to column tRCD; ACT to PRECHARGE tRAS; PRECHARGE to ACT or REFRESH
24//! tRP; ACT to ACT tRC in one bank, tRRD_L within a bank group, tRRD_S across, and no fifth ACT within tFAW of the
25//! fourth before it; REFRESH with every bank precharged, and no command for tRFC after it; no two bursts on the data
26//! bus at once. A PRECHARGE to a bank with no open row is legal and changes nothing but its tRP. The refresh account
27//! starts at cycle 0 with the device freshly refreshed: each tREFI that passes owes one REFRESH, each REFRESH pays
28//! one, at most eight may be owed at any clock and at most eight paid ahead (one past that pays nothing). That bound
29//! keeps every row open less than 9 × tREFI, which is tRAS's maximum, so tRAS's maximum needs no rule of its own.
30
31use super::ddr2_jedec::Violation;
32use super::dram::StreamDesc;
33use serde::Serialize;
34use std::collections::{BTreeMap, VecDeque};
35
36/// The REFRESH commands JESD79-4 lets a controller owe, and the most it may pay ahead, in fixed 1x mode.
37pub const REFRESH_POSTPONE_MAX: i64 = 8;
38pub const REFRESH_PULL_IN_MAX: i64 = 8;
39/// Beats in a burst: the burst length is fixed at eight (MEM_DDR4_BL_ENUM DDR4_BL_BL8).
40pub const BL: u64 = 8;
41/// Lines the controller model holds ahead of its column commands, whose rows it may open early. Modeled: the depth
42/// of the hard controller's command queue is not in its configuration.
43pub const LOOKAHEAD: usize = 16;
44/// Where [`Ddr4Grade::D5005_EMIF`] is read from.
45pub const D5005_EMIF_IP: &str = "OFS ofs-d5005 2024.1-1 ipss/mem/ip/emif_ddr4_no_ecc.ip";
46
47/// A DDR4 channel as its controller is configured: geometry, latencies and timing minimums (picoseconds unless named
48/// in clocks).
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
50pub struct Ddr4Grade {
51    pub part: &'static str,
52    pub bank_groups: u32,
53    pub banks_per_group: u32,
54    pub row_bits: u32,
55    pub col_bits: u32,
56    /// Data bits the channel carries a transfer.
57    pub dq_bits: u32,
58    /// Memory clocks a controller clock spans: a controller-side word is one burst.
59    pub rate: u64,
60    pub mem_mhz: u64,
61    pub cl: u64,
62    pub cwl: u64,
63    /// Command-address parity latency, added to both latencies.
64    pub pl: u64,
65    pub trcd_ps: u64,
66    pub trp_ps: u64,
67    pub tras_ps: u64,
68    /// ACT to ACT in one bank; `None` for tRAS + tRP.
69    pub trc_ps: Option<u64>,
70    pub trrd_s_ck: u64,
71    pub trrd_l_ck: u64,
72    pub tfaw_ps: u64,
73    pub tccd_s_ck: u64,
74    pub tccd_l_ck: u64,
75    pub twr_ps: u64,
76    pub twtr_s_ck: u64,
77    pub twtr_l_ck: u64,
78    pub trtp_ps: u64,
79    pub trfc_ps: u64,
80    pub trefi_ps: u64,
81}
82
83impl Ddr4Grade {
84    /// One memory channel of the Intel PAC D5005 as its OFS shell configures it ([`D5005_EMIF_IP`]): a single-rank
85    /// RDIMM of x8 devices (MEM_DDR4_FORMAT_ENUM MEM_FORMAT_RDIMM, MEM_DDR4_RANKS_PER_DIMM 1, MEM_DDR4_DQ_PER_DQS 8),
86    /// 72 bits wide (MEM_DDR4_DQ_WIDTH 72); 4 bank groups of 4 banks (MEM_DDR4_BANK_GROUP_WIDTH 2,
87    /// MEM_DDR4_BANK_ADDR_WIDTH 2), 2^16 rows (MEM_DDR4_ROW_ADDR_WIDTH 16) of 2^10 columns (MEM_DDR4_COL_ADDR_WIDTH 10);
88    /// DDR4-2400 (MEM_DDR4_SPEEDBIN_ENUM DDR4_SPEEDBIN_2400) at 1200 MHz (PHY_DDR4_MEM_CLK_FREQ_MHZ 1200.0) under a
89    /// quarter-rate controller (PHY_DDR4_RATE_ENUM RATE_QUARTER); CL 21 (MEM_DDR4_TCL), CWL 16 (MEM_DDR4_WTCL),
90    /// parity latency 4 (MEM_DDR4_AC_PARITY_LATENCY DDR4_AC_PARITY_LATENCY_4); tRCD and tRP 14.16 ns, tRAS 32 ns,
91    /// tFAW 21 ns, tWR 15 ns, tRFC 350 ns, tREFI 7.8 µs (MEM_DDR4_TRCD_NS, _TRP_NS, _TRAS_NS, _TFAW_NS, _TWR_NS,
92    /// _TRFC_NS, _TREFI_US); tRRD_S 4, tRRD_L 6, tCCD_S 4, tCCD_L 6, tWTR_S 3, tWTR_L 9 clocks (MEM_DDR4_TRRD_S_CYC,
93    /// _TRRD_L_CYC, _TCCD_S_CYC, _TCCD_L_CYC, _TWTR_S_CYC, _TWTR_L_CYC). The configuration names no tRTP: it is
94    /// JESD79-4's minimum, the larger of 4 clocks and 7.5 ns.
95    pub const D5005_EMIF: Ddr4Grade = Ddr4Grade {
96        part: "d5005-emif-ddr4-2400",
97        bank_groups: 4,
98        banks_per_group: 4,
99        row_bits: 16,
100        col_bits: 10,
101        dq_bits: 72,
102        rate: 4,
103        mem_mhz: 1200,
104        cl: 21,
105        cwl: 16,
106        pl: 4,
107        trcd_ps: 14_160,
108        trp_ps: 14_160,
109        tras_ps: 32_000,
110        trc_ps: None,
111        trrd_s_ck: 4,
112        trrd_l_ck: 6,
113        tfaw_ps: 21_000,
114        tccd_s_ck: 4,
115        tccd_l_ck: 6,
116        twr_ps: 15_000,
117        twtr_s_ck: 3,
118        twtr_l_ck: 9,
119        trtp_ps: 7_500,
120        trfc_ps: 350_000,
121        trefi_ps: 7_800_000,
122    };
123
124    /// The configuration clocked: every picosecond minimum in whole memory clocks, rounded up.
125    pub fn at(&self) -> Ddr4Timing {
126        let up = |ps: u64| (ps * self.mem_mhz).div_ceil(1_000_000);
127        Ddr4Timing {
128            grade: *self,
129            rl: self.cl + self.pl,
130            wl: self.cwl + self.pl,
131            trcd: up(self.trcd_ps),
132            trp: up(self.trp_ps),
133            tras: up(self.tras_ps),
134            trc: up(self.trc_ps.unwrap_or(self.tras_ps + self.trp_ps)),
135            trrd_s: self.trrd_s_ck,
136            trrd_l: self.trrd_l_ck,
137            tfaw: up(self.tfaw_ps),
138            tccd_s: self.tccd_s_ck.max(BL / 2),
139            tccd_l: self.tccd_l_ck.max(BL / 2),
140            twr: up(self.twr_ps),
141            twtr_s: self.twtr_s_ck,
142            twtr_l: self.twtr_l_ck,
143            trtp: up(self.trtp_ps).max(4),
144            trfc: up(self.trfc_ps),
145            trefi: self.trefi_ps * self.mem_mhz / 1_000_000,
146        }
147    }
148}
149
150/// A channel clocked: every rule in whole memory clocks.
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
152pub struct Ddr4Timing {
153    pub grade: Ddr4Grade,
154    pub rl: u64,
155    pub wl: u64,
156    pub trcd: u64,
157    pub trp: u64,
158    pub tras: u64,
159    pub trc: u64,
160    pub trrd_s: u64,
161    pub trrd_l: u64,
162    pub tfaw: u64,
163    pub tccd_s: u64,
164    pub tccd_l: u64,
165    pub twr: u64,
166    pub twtr_s: u64,
167    pub twtr_l: u64,
168    pub trtp: u64,
169    pub trfc: u64,
170    pub trefi: u64,
171}
172
173impl Ddr4Timing {
174    pub fn banks(&self) -> u32 {
175        self.grade.bank_groups * self.grade.banks_per_group
176    }
177
178    /// Lines (bursts) the channel addresses: every bank's rows' columns in bursts of eight.
179    pub fn lines(&self) -> u64 {
180        u64::from(self.banks()) << (self.grade.row_bits + self.grade.col_bits - BL.trailing_zeros())
181    }
182
183    /// Bits a burst moves: eight transfers of the channel's width.
184    pub fn burst_bits(&self) -> u64 {
185        BL * u64::from(self.grade.dq_bits)
186    }
187
188    /// Clocks a burst holds the data bus.
189    pub fn burst_ck(&self) -> u64 {
190        BL / 2
191    }
192
193    /// WRITE to READ: WL + BL/2 + tWTR_L within a bank group, tWTR_S across.
194    pub fn write_to_read(&self, same_group: bool) -> u64 {
195        self.wl + BL / 2 + if same_group { self.twtr_l } else { self.twtr_s }
196    }
197
198    /// READ to WRITE, any bank: RL + BL/2 − WL + 2.
199    pub fn read_to_write(&self) -> u64 {
200        self.rl + BL / 2 + 2 - self.wl
201    }
202
203    /// WRITE to PRECHARGE, one bank: WL + BL/2 + tWR.
204    pub fn write_to_precharge(&self) -> u64 {
205        self.wl + BL / 2 + self.twr
206    }
207
208    /// Controller clocks `mem_ck` memory clocks take, rounded up.
209    pub fn controller_ck(&self, mem_ck: u64) -> u64 {
210        mem_ck.div_ceil(self.grade.rate)
211    }
212
213    /// Bytes per second the data bus carries at full use, `line_bytes` of every burst being data.
214    pub fn peak_bytes_per_s(&self, line_bytes: u64) -> f64 {
215        line_bytes as f64 * self.grade.mem_mhz as f64 * 1e6 / self.burst_ck() as f64
216    }
217}
218
219/// Where a line lives: bank group, bank within it, row, and the burst's first column (a multiple of eight).
220#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
221pub struct Location {
222    pub group: u32,
223    pub bank: u32,
224    pub row: u64,
225    pub col: u64,
226}
227
228/// A line (one burst) by the controller's address order, chip select, row, bank, column, bank group from the most
229/// significant end (CTRL_DDR4_ADDR_ORDER_ENUM DDR4_CTRL_ADDR_ORDER_CS_R_B_C_BG): consecutive lines take the bank
230/// groups in turn, then the bursts of a row, then the banks, then the rows. Lines past the channel's last wrap.
231pub fn locate(t: &Ddr4Timing, line: u64) -> Location {
232    let g = t.grade;
233    let gbits = g.bank_groups.trailing_zeros();
234    let cbits = g.col_bits - BL.trailing_zeros();
235    let bbits = g.banks_per_group.trailing_zeros();
236    let field = |shift: u32, bits: u32| (line >> shift) & ((1u64 << bits) - 1);
237    Location {
238        group: field(0, gbits) as u32,
239        col: field(gbits, cbits) << BL.trailing_zeros(),
240        bank: field(gbits + cbits, bbits) as u32,
241        row: field(gbits + cbits + bbits, g.row_bits),
242    }
243}
244
245/// One command on the channel. `PreAll` precharges every bank; `Ref` is an all-bank refresh.
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum Ddr4Cmd {
248    Act { group: u32, bank: u32, row: u64 },
249    Rd { group: u32, bank: u32, col: u64 },
250    Wr { group: u32, bank: u32, col: u64 },
251    Pre { group: u32, bank: u32 },
252    PreAll,
253    Ref,
254}
255
256/// A command and the memory clock it issues on.
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258pub struct Ddr4Timed {
259    pub at: u64,
260    pub cmd: Ddr4Cmd,
261}
262
263/// What a legal trace put on the channel: its commands by kind, the bursts that turned the data bus around, the clocks
264/// its bursts held the data bus, and the clock after its last data or command.
265#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
266pub struct Ddr4Facts {
267    pub activates: u64,
268    pub reads: u64,
269    pub writes: u64,
270    pub precharges: u64,
271    pub refreshes: u64,
272    pub turnarounds: u64,
273    pub data_ck: u64,
274    pub end: u64,
275}
276
277impl Ddr4Facts {
278    /// The share of the trace's clocks the data bus carried data.
279    pub fn efficiency(&self) -> f64 {
280        if self.end == 0 {
281            0.0
282        } else {
283            self.data_ck as f64 / self.end as f64
284        }
285    }
286
287    /// Bytes per second the trace moved, `line_bytes` of every burst being data.
288    pub fn bytes_per_s(&self, t: &Ddr4Timing, line_bytes: u64) -> f64 {
289        self.efficiency() * t.peak_bytes_per_s(line_bytes)
290    }
291}
292
293#[derive(Clone, Copy)]
294struct CheckedBank {
295    open: Option<u64>,
296    act_at: u64,
297    last_act: Option<u64>,
298    ready_at: u64,
299    last_rd: Option<u64>,
300    last_wr: Option<u64>,
301}
302
303/// The JESD79-4 rules of one channel, held over a trace one command at a time.
304pub struct BusChecker {
305    t: Ddr4Timing,
306    banks: Vec<CheckedBank>,
307    last_act: Option<u64>,
308    last_act_group: Vec<Option<u64>>,
309    faw: VecDeque<u64>,
310    last_cas: Option<u64>,
311    last_cas_group: Vec<Option<u64>>,
312    last_rd: Option<u64>,
313    last_wr_group: Vec<Option<u64>>,
314    last_dir: Option<bool>,
315    data_free: u64,
316    refresh_until: u64,
317    /// REFRESH paid minus REFRESH owed, as of `account_at`.
318    balance: i64,
319    account_at: u64,
320    prev: Option<u64>,
321    index: usize,
322    facts: Ddr4Facts,
323}
324
325impl BusChecker {
326    pub fn new(t: &Ddr4Timing) -> BusChecker {
327        let groups = t.grade.bank_groups as usize;
328        let bank = CheckedBank { open: None, act_at: 0, last_act: None, ready_at: 0, last_rd: None, last_wr: None };
329        BusChecker {
330            t: *t,
331            banks: vec![bank; t.banks() as usize],
332            last_act: None,
333            last_act_group: vec![None; groups],
334            faw: VecDeque::with_capacity(4),
335            last_cas: None,
336            last_cas_group: vec![None; groups],
337            last_rd: None,
338            last_wr_group: vec![None; groups],
339            last_dir: None,
340            data_free: 0,
341            refresh_until: 0,
342            balance: 0,
343            account_at: 0,
344            prev: None,
345            index: 0,
346            facts: Ddr4Facts::default(),
347        }
348    }
349
350    fn violation(&self, cycle: u64, rule: &'static str, detail: String) -> Violation {
351        Violation { index: self.index, cycle, rule, detail }
352    }
353
354    /// Bring the refresh account to clock `at`: one REFRESH owed for every tREFI boundary passed.
355    fn owe_until(&mut self, at: u64) {
356        self.balance -= (at / self.t.trefi - self.account_at / self.t.trefi) as i64;
357        self.account_at = at;
358    }
359
360    fn bank(&self, group: u32, bank: u32, at: u64) -> Result<usize, Violation> {
361        let g = self.t.grade;
362        if group >= g.bank_groups || bank >= g.banks_per_group {
363            return Err(self.violation(at, "address range", format!("bank group {group} bank {bank}")));
364        }
365        Ok((group * g.banks_per_group + bank) as usize)
366    }
367
368    /// Hold the trace's next command to every rule.
369    pub fn push(&mut self, c: Ddr4Timed) -> Result<(), Violation> {
370        let t = self.t;
371        let g = t.grade;
372        let at = c.at;
373        if let Some(p) = self.prev {
374            if at <= p {
375                return Err(self.violation(at, "one command per clock", format!("the command before it was at clock {p}")));
376            }
377        }
378        if at < self.refresh_until {
379            return Err(self.violation(at, "tRFC", format!("a REFRESH holds the channel until clock {}", self.refresh_until)));
380        }
381        self.owe_until(at);
382        let paid = self.balance + i64::from(c.cmd == Ddr4Cmd::Ref);
383        if paid < -REFRESH_POSTPONE_MAX {
384            return Err(self.violation(at, "refresh postponed", format!("{} REFRESH owed at clock {at}", -paid)));
385        }
386        match c.cmd {
387            Ddr4Cmd::Act { group, bank, row } => {
388                let b = self.bank(group, bank, at)?;
389                if row >= 1 << g.row_bits {
390                    return Err(self.violation(at, "address range", format!("row {row} of {}", 1u64 << g.row_bits)));
391                }
392                let s = self.banks[b];
393                if let Some(open) = s.open {
394                    return Err(self.violation(at, "ACT to an open bank", format!("bank {b} has row {open} open since clock {}", s.act_at)));
395                }
396                if at < s.ready_at {
397                    return Err(self.violation(at, "tRP", format!("bank {b} was precharged at clock {}", s.ready_at - t.trp)));
398                }
399                if let Some(la) = s.last_act {
400                    if at < la + t.trc {
401                        return Err(self.violation(at, "tRC", format!("bank {b} was activated at clock {la}")));
402                    }
403                }
404                if let Some(la) = self.last_act_group[group as usize] {
405                    if at < la + t.trrd_l {
406                        return Err(self.violation(at, "tRRD_L", format!("an ACT to bank group {group} at clock {la}")));
407                    }
408                }
409                if let Some(la) = self.last_act {
410                    if at < la + t.trrd_s {
411                        return Err(self.violation(at, "tRRD_S", format!("an ACT at clock {la}")));
412                    }
413                }
414                if self.faw.len() == 4 {
415                    if at < self.faw[0] + t.tfaw {
416                        return Err(self.violation(at, "tFAW", format!("four ACTs since clock {}", self.faw[0])));
417                    }
418                    self.faw.pop_front();
419                }
420                self.faw.push_back(at);
421                self.last_act = Some(at);
422                self.last_act_group[group as usize] = Some(at);
423                self.banks[b] = CheckedBank { open: Some(row), act_at: at, last_act: Some(at), last_rd: None, last_wr: None, ..s };
424                self.facts.activates += 1;
425            }
426            Ddr4Cmd::Rd { group, bank, col } | Ddr4Cmd::Wr { group, bank, col } => {
427                let write = matches!(c.cmd, Ddr4Cmd::Wr { .. });
428                let b = self.bank(group, bank, at)?;
429                if col >= 1 << g.col_bits || col % BL != 0 {
430                    return Err(self.violation(at, "address range", format!("column {col} of {} in bursts of {BL}", 1u64 << g.col_bits)));
431                }
432                let s = self.banks[b];
433                if s.open.is_none() {
434                    return Err(self.violation(at, "column command to an idle bank", format!("bank {b} has no open row")));
435                }
436                if at < s.act_at + t.trcd {
437                    return Err(self.violation(at, "tRCD", format!("bank {b} was activated at clock {}", s.act_at)));
438                }
439                if let Some(l) = self.last_cas_group[group as usize] {
440                    if at < l + t.tccd_l {
441                        return Err(self.violation(at, "tCCD_L", format!("a column command to bank group {group} at clock {l}")));
442                    }
443                }
444                if let Some(l) = self.last_cas {
445                    if at < l + t.tccd_s {
446                        return Err(self.violation(at, "tCCD_S", format!("a column command at clock {l}")));
447                    }
448                }
449                if write {
450                    if let Some(r) = self.last_rd {
451                        if at < r + t.read_to_write() {
452                            return Err(self.violation(at, "READ to WRITE", format!("a READ at clock {r}")));
453                        }
454                    }
455                } else {
456                    for (grp, w) in self.last_wr_group.iter().enumerate() {
457                        if let Some(w) = *w {
458                            let same = grp as u32 == group;
459                            if at < w + t.write_to_read(same) {
460                                let rule = if same { "tWTR_L" } else { "tWTR_S" };
461                                return Err(self.violation(at, rule, format!("a WRITE to bank group {grp} at clock {w}")));
462                            }
463                        }
464                    }
465                }
466                let data_at = at + if write { t.wl } else { t.rl };
467                if data_at < self.data_free {
468                    return Err(self.violation(at, "data bus", format!("a burst holds the data bus until clock {}", self.data_free)));
469                }
470                self.data_free = data_at + t.burst_ck();
471                if self.last_dir.is_some_and(|d| d != write) {
472                    self.facts.turnarounds += 1;
473                }
474                self.last_dir = Some(write);
475                self.last_cas = Some(at);
476                self.last_cas_group[group as usize] = Some(at);
477                if write {
478                    self.last_wr_group[group as usize] = Some(at);
479                    self.banks[b].last_wr = Some(at);
480                    self.facts.writes += 1;
481                } else {
482                    self.last_rd = Some(at);
483                    self.banks[b].last_rd = Some(at);
484                    self.facts.reads += 1;
485                }
486                self.facts.data_ck += t.burst_ck();
487                self.facts.end = self.facts.end.max(self.data_free);
488            }
489            Ddr4Cmd::Pre { .. } | Ddr4Cmd::PreAll => {
490                let targets = match c.cmd {
491                    Ddr4Cmd::Pre { group, bank } => {
492                        let b = self.bank(group, bank, at)?;
493                        b..b + 1
494                    }
495                    _ => 0..self.banks.len(),
496                };
497                for b in targets {
498                    let s = self.banks[b];
499                    if s.open.is_some() {
500                        if at < s.act_at + t.tras {
501                            return Err(self.violation(at, "tRAS", format!("bank {b} was activated at clock {}", s.act_at)));
502                        }
503                        if let Some(r) = s.last_rd {
504                            if at < r + t.trtp {
505                                return Err(self.violation(at, "tRTP", format!("bank {b} was read at clock {r}")));
506                            }
507                        }
508                        if let Some(w) = s.last_wr {
509                            if at < w + t.write_to_precharge() {
510                                return Err(self.violation(at, "tWR", format!("bank {b} was written at clock {w}")));
511                            }
512                        }
513                    }
514                    self.banks[b].open = None;
515                    self.banks[b].ready_at = at + t.trp;
516                }
517                self.facts.precharges += 1;
518            }
519            Ddr4Cmd::Ref => {
520                for (b, s) in self.banks.iter().enumerate() {
521                    if let Some(row) = s.open {
522                        return Err(self.violation(at, "REFRESH with an open bank", format!("bank {b} has row {row} open")));
523                    }
524                    if at < s.ready_at {
525                        return Err(self.violation(at, "tRP", format!("bank {b} was precharged at clock {}", s.ready_at - t.trp)));
526                    }
527                }
528                self.refresh_until = at + t.trfc;
529                self.balance = (self.balance + 1).min(REFRESH_PULL_IN_MAX);
530                self.facts.refreshes += 1;
531            }
532        }
533        self.prev = Some(at);
534        self.facts.end = self.facts.end.max(at + 1);
535        self.index += 1;
536        Ok(())
537    }
538
539    /// The trace ends: the refresh account must hold at its last clock too.
540    pub fn finish(mut self) -> Result<Ddr4Facts, Violation> {
541        let end = self.facts.end;
542        self.owe_until(end);
543        if self.balance < -REFRESH_POSTPONE_MAX {
544            return Err(self.violation(end, "refresh postponed", format!("{} REFRESH owed at the trace's end, clock {end}", -self.balance)));
545        }
546        Ok(self.facts)
547    }
548}
549
550/// Hold `trace` to the JESD79-4 rules of `t`: the first violation, or what the trace put on the channel.
551pub fn check_bus(t: &Ddr4Timing, trace: &[Ddr4Timed]) -> Result<Ddr4Facts, Violation> {
552    let mut checker = BusChecker::new(t);
553    for &c in trace {
554        checker.push(c)?;
555    }
556    checker.finish()
557}
558
559/// The lines a stream touches, in its order, with its direction: every line any of its elements covers, a line that
560/// consecutive elements share once.
561pub fn stream_lines(s: &StreamDesc, line_bytes: u64) -> impl Iterator<Item = (u64, bool)> + '_ {
562    let mut last = None;
563    (0..s.count)
564        .flat_map(move |i| {
565            let start = s.base + i * s.stride_bytes;
566            start / line_bytes..(start + s.elem_bytes.max(1)).div_ceil(line_bytes)
567        })
568        .filter(move |&line| last.replace(line) != Some(line))
569        .map(move |line| (line, s.write))
570}
571
572#[derive(Clone, Copy, Default)]
573struct OpenBank {
574    row: Option<u64>,
575    act_at: u64,
576    last_act: Option<u64>,
577    ready_at: u64,
578    last_rd: Option<u64>,
579    last_wr: Option<u64>,
580    /// The bank's last command: no later command of its own comes before it.
581    last_use: u64,
582}
583
584/// The controller model: lines in, commands out in clock order.
585pub struct Scheduler {
586    t: Ddr4Timing,
587    lookahead: usize,
588    /// Placed commands not yet released, by clock: a clock holds one command.
589    pending: BTreeMap<u64, Ddr4Cmd>,
590    /// Every clock below this is released and no command is placed below it. After each line it stands at the next
591    /// line's place in the queue (the clock after the column command `lookahead` lines back) or past it, so it holds
592    /// a line's row commands until the line enters the queue.
593    released: u64,
594    /// The column clocks of the last `lookahead` lines.
595    recent: VecDeque<u64>,
596    banks: Vec<OpenBank>,
597    last_act: Option<u64>,
598    last_act_group: Vec<Option<u64>>,
599    faw: VecDeque<u64>,
600    last_cas: Option<u64>,
601    last_cas_group: Vec<Option<u64>>,
602    last_rd: Option<u64>,
603    last_wr_group: Vec<Option<u64>>,
604    data_free: u64,
605    refresh_until: u64,
606    next_refresh: u64,
607}
608
609impl Scheduler {
610    pub fn new(t: &Ddr4Timing, lookahead: usize) -> Scheduler {
611        let groups = t.grade.bank_groups as usize;
612        let lookahead = lookahead.max(1);
613        Scheduler {
614            t: *t,
615            lookahead,
616            pending: BTreeMap::new(),
617            released: 0,
618            recent: VecDeque::with_capacity(lookahead),
619            banks: vec![OpenBank::default(); t.banks() as usize],
620            last_act: None,
621            last_act_group: vec![None; groups],
622            faw: VecDeque::with_capacity(4),
623            last_cas: None,
624            last_cas_group: vec![None; groups],
625            last_rd: None,
626            last_wr_group: vec![None; groups],
627            data_free: 0,
628            refresh_until: 0,
629            next_refresh: t.trefi,
630        }
631    }
632
633    /// The clock after the last column command: every later column command and refresh comes at or after it.
634    fn frontier(&self) -> u64 {
635        self.last_cas.map_or(0, |c| c + 1)
636    }
637
638    /// The clock after the column command of the line `lookahead` lines back: the next line's row commands wait for
639    /// it, the line entering the queue as that one leaves.
640    fn queued(&self) -> u64 {
641        if self.recent.len() == self.lookahead {
642            self.recent[0] + 1
643        } else {
644            0
645        }
646    }
647
648    fn place(&mut self, earliest: u64, cmd: Ddr4Cmd) -> u64 {
649        let mut at = earliest.max(self.released).max(self.refresh_until);
650        while self.pending.contains_key(&at) {
651            at += 1;
652        }
653        self.pending.insert(at, cmd);
654        at
655    }
656
657    /// The earliest clock bank `b`'s open row may close.
658    fn close_from(&self, b: usize) -> u64 {
659        let (t, s) = (&self.t, &self.banks[b]);
660        let mut at = (s.act_at + t.tras).max(s.last_use + 1);
661        if let Some(r) = s.last_rd {
662            at = at.max(r + t.trtp);
663        }
664        if let Some(w) = s.last_wr {
665            at = at.max(w + t.write_to_precharge());
666        }
667        at
668    }
669
670    /// The refresh now due: every open bank precharged, then the REFRESH, neither before its interval comes due.
671    fn refresh(&mut self) {
672        let t = self.t;
673        let due = self.frontier().max(self.next_refresh);
674        let open: Vec<usize> = (0..self.banks.len()).filter(|&b| self.banks[b].row.is_some()).collect();
675        if !open.is_empty() {
676            let pre_at = open.iter().map(|&b| self.close_from(b)).fold(due, u64::max);
677            let p = self.place(pre_at, Ddr4Cmd::PreAll);
678            for s in &mut self.banks {
679                s.row = None;
680                s.ready_at = p + t.trp;
681                s.last_use = p;
682            }
683        }
684        let ref_at = self.banks.iter().map(|s| s.ready_at.max(s.last_use + 1)).fold(due, u64::max);
685        let r = self.place(ref_at, Ddr4Cmd::Ref);
686        for s in &mut self.banks {
687            s.last_use = r;
688        }
689        self.refresh_until = r + t.trfc;
690        self.next_refresh += t.trefi;
691    }
692
693    /// Issue one line: a READ, or a WRITE when `write`, opening its row first when it is not the bank's open one. The
694    /// clock of its column command.
695    pub fn line(&mut self, line: u64, write: bool, sink: &mut dyn FnMut(Ddr4Timed)) -> u64 {
696        self.line_at(line, write, 0, sink)
697    }
698
699    /// Issue one line the controller takes at clock `arrive`: none of its commands comes before it, and a refresh
700    /// that came due while the channel waited for it is issued first. The clock of its column command.
701    pub fn line_at(&mut self, line: u64, write: bool, arrive: u64, sink: &mut dyn FnMut(Ddr4Timed)) -> u64 {
702        let t = self.t;
703        let g = t.grade;
704        while self.frontier().max(arrive) >= self.next_refresh {
705            self.refresh();
706        }
707        let loc = locate(&t, line);
708        let b = (loc.group * g.banks_per_group + loc.bank) as usize;
709        if self.banks[b].row != Some(loc.row) {
710            if self.banks[b].row.is_some() {
711                let pre_at = self.close_from(b).max(arrive);
712                let p = self.place(pre_at, Ddr4Cmd::Pre { group: loc.group, bank: loc.bank });
713                let s = &mut self.banks[b];
714                s.row = None;
715                s.ready_at = p + t.trp;
716                s.last_use = p;
717            }
718            let s = self.banks[b];
719            let mut act_at = s.ready_at.max(s.last_use + 1).max(arrive);
720            if let Some(la) = s.last_act {
721                act_at = act_at.max(la + t.trc);
722            }
723            if let Some(la) = self.last_act_group[loc.group as usize] {
724                act_at = act_at.max(la + t.trrd_l);
725            }
726            if let Some(la) = self.last_act {
727                act_at = act_at.max(la + t.trrd_s);
728            }
729            if self.faw.len() == 4 {
730                act_at = act_at.max(self.faw[0] + t.tfaw);
731            }
732            let a = self.place(act_at, Ddr4Cmd::Act { group: loc.group, bank: loc.bank, row: loc.row });
733            if self.faw.len() == 4 {
734                self.faw.pop_front();
735            }
736            self.faw.push_back(a);
737            self.last_act = Some(a);
738            self.last_act_group[loc.group as usize] = Some(a);
739            self.banks[b] = OpenBank { row: Some(loc.row), act_at: a, last_act: Some(a), ready_at: s.ready_at, last_rd: None, last_wr: None, last_use: a };
740        }
741        let latency = if write { t.wl } else { t.rl };
742        let mut cas_at = (self.banks[b].act_at + t.trcd).max(self.frontier()).max(arrive);
743        if let Some(l) = self.last_cas_group[loc.group as usize] {
744            cas_at = cas_at.max(l + t.tccd_l);
745        }
746        if let Some(l) = self.last_cas {
747            cas_at = cas_at.max(l + t.tccd_s);
748        }
749        if write {
750            if let Some(r) = self.last_rd {
751                cas_at = cas_at.max(r + t.read_to_write());
752            }
753        } else {
754            for (grp, w) in self.last_wr_group.iter().enumerate() {
755                if let Some(w) = *w {
756                    cas_at = cas_at.max(w + t.write_to_read(grp as u32 == loc.group));
757                }
758            }
759        }
760        cas_at = cas_at.max(self.data_free.saturating_sub(latency));
761        let cmd = if write {
762            Ddr4Cmd::Wr { group: loc.group, bank: loc.bank, col: loc.col }
763        } else {
764            Ddr4Cmd::Rd { group: loc.group, bank: loc.bank, col: loc.col }
765        };
766        let c = self.place(cas_at, cmd);
767        self.data_free = c + latency + t.burst_ck();
768        self.last_cas = Some(c);
769        self.last_cas_group[loc.group as usize] = Some(c);
770        if write {
771            self.last_wr_group[loc.group as usize] = Some(c);
772            self.banks[b].last_wr = Some(c);
773        } else {
774            self.last_rd = Some(c);
775            self.banks[b].last_rd = Some(c);
776        }
777        self.banks[b].last_use = c;
778        if self.recent.len() == self.lookahead {
779            self.recent.pop_front();
780        }
781        self.recent.push_back(c);
782        self.release(sink);
783        c
784    }
785
786    /// Hand `sink`, in clock order, every placed command below the earliest clock a later one may take: a later column
787    /// command or refresh comes after the last column command; a later ACT after the last ACT and a PRECHARGE after its
788    /// bank's last command, each no earlier than the next line's place in the queue. Every clock before that place is
789    /// so released, and no command placed after it: that is the floor under the next line's row commands.
790    fn release(&mut self, sink: &mut dyn FnMut(Ddr4Timed)) {
791        let queued = self.queued();
792        let mut low = self.frontier().min(self.last_act.map_or(0, |a| a + 1).max(queued));
793        for s in &self.banks {
794            if s.row.is_some() {
795                low = low.min((s.last_use + 1).max(queued));
796            }
797        }
798        if low <= self.released {
799            return;
800        }
801        let later = self.pending.split_off(&low);
802        for (at, cmd) in std::mem::replace(&mut self.pending, later) {
803            sink(Ddr4Timed { at, cmd });
804        }
805        self.released = low;
806    }
807
808    /// Every command still held, in clock order.
809    pub fn finish(mut self, sink: &mut dyn FnMut(Ddr4Timed)) {
810        for (at, cmd) in std::mem::take(&mut self.pending) {
811            sink(Ddr4Timed { at, cmd });
812        }
813    }
814}
815
816/// The controller's trace for `streams` (byte addresses in one channel, `line_bytes` to a burst) at [`LOOKAHEAD`].
817pub fn schedule(t: &Ddr4Timing, line_bytes: u64, streams: &[StreamDesc]) -> Vec<Ddr4Timed> {
818    let mut out = Vec::new();
819    let mut sched = Scheduler::new(t, LOOKAHEAD);
820    let mut sink = |c: Ddr4Timed| out.push(c);
821    for s in streams {
822        for (line, write) in stream_lines(s, line_bytes) {
823            sched.line(line, write, &mut sink);
824        }
825    }
826    sched.finish(&mut sink);
827    out
828}
829
830/// The controller's trace for `streams` held to the rules as it is issued, never held whole: what it put on the
831/// channel, or the first rule it broke.
832pub fn measure(t: &Ddr4Timing, line_bytes: u64, streams: &[StreamDesc]) -> Result<Ddr4Facts, Violation> {
833    let mut checker = BusChecker::new(t);
834    let mut broke: Option<Violation> = None;
835    {
836        let mut sched = Scheduler::new(t, LOOKAHEAD);
837        let mut sink = |c: Ddr4Timed| {
838            if broke.is_none() {
839                broke = checker.push(c).err();
840            }
841        };
842        for s in streams {
843            for (line, write) in stream_lines(s, line_bytes) {
844                sched.line(line, write, &mut sink);
845            }
846        }
847        sched.finish(&mut sink);
848    }
849    match broke {
850        Some(v) => Err(v),
851        None => checker.finish(),
852    }
853}
854
855/// Clocks a command kind is logged as by [`verilog_model`]: ACT, READ, WRITE, PRECHARGE, PRECHARGE ALL, REFRESH.
856pub const LOGGED_KINDS: [&str; 6] = ["ACT", "RD", "WR", "PRE", "PREA", "REF"];
857
858/// A DDR4 channel behind an Avalon-MM slave for simulation, scheduled by this module's controller model: every line
859/// a burst brings is issued as [`Scheduler::line_at`] issues it at the memory clock the slave takes it (four to a
860/// controller clock), a write's data is stored as it is taken, and a read's data, as the memory held it when the read
861/// was taken, returns in order `PIPE` controller clocks after the controller clock its burst leaves the data bus in.
862/// With `LOG` set it writes, to `<LOGFILE>_<ID>.log`, every line it schedules (`L line write arrive column`) and every
863/// command it places (`C clock kind group bank row-or-column`, the kind an index of [`LOGGED_KINDS`]).
864pub fn verilog_model(t: &Ddr4Timing, lookahead: usize) -> String {
865    let g = t.grade;
866    let gbits = g.bank_groups.trailing_zeros();
867    let cbits = g.col_bits - BL.trailing_zeros();
868    let bbits = g.banks_per_group.trailing_zeros();
869    let banks = t.banks();
870    let groups = g.bank_groups;
871    format!(
872        r#"// A DDR4 channel ({part}) behind an Avalon-MM slave, for simulation: the in-order controller model of
873// codegen_sva::ddr4_jedec schedules every line at the memory clock the slave takes it, {rate} memory clocks a controller
874// clock; a read's data returns in order PIPE controller clocks after its burst leaves the data bus.
875module logos_ddr4_channel_model #(parameter AW = 27, parameter DW = 512, parameter BW = 7, parameter LINES_LOG2 = 12,
876                                  parameter PIPE = 0, parameter QUEUE = 1024, parameter LOG = 0,
877                                  parameter LOGFILE = "ddr4_channel", parameter ID = 0) (
878  input               clk,
879  input               rst_n,
880  input  [AW-1:0]     address,
881  input               read,
882  input               write,
883  input  [BW-1:0]     burstcount,
884  input  [DW-1:0]     writedata,
885  input  [DW/8-1:0]   byteenable,
886  output reg          waitrequest,
887  output reg [DW-1:0] readdata,
888  output reg          readdatavalid
889);
890  localparam RATE = {rate}, RL = {rl}, WL = {wl}, TRCD = {trcd}, TRP = {trp}, TRAS = {tras}, TRC = {trc};
891  localparam TRRD_S = {trrd_s}, TRRD_L = {trrd_l}, TFAW = {tfaw}, TCCD_S = {tccd_s}, TCCD_L = {tccd_l};
892  localparam TRTP = {trtp}, WTP = {wtp}, RTW = {rtw}, WTR_S = {wtr_s}, WTR_L = {wtr_l}, TRFC = {trfc}, TREFI = {trefi};
893  localparam BURST = {burst}, NB = {banks}, NG = {groups}, LOOK = {lookahead}, RING = 65536;
894  reg [DW-1:0] mem [0:(1<<LINES_LOG2)-1];
895  integer i, logf;
896  reg [8*256-1:0] log_name;
897  initial begin
898    for (i = 0; i < (1 << LINES_LOG2); i = i + 1) mem[i] = {{DW{{1'bx}}}};
899    if (LOG) begin
900      $sformat(log_name, "%0s_%0d.log", LOGFILE, ID);
901      logf = $fopen(log_name, "w");
902    end
903  end
904
905  // The controller model's state, as the scheduler keeps it.
906  reg        b_open [0:NB-1];
907  reg [63:0] b_row [0:NB-1], b_act_at [0:NB-1], b_last_act [0:NB-1], b_ready_at [0:NB-1];
908  reg [63:0] b_last_rd [0:NB-1], b_last_wr [0:NB-1], b_last_use [0:NB-1];
909  reg        b_has_act [0:NB-1], b_has_rd [0:NB-1], b_has_wr [0:NB-1];
910  reg        has_act, has_cas, has_rd;
911  reg [63:0] last_act, last_cas, last_rd, data_free, refresh_until, next_refresh, released;
912  reg        g_has_act [0:NG-1], g_has_cas [0:NG-1], g_has_wr [0:NG-1];
913  reg [63:0] g_last_act [0:NG-1], g_last_cas [0:NG-1], g_last_wr [0:NG-1];
914  reg [63:0] faw [0:3];
915  integer    faw_n, rec_n, rec_h;
916  reg [63:0] recent [0:LOOK-1];
917  reg        slot_v [0:RING-1];
918  reg [63:0] slot_t [0:RING-1];
919  initial begin
920    for (i = 0; i < NB; i = i + 1) begin
921      b_open[i] = 0; b_has_act[i] = 0; b_has_rd[i] = 0; b_has_wr[i] = 0;
922      b_row[i] = 0; b_act_at[i] = 0; b_last_act[i] = 0; b_ready_at[i] = 0; b_last_rd[i] = 0; b_last_wr[i] = 0;
923      b_last_use[i] = 0;
924    end
925    for (i = 0; i < NG; i = i + 1) begin
926      g_has_act[i] = 0; g_has_cas[i] = 0; g_has_wr[i] = 0; g_last_act[i] = 0; g_last_cas[i] = 0; g_last_wr[i] = 0;
927    end
928    for (i = 0; i < RING; i = i + 1) slot_v[i] = 0;
929    has_act = 0; has_cas = 0; has_rd = 0; last_act = 0; last_cas = 0; last_rd = 0; data_free = 0;
930    refresh_until = 0; next_refresh = TREFI; released = 0; faw_n = 0; rec_n = 0; rec_h = 0;
931  end
932
933  function [63:0] max2(input [63:0] a, input [63:0] b);
934    max2 = a > b ? a : b;
935  endfunction
936  function [63:0] frontier(input dummy);
937    frontier = has_cas ? last_cas + 1 : 0;
938  endfunction
939  function [63:0] queued(input dummy);
940    queued = rec_n == LOOK ? recent[rec_h] + 1 : 0;
941  endfunction
942  function [63:0] close_from(input integer b);
943    begin
944      close_from = max2(b_act_at[b] + TRAS, b_last_use[b] + 1);
945      if (b_has_rd[b]) close_from = max2(close_from, b_last_rd[b] + TRTP);
946      if (b_has_wr[b]) close_from = max2(close_from, b_last_wr[b] + WTP);
947    end
948  endfunction
949
950  task place(input [63:0] earliest, input integer kind, input integer grp, input integer bnk, input [63:0] arg,
951             output [63:0] at);
952    begin
953      at = max2(max2(earliest, released), refresh_until);
954      while (slot_v[at % RING] && slot_t[at % RING] == at) at = at + 1;
955      slot_v[at % RING] = 1;
956      slot_t[at % RING] = at;
957      if (LOG) $fwrite(logf, "C %0d %0d %0d %0d %0d\n", at, kind, grp, bnk, arg);
958    end
959  endtask
960
961  task refresh;
962    reg [63:0] due, pre_at, ref_at, p, r;
963    reg any_open;
964    integer b;
965    begin
966      due = max2(frontier(0), next_refresh);
967      any_open = 0;
968      pre_at = due;
969      for (b = 0; b < NB; b = b + 1) if (b_open[b]) begin
970        any_open = 1;
971        pre_at = max2(pre_at, close_from(b));
972      end
973      if (any_open) begin
974        place(pre_at, 4, 0, 0, 0, p);
975        for (b = 0; b < NB; b = b + 1) begin
976          b_open[b] = 0;
977          b_ready_at[b] = p + TRP;
978          b_last_use[b] = p;
979        end
980      end
981      ref_at = due;
982      for (b = 0; b < NB; b = b + 1) ref_at = max2(ref_at, max2(b_ready_at[b], b_last_use[b] + 1));
983      place(ref_at, 5, 0, 0, 0, r);
984      for (b = 0; b < NB; b = b + 1) b_last_use[b] = r;
985      refresh_until = r + TRFC;
986      next_refresh = next_refresh + TREFI;
987    end
988  endtask
989
990  task release_floor;
991    reg [63:0] q, low;
992    integer b;
993    begin
994      q = queued(0);
995      low = frontier(0);
996      low = low < max2(has_act ? last_act + 1 : 0, q) ? low : max2(has_act ? last_act + 1 : 0, q);
997      for (b = 0; b < NB; b = b + 1) if (b_open[b] && max2(b_last_use[b] + 1, q) < low) low = max2(b_last_use[b] + 1, q);
998      if (low > released) released = low;
999    end
1000  endtask
1001
1002  task line_at(input [63:0] line, input wr, input [63:0] arrive, output [63:0] c);
1003    reg [63:0] row, col, pre_at, act_at, cas_at, p, a, latency;
1004    integer grp, bnk, b, k;
1005    begin
1006      while (max2(frontier(0), arrive) >= next_refresh) refresh;
1007      grp = line[{gb1}:0];
1008      col = line[{c_hi}:{gbits}] << 3;
1009      bnk = line[{b_hi}:{b_lo}];
1010      row = line[{r_hi}:{r_lo}];
1011      b = grp * {bpg} + bnk;
1012      if (!(b_open[b] && b_row[b] == row)) begin
1013        if (b_open[b]) begin
1014          pre_at = max2(close_from(b), arrive);
1015          place(pre_at, 3, grp, bnk, 0, p);
1016          b_open[b] = 0;
1017          b_ready_at[b] = p + TRP;
1018          b_last_use[b] = p;
1019        end
1020        act_at = max2(max2(b_ready_at[b], b_last_use[b] + 1), arrive);
1021        if (b_has_act[b]) act_at = max2(act_at, b_last_act[b] + TRC);
1022        if (g_has_act[grp]) act_at = max2(act_at, g_last_act[grp] + TRRD_L);
1023        if (has_act) act_at = max2(act_at, last_act + TRRD_S);
1024        if (faw_n == 4) act_at = max2(act_at, faw[0] + TFAW);
1025        place(act_at, 0, grp, bnk, row, a);
1026        if (faw_n == 4) begin
1027          faw[0] = faw[1]; faw[1] = faw[2]; faw[2] = faw[3]; faw[3] = a;
1028        end else begin
1029          faw[faw_n] = a;
1030          faw_n = faw_n + 1;
1031        end
1032        has_act = 1; last_act = a;
1033        g_has_act[grp] = 1; g_last_act[grp] = a;
1034        b_open[b] = 1; b_row[b] = row; b_act_at[b] = a; b_has_act[b] = 1; b_last_act[b] = a;
1035        b_has_rd[b] = 0; b_has_wr[b] = 0; b_last_use[b] = a;
1036      end
1037      latency = wr ? WL : RL;
1038      cas_at = max2(max2(b_act_at[b] + TRCD, frontier(0)), arrive);
1039      if (g_has_cas[grp]) cas_at = max2(cas_at, g_last_cas[grp] + TCCD_L);
1040      if (has_cas) cas_at = max2(cas_at, last_cas + TCCD_S);
1041      if (wr) begin
1042        if (has_rd) cas_at = max2(cas_at, last_rd + RTW);
1043      end else begin
1044        for (k = 0; k < NG; k = k + 1) if (g_has_wr[k]) cas_at = max2(cas_at, g_last_wr[k] + (k == grp ? WTR_L : WTR_S));
1045      end
1046      cas_at = max2(cas_at, data_free >= latency ? data_free - latency : 0);
1047      place(cas_at, wr ? 2 : 1, grp, bnk, col, c);
1048      data_free = c + latency + BURST;
1049      has_cas = 1; last_cas = c;
1050      g_has_cas[grp] = 1; g_last_cas[grp] = c;
1051      if (wr) begin
1052        g_has_wr[grp] = 1; g_last_wr[grp] = c;
1053        b_has_wr[b] = 1; b_last_wr[b] = c;
1054      end else begin
1055        has_rd = 1; last_rd = c;
1056        b_has_rd[b] = 1; b_last_rd[b] = c;
1057      end
1058      b_last_use[b] = c;
1059      if (rec_n == LOOK) begin
1060        recent[rec_h] = c;
1061        rec_h = (rec_h + 1) % LOOK;
1062      end else begin
1063        recent[(rec_h + rec_n) % LOOK] = c;
1064        rec_n = rec_n + 1;
1065      end
1066      release_floor;
1067      if (LOG) $fwrite(logf, "L %0d %0d %0d %0d\n", line, wr, arrive, c);
1068    end
1069  endtask
1070
1071  // Reads taken and not yet returned, in order: the line's data and the controller clock it is due.
1072  reg [DW-1:0] q_data [0:QUEUE-1];
1073  reg [63:0]   q_due [0:QUEUE-1];
1074  integer qh = 0, qn = 0;
1075  reg [63:0] cycle = 0, cas;
1076  reg [AW-1:0] w_addr;
1077  reg [BW-1:0] w_left = 0;
1078  reg [AW-1:0] at_line;
1079  integer j;
1080  always @(posedge clk) begin
1081    cycle = cycle + 1;
1082    readdatavalid <= 1'b0;
1083    if (!rst_n) begin
1084      waitrequest <= 1'b1;
1085    end else begin
1086      if (read && !waitrequest) begin
1087        for (j = 0; j < burstcount; j = j + 1) begin
1088          at_line = address + j;
1089          line_at(at_line, 1'b0, cycle * RATE, cas);
1090          q_data[(qh + qn) % QUEUE] = mem[at_line[LINES_LOG2-1:0]];
1091          q_due[(qh + qn) % QUEUE] = (cas + RL + BURST + RATE - 1) / RATE + PIPE;
1092          qn = qn + 1;
1093        end
1094      end
1095      if (write && !waitrequest) begin
1096        at_line = w_left == 0 ? address : w_addr;
1097        for (j = 0; j < DW / 8; j = j + 1)
1098          if (byteenable[j]) mem[at_line[LINES_LOG2-1:0]][8*j +: 8] = writedata[8*j +: 8];
1099        line_at(at_line, 1'b1, cycle * RATE, cas);
1100        w_left = w_left == 0 ? burstcount - 1 : w_left - 1;
1101        w_addr = at_line + 1;
1102      end
1103      if (qn != 0 && q_due[qh] <= cycle) begin
1104        readdata <= q_data[qh];
1105        readdatavalid <= 1'b1;
1106        qh = (qh + 1) % QUEUE;
1107        qn = qn - 1;
1108      end
1109      waitrequest <= qn + (1 << (BW - 1)) > QUEUE;
1110    end
1111  end
1112endmodule
1113"#,
1114        part = g.part,
1115        rate = g.rate,
1116        rl = t.rl,
1117        wl = t.wl,
1118        trcd = t.trcd,
1119        trp = t.trp,
1120        tras = t.tras,
1121        trc = t.trc,
1122        trrd_s = t.trrd_s,
1123        trrd_l = t.trrd_l,
1124        tfaw = t.tfaw,
1125        tccd_s = t.tccd_s,
1126        tccd_l = t.tccd_l,
1127        trtp = t.trtp,
1128        wtp = t.write_to_precharge(),
1129        rtw = t.read_to_write(),
1130        wtr_s = t.write_to_read(false),
1131        wtr_l = t.write_to_read(true),
1132        trfc = t.trfc,
1133        trefi = t.trefi,
1134        burst = t.burst_ck(),
1135        banks = banks,
1136        groups = groups,
1137        lookahead = lookahead.max(1),
1138        gb1 = gbits - 1,
1139        gbits = gbits,
1140        c_hi = gbits + cbits - 1,
1141        b_lo = gbits + cbits,
1142        b_hi = gbits + cbits + bbits - 1,
1143        r_lo = gbits + cbits + bbits,
1144        r_hi = gbits + cbits + bbits + g.row_bits - 1,
1145        bpg = g.banks_per_group,
1146    )
1147}