Skip to main content

logicaffeine_compile/codegen_sva/hart/
ddr2.rs

1//! The hart's main memory: the Nexys A7's DDR2, a Micron MT47H64M16 (1 Gb, x16, eight banks of 8192 rows of
2//! 1024 columns), run by `hart_ddr2.v` at CK = 125 MHz — the slowest clock the part's DLL is specified for, and twice
3//! the hart's. One hart clock is a *frame*: two CK cycles, four data beats, one 64-bit word.
4//!
5//! The controller moves whole 64-byte lines, closed-page: ACTIVATE, four BL8 bursts two frames apart (the last with
6//! auto-precharge), and the bank idle again before the next line. Every command is in a frame's first CK cycle.
7//! This module is the one definition of the part's timing, the frame counts the controller schedules by, the mode
8//! registers, the power-up sequence, the address map and the calibration pattern; `hart_ddr2.v` takes them through
9//! [`verilog_params`], and the simulation's device model checks the controller against the part's own numbers.
10
11use super::super::ddr2_phy::{ddr2_pins, Ddr2Cmd, InitStep, ModeReg};
12
13/// One CK cycle, and one frame (a hart clock), in picoseconds.
14pub const CK_PS: u64 = 8_000;
15pub const FRAME_PS: u64 = 2 * CK_PS;
16
17/// The part's timing, from the MT47H64M16 data sheet (-25E grade), in picoseconds unless named in CK cycles.
18#[derive(Clone, Copy, Debug)]
19pub struct Part {
20    /// ACTIVATE to READ or WRITE.
21    pub trcd: u64,
22    /// PRECHARGE to ACTIVATE.
23    pub trp: u64,
24    /// ACTIVATE to PRECHARGE, least.
25    pub tras: u64,
26    /// ACTIVATE to ACTIVATE in one bank.
27    pub trc: u64,
28    /// ACTIVATE to ACTIVATE in different banks.
29    pub trrd: u64,
30    /// The last write beat to PRECHARGE.
31    pub twr: u64,
32    /// The last write beat to READ.
33    pub twtr: u64,
34    /// READ to PRECHARGE.
35    pub trtp: u64,
36    /// REFRESH to anything.
37    pub trfc: u64,
38    /// The longest one refresh may follow another.
39    pub trefi: u64,
40    /// Power-up with CKE low.
41    pub power_up: u64,
42    /// CKE high to the first command.
43    pub cke_settle: u64,
44    /// A mode register load to the next command, in CK.
45    pub tmrd_ck: u64,
46    /// DLL reset to the first READ, in CK.
47    pub tdllk_ck: u64,
48    /// CAS latency, in CK; write latency is one less.
49    pub cl: u64,
50}
51
52pub const MT47H64M16: Part = Part {
53    trcd: 12_500,
54    trp: 12_500,
55    tras: 45_000,
56    trc: 57_500,
57    trrd: 10_000,
58    twr: 15_000,
59    twtr: 7_500,
60    trtp: 7_500,
61    trfc: 127_500,
62    trefi: 7_800_000,
63    power_up: 200_000_000,
64    cke_settle: 400_000,
65    tmrd_ck: 2,
66    tdllk_ck: 200,
67    cl: 5,
68};
69
70/// Burst length, in beats (BL8): sixteen bytes, two frames.
71pub const BURST_BEATS: u64 = 8;
72pub const BURSTS_PER_LINE: u64 = 4;
73
74/// Frames covering at least `ps`.
75pub const fn frames(ps: u64) -> u64 {
76    (ps + FRAME_PS - 1) / FRAME_PS
77}
78
79/// CK cycles covering at least `ps`.
80pub const fn cks(ps: u64) -> u64 {
81    (ps + CK_PS - 1) / CK_PS
82}
83
84/// The controller's schedule, in frames: what [`verilog_params`] hands `hart_ddr2.v`. A line transaction issues
85/// ACTIVATE at frame 0 and its four column commands at `cas(k)`; the write data's DQS preamble is the frame after a
86/// WRITE and its eight beats the two frames after that (WL = CL − 1 = four CK cycles); the bank is idle and every
87/// constraint met by `read_done` / `write_done`.
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub struct Schedule {
90    pub rcd: u64,
91    pub ccd: u64,
92    pub read_done: u64,
93    pub write_done: u64,
94    pub rfc: u64,
95    pub refi: u64,
96    pub power_up: u64,
97    pub cke_settle: u64,
98    pub mrd: u64,
99    pub dllk: u64,
100}
101
102impl Schedule {
103    pub fn cas(&self, k: u64) -> u64 {
104        self.rcd + k * self.ccd
105    }
106}
107
108pub fn schedule(p: &Part) -> Schedule {
109    let rcd = frames(p.trcd);
110    let ccd = BURST_BEATS / 4;
111    let last = rcd + (BURSTS_PER_LINE - 1) * ccd;
112    // A READ with auto-precharge precharges BL/2 − 2 + max(tRTP, 2 CK) CK after it, not before tRAS from ACTIVATE;
113    // the bank takes tRP more, and the next ACTIVATE also waits tRC.
114    let rd_pre_ps = (last * FRAME_PS + (BURST_BEATS / 2 - 2) * CK_PS + p.trtp.max(2 * CK_PS)).max(p.tras);
115    let read_done = frames(rd_pre_ps + p.trp).max(frames(p.trc));
116    // A WRITE with auto-precharge precharges WL + BL/2 + WR CK after it (WR = tWR in whole CK cycles).
117    let wl = p.cl - 1;
118    let wr_pre_ps = (last * FRAME_PS + (wl + BURST_BEATS / 2 + cks(p.twr)) * CK_PS).max(p.tras);
119    let write_done = frames(wr_pre_ps + p.trp).max(frames(p.trc));
120    Schedule {
121        rcd,
122        ccd,
123        read_done,
124        write_done,
125        rfc: frames(p.trfc),
126        // Refresh a little before the deadline: a line transaction may be under way when it falls due.
127        refi: p.trefi / FRAME_PS - 2 * write_done,
128        power_up: frames(p.power_up),
129        cke_settle: frames(p.cke_settle),
130        mrd: (p.tmrd_ck + 1) / 2,
131        dllk: (p.tdllk_ck + 1) / 2,
132    }
133}
134
135/// The mode register: BL8, sequential, CAS latency, write recovery, and whether the DLL resets.
136pub fn mr(p: &Part, dll_reset: bool) -> u16 {
137    let bl8 = 0b011;
138    let cl = (p.cl as u16 & 0b111) << 4;
139    let wr = ((cks(p.twr) as u16 - 1) & 0b111) << 9;
140    bl8 | cl | wr | if dll_reset { 1 << 8 } else { 0 }
141}
142
143/// Extended mode register 1: DLL on, full drive, no on-die termination, no additive latency, differential DQS; the
144/// off-chip driver calibration as given (0b111 default, 0 exit).
145pub fn emr1(ocd: u16) -> u16 {
146    (ocd & 0b111) << 7
147}
148
149/// The JEDEC power-up sequence in frames.
150pub fn init_sequence(p: &Part) -> Vec<InitStep> {
151    use InitStep::*;
152    let s = schedule(p);
153    let rp = frames(p.trp);
154    vec![
155        CkeLowWait { cycles: s.power_up },
156        Wait { cycles: s.cke_settle },
157        PrechargeAll,
158        Wait { cycles: rp },
159        LoadMode { reg: ModeReg::Emr2, a: 0 },
160        Wait { cycles: s.mrd },
161        LoadMode { reg: ModeReg::Emr3, a: 0 },
162        Wait { cycles: s.mrd },
163        LoadMode { reg: ModeReg::Emr1, a: emr1(0) },
164        Wait { cycles: s.mrd },
165        LoadMode { reg: ModeReg::Mr, a: mr(p, true) },
166        Wait { cycles: s.mrd },
167        PrechargeAll,
168        Wait { cycles: rp },
169        Refresh,
170        Wait { cycles: s.rfc },
171        Refresh,
172        Wait { cycles: s.rfc },
173        LoadMode { reg: ModeReg::Mr, a: mr(p, false) },
174        Wait { cycles: s.dllk },
175        LoadMode { reg: ModeReg::Emr1, a: emr1(0b111) },
176        Wait { cycles: s.mrd },
177        LoadMode { reg: ModeReg::Emr1, a: emr1(0) },
178        Wait { cycles: s.mrd },
179    ]
180}
181
182/// An init step as the controller's ROM word: `{cke, cs_n, ras_n, cas_n, we_n, ba[2:0], a[12:0], frames[23:0]}`
183/// (45 bits), the command's pins from the JEDEC truth table [`ddr2_pins`].
184pub fn init_word(step: &InitStep) -> u64 {
185    let (cke, cmd, ba, a, wait) = match *step {
186        InitStep::CkeLowWait { cycles } => (0u64, Ddr2Cmd::Deselect, 0u64, 0u64, cycles),
187        InitStep::Wait { cycles } => (1, Ddr2Cmd::Nop, 0, 0, cycles),
188        InitStep::PrechargeAll => (1, Ddr2Cmd::Precharge, 0, 1 << 10, 0),
189        InitStep::Refresh => (1, Ddr2Cmd::Refresh, 0, 0, 0),
190        InitStep::LoadMode { reg, a } => (1, Ddr2Cmd::LoadMode, reg.ba() as u64, a as u64, 0),
191    };
192    assert!(wait < 1 << 24, "an init wait of {wait} frames does not fit the ROM word");
193    let (cs, ras, cas, we) = ddr2_pins(cmd);
194    (cke << 44)
195        | ((cs as u64) << 43)
196        | ((ras as u64) << 42)
197        | ((cas as u64) << 41)
198        | ((we as u64) << 40)
199        | (ba << 37)
200        | (a << 24)
201        | wait
202}
203
204/// Where a byte of the RAM lives: (row, bank, column). The bank bits sit just above a row's 2 KB, so consecutive
205/// rows of the address space rotate through the banks.
206pub fn locate(offset: u64) -> (u64, u64, u64) {
207    ((offset >> 14) & 0x1fff, (offset >> 11) & 0b111, (offset >> 1) & 0x3ff)
208}
209
210/// The line the controller writes and reads back to calibrate its read capture: thirty-two beats per byte lane, each
211/// byte distinct within its lane and from every other's complement, so the first beat (or its complement) is found
212/// at exactly one place in the captured stream and the rest confirm it.
213pub fn calibration_line() -> [u64; 8] {
214    let mut words = [0u64; 8];
215    let mut x: u32 = 0x9e37_79b9;
216    let mut used = [[false; 256]; 2];
217    for beat in 0..32 {
218        for lane in 0..2 {
219            let byte = loop {
220                x ^= x << 13;
221                x ^= x >> 17;
222                x ^= x << 5;
223                let b = (x >> 11) as u8;
224                if !used[lane][b as usize] && !used[lane][!b as usize] {
225                    used[lane][b as usize] = true;
226                    break b;
227                }
228            };
229            words[beat / 4] |= (byte as u64) << (16 * (beat % 4) + 8 * lane);
230        }
231    }
232    words
233}
234
235/// The parameters `hart_ddr2.v` includes (`hart_ddr2_params.vh`): the schedule in frames, the power-up ROM, the
236/// calibration line.
237pub fn verilog_params(p: &Part) -> String {
238    let s = schedule(p);
239    let seq = init_sequence(p);
240    let mut v = String::from("// Generated from codegen_sva::hart::ddr2. Do not edit.\n`ifndef HART_DDR2_PARAMS_VH\n`define HART_DDR2_PARAMS_VH\n");
241    for (name, value) in [
242        ("RCD", s.rcd),
243        ("CCD", s.ccd),
244        ("READ_DONE", s.read_done),
245        ("WRITE_DONE", s.write_done),
246        ("RFC", s.rfc),
247        ("REFI", s.refi),
248        ("INIT_STEPS", seq.len() as u64),
249    ] {
250        v.push_str(&format!("`define DDR2_{name} {value}\n"));
251    }
252    v.push_str("`define DDR2_INIT_ROM(rom) \\\n");
253    for (i, step) in seq.iter().enumerate() {
254        v.push_str(&format!("  rom[{i}] = 45'h{:012x}; \\\n", init_word(step)));
255    }
256    v.push_str("  ;\n`define DDR2_CAL_LINE(line) \\\n");
257    for (i, w) in calibration_line().iter().enumerate() {
258        v.push_str(&format!("  line[{i}] = 64'h{w:016x}; \\\n"));
259    }
260    v.push_str("  ;\n`endif\n");
261    v
262}