1use super::ddr2_phy::{ddr2_pins, Ddr2Cmd};
19use super::dram::DramTimings;
20use std::fmt;
21
22pub const REFRESH_POSTPONE_MAX: u64 = 8;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct Ddr2Grade {
29 pub part: &'static str,
30 pub banks: u32,
31 pub row_bits: u32,
32 pub col_bits: u32,
33 pub dq_bits: u32,
34 pub cl_floors: &'static [(u64, u64)],
36 pub tck_max_ps: u64,
37 pub trcd_ps: u64,
38 pub trp_ps: u64,
39 pub tras_ps: u64,
40 pub tras_max_ps: u64,
41 pub trc_ps: u64,
42 pub trrd_ps: u64,
43 pub tfaw_ps: u64,
44 pub twr_ps: u64,
45 pub twtr_ps: u64,
46 pub trtp_ps: u64,
47 pub trfc_ps: u64,
48 pub trefi_ps: u64,
49 pub tccd_ck: u64,
50}
51
52impl Ddr2Grade {
53 pub const MT47H64M16_25: Ddr2Grade = Ddr2Grade {
56 part: "MT47H64M16-25",
57 cl_floors: &[(5000, 3), (3750, 4), (3000, 5), (2500, 6)],
58 trcd_ps: 15_000,
59 trp_ps: 15_000,
60 ..Self::MT47H64M16_25E
61 };
62
63 pub const MT47H64M16_25E: Ddr2Grade = Ddr2Grade {
65 part: "MT47H64M16-25E",
66 banks: 8,
67 row_bits: 13,
68 col_bits: 10,
69 dq_bits: 16,
70 cl_floors: &[(5000, 3), (3750, 4), (2500, 5)],
71 tck_max_ps: 8_000,
72 trcd_ps: 12_500,
73 trp_ps: 12_500,
74 tras_ps: 40_000,
75 tras_max_ps: 70_000_000,
76 trc_ps: 55_000,
77 trrd_ps: 10_000,
78 tfaw_ps: 45_000,
79 twr_ps: 15_000,
80 twtr_ps: 7_500,
81 trtp_ps: 7_500,
82 trfc_ps: 127_500,
83 trefi_ps: 7_800_000,
84 tccd_ck: 2,
85 };
86
87 pub fn at(&self, tck_ps: u64, bl: u64) -> Result<Ddr2Timing, String> {
89 if bl != 4 && bl != 8 {
90 return Err(format!("{}: a DDR2 burst is 4 or 8 beats, not {bl}", self.part));
91 }
92 if tck_ps == 0 || tck_ps > self.tck_max_ps {
93 return Err(format!("{}: tCK {tck_ps} ps is outside the part's range (at most {} ps)", self.part, self.tck_max_ps));
94 }
95 let cl = self
96 .cl_floors
97 .iter()
98 .find(|&&(floor, _)| floor <= tck_ps)
99 .map(|&(_, cl)| cl)
100 .ok_or_else(|| format!("{}: tCK {tck_ps} ps is faster than any CAS latency it supports", self.part))?;
101 let up = |ps: u64| ps.div_ceil(tck_ps);
102 let up2 = |ps: u64| up(ps).max(2);
103 Ok(Ddr2Timing {
104 grade: *self,
105 tck_ps,
106 bl,
107 cl,
108 wl: cl - 1,
109 trcd: up(self.trcd_ps),
110 trp: up(self.trp_ps),
111 trpa: up(self.trp_ps + tck_ps),
112 tras: up(self.tras_ps),
113 tras_max: self.tras_max_ps / tck_ps,
114 trc: up(self.trc_ps),
115 trrd: up2(self.trrd_ps),
116 tfaw: up(self.tfaw_ps),
117 twr: up2(self.twr_ps),
118 twtr: up2(self.twtr_ps),
119 trtp: up2(self.trtp_ps),
120 trfc: up(self.trfc_ps),
121 trefi: self.trefi_ps / tck_ps,
122 tccd: self.tccd_ck,
123 })
124 }
125}
126
127#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub struct Ddr2Timing {
130 pub grade: Ddr2Grade,
131 pub tck_ps: u64,
132 pub bl: u64,
133 pub cl: u64,
134 pub wl: u64,
135 pub trcd: u64,
136 pub trp: u64,
137 pub trpa: u64,
138 pub tras: u64,
139 pub tras_max: u64,
140 pub trc: u64,
141 pub trrd: u64,
142 pub tfaw: u64,
143 pub twr: u64,
144 pub twtr: u64,
145 pub trtp: u64,
146 pub trfc: u64,
147 pub trefi: u64,
148 pub tccd: u64,
149}
150
151impl Ddr2Timing {
152 pub fn read_to_precharge(&self) -> u64 {
154 self.bl / 2 - 2 + self.trtp.max(2)
155 }
156
157 pub fn write_to_precharge(&self) -> u64 {
159 self.wl + self.bl / 2 + self.twr
160 }
161
162 pub fn write_to_read(&self) -> u64 {
164 self.wl + self.bl / 2 + self.twtr
165 }
166
167 pub fn read_to_write(&self) -> u64 {
169 self.bl / 2 + 2
170 }
171
172 pub fn cas_to_cas(&self) -> u64 {
174 self.tccd.max(self.bl / 2)
175 }
176
177 pub fn dram_timings(&self) -> DramTimings {
183 let bytes = u64::from(self.grade.dq_bits / 8);
184 DramTimings {
185 banks: self.grade.banks,
186 row_bytes: (1u64 << self.grade.col_bits) * bytes,
187 burst_bytes: self.bl * bytes,
188 data_cycles: self.bl / 2,
189 trcd: self.trcd,
190 trp: self.trp,
191 tras: self.tras,
192 trc: self.trc,
193 trrd: self.trrd,
194 tfaw: self.tfaw,
195 tccd: self.tccd,
196 trfc_pb: self.trfc,
197 trefi_pb: self.trefi,
198 twr: self.wl + self.twr,
199 tturn: (self.wl + self.twtr).max(2),
200 hot: false,
201 }
202 }
203}
204
205pub fn command_of_pins(cs_n: u8, ras_n: u8, cas_n: u8, we_n: u8) -> Option<Ddr2Cmd> {
208 use Ddr2Cmd::*;
209 if [cs_n, ras_n, cas_n, we_n].iter().any(|&p| p > 1) {
210 return None;
211 }
212 if cs_n == 1 {
213 return Some(Deselect);
214 }
215 [Nop, Active, Read, Write, Precharge, Refresh, LoadMode].into_iter().find(|&c| ddr2_pins(c) == (cs_n, ras_n, cas_n, we_n))
216}
217
218#[derive(Clone, Copy, Debug, PartialEq, Eq)]
221pub struct BusCmd {
222 pub cycle: u64,
223 pub cmd: Ddr2Cmd,
224 pub ba: u8,
225 pub a: u16,
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
230pub struct CasEvent {
231 pub cycle: u64,
232 pub write: bool,
233 pub bank: u8,
234 pub row: u16,
235 pub col: u16,
236 pub data_at: u64,
237}
238
239#[derive(Clone, Debug, Default, PartialEq, Eq)]
242pub struct BusFacts {
243 pub activates: u64,
244 pub reads: u64,
245 pub writes: u64,
246 pub precharges: u64,
247 pub refreshes: u64,
248 pub first_cycle: u64,
249 pub last_cycle: u64,
250 pub end: u64,
251 pub data_cycles: u64,
252 pub cas: Vec<CasEvent>,
253}
254
255#[derive(Clone, Debug, PartialEq, Eq)]
258pub struct Violation {
259 pub index: usize,
260 pub cycle: u64,
261 pub rule: &'static str,
262 pub detail: String,
263}
264
265impl fmt::Display for Violation {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 write!(f, "command {} at cycle {} breaks {}: {}", self.index, self.cycle, self.rule, self.detail)
268 }
269}
270
271#[derive(Clone, Copy)]
272struct Bank {
273 open: Option<u16>,
274 act_at: u64,
275 last_act: Option<u64>,
276 ready_at: u64,
277 ready_rule: &'static str,
278 last_rd: Option<u64>,
279 last_wr: Option<u64>,
280}
281
282pub fn check_bus(t: &Ddr2Timing, trace: &[BusCmd]) -> Result<BusFacts, Violation> {
285 let nb = t.grade.banks as usize;
286 let mut banks = vec![
287 Bank { open: None, act_at: 0, last_act: None, ready_at: 0, ready_rule: "tRP", last_rd: None, last_wr: None };
288 nb
289 ];
290 let mut acts: Vec<u64> = Vec::new();
291 let (mut last_cas, mut last_rd, mut last_wr): (Option<u64>, Option<u64>, Option<u64>) = (None, None, None);
292 let mut refreshes = 0u64;
293 let mut prev: Option<u64> = None;
294 let mut facts = BusFacts::default();
295 let mut busy = 0u64;
296 let owed = |at: u64| (at / t.trefi).saturating_sub(REFRESH_POSTPONE_MAX);
297
298 for (index, c) in trace.iter().enumerate() {
299 if matches!(c.cmd, Ddr2Cmd::Nop | Ddr2Cmd::Deselect) {
300 continue;
301 }
302 let fail = |rule: &'static str, detail: String| Err(Violation { index, cycle: c.cycle, rule, detail });
303 if let Some(p) = prev {
304 if c.cycle <= p {
305 return fail("one command per cycle", format!("the command before it was at cycle {p}"));
306 }
307 } else {
308 facts.first_cycle = c.cycle;
309 }
310 prev = Some(c.cycle);
311 let paid = refreshes + u64::from(c.cmd == Ddr2Cmd::Refresh);
312 if owed(c.cycle) > paid {
313 return fail("refresh interval", format!("{paid} REFRESH by cycle {}, {} owed", c.cycle, owed(c.cycle)));
314 }
315 for (b, bank) in banks.iter().enumerate() {
316 if bank.open.is_some() && c.cycle - bank.act_at > t.tras_max {
317 return fail("tRAS max", format!("bank {b} open since cycle {} (at most {})", bank.act_at, t.tras_max));
318 }
319 }
320 let ba = usize::from(c.ba);
321 let banked = matches!(c.cmd, Ddr2Cmd::Active | Ddr2Cmd::Read | Ddr2Cmd::Write)
322 || (c.cmd == Ddr2Cmd::Precharge && c.a & (1 << 10) == 0);
323 if banked && ba >= nb {
324 return fail("address range", format!("bank {ba} of {nb}"));
325 }
326 match c.cmd {
327 Ddr2Cmd::Nop | Ddr2Cmd::Deselect => unreachable!("passed over above"),
328 Ddr2Cmd::LoadMode => return fail("mode register", "a mode-register write inside traffic".into()),
329 Ddr2Cmd::Active => {
330 if u32::from(c.a) >= 1 << t.grade.row_bits {
331 return fail("address range", format!("row {} of {}", c.a, 1u32 << t.grade.row_bits));
332 }
333 let bank = banks[ba];
334 if let Some(row) = bank.open {
335 return fail("ACT to an open bank", format!("bank {ba} has row {row} open since cycle {}", bank.act_at));
336 }
337 if c.cycle < bank.ready_at {
338 return fail(bank.ready_rule, format!("bank {ba} is ready at cycle {}", bank.ready_at));
339 }
340 if let Some(la) = bank.last_act {
341 if c.cycle < la + t.trc {
342 return fail("tRC", format!("bank {ba} was activated at cycle {la}"));
343 }
344 }
345 if let Some(&la) = acts.last() {
346 if c.cycle < la + t.trrd {
347 return fail("tRRD", format!("an ACT at cycle {la}"));
348 }
349 }
350 if acts.len() >= 4 {
351 let fourth = acts[acts.len() - 4];
352 if c.cycle < fourth + t.tfaw {
353 return fail("tFAW", format!("four ACTs since cycle {fourth}"));
354 }
355 }
356 acts.push(c.cycle);
357 if acts.len() > 4 {
358 acts.remove(0);
359 }
360 banks[ba] = Bank { open: Some(c.a), act_at: c.cycle, last_act: Some(c.cycle), last_rd: None, last_wr: None, ..bank };
361 facts.activates += 1;
362 }
363 Ddr2Cmd::Read | Ddr2Cmd::Write => {
364 let write = c.cmd == Ddr2Cmd::Write;
365 let auto = c.a & (1 << 10) != 0;
366 let col_mask = (1u16 << t.grade.col_bits) - 1;
367 if c.a & !(col_mask | (1 << 10)) != 0 {
368 return fail("address range", format!("column address {:#x} past {} column bits", c.a, t.grade.col_bits));
369 }
370 let bank = banks[ba];
371 let Some(row) = bank.open else {
372 return fail("CAS to an idle bank", format!("bank {ba} has no open row"));
373 };
374 if c.cycle < bank.act_at + t.trcd {
375 return fail("tRCD", format!("bank {ba} was activated at cycle {}", bank.act_at));
376 }
377 if let Some(l) = last_cas {
378 if c.cycle < l + t.cas_to_cas() {
379 return fail("CAS to CAS", format!("a column command at cycle {l}"));
380 }
381 }
382 if !write {
383 if let Some(w) = last_wr {
384 if c.cycle < w + t.write_to_read() {
385 return fail("WRITE to READ", format!("a WRITE at cycle {w}"));
386 }
387 }
388 } else if let Some(r) = last_rd {
389 if c.cycle < r + t.read_to_write() {
390 return fail("READ to WRITE", format!("a READ at cycle {r}"));
391 }
392 }
393 last_cas = Some(c.cycle);
394 let data_at = c.cycle + if write { t.wl } else { t.cl };
395 if write {
396 last_wr = Some(c.cycle);
397 banks[ba].last_wr = Some(c.cycle);
398 facts.writes += 1;
399 } else {
400 last_rd = Some(c.cycle);
401 banks[ba].last_rd = Some(c.cycle);
402 facts.reads += 1;
403 }
404 facts.data_cycles += t.bl / 2;
405 busy = busy.max(data_at + t.bl / 2 - 1);
406 facts.cas.push(CasEvent { cycle: c.cycle, write, bank: c.ba, row, col: c.a & col_mask, data_at });
407 if auto {
408 let spacing = if write { t.write_to_precharge() } else { t.read_to_precharge() };
409 let start = (c.cycle + spacing).max(bank.act_at + t.tras);
410 let b = &mut banks[ba];
411 b.open = None;
412 if start + t.trp > b.ready_at {
413 b.ready_at = start + t.trp;
414 b.ready_rule = "tRP";
415 }
416 }
417 }
418 Ddr2Cmd::Precharge => {
419 let all = c.a & (1 << 10) != 0;
420 let (period, rule) = if all { (t.trpa, "tRPA") } else { (t.trp, "tRP") };
421 for b in 0..nb {
422 if !all && b != ba {
423 continue;
424 }
425 let bank = banks[b];
426 if bank.open.is_some() {
427 if c.cycle < bank.act_at + t.tras {
428 return fail("tRAS", format!("bank {b} was activated at cycle {}", bank.act_at));
429 }
430 if let Some(r) = bank.last_rd {
431 if c.cycle < r + t.read_to_precharge() {
432 return fail("READ to PRECHARGE", format!("bank {b} read at cycle {r}"));
433 }
434 }
435 if let Some(w) = bank.last_wr {
436 if c.cycle < w + t.write_to_precharge() {
437 return fail("WRITE to PRECHARGE", format!("bank {b} written at cycle {w}"));
438 }
439 }
440 } else if !all {
441 continue;
442 }
443 let bank = &mut banks[b];
444 bank.open = None;
445 if c.cycle + period > bank.ready_at {
446 bank.ready_at = c.cycle + period;
447 bank.ready_rule = rule;
448 }
449 }
450 facts.precharges += 1;
451 }
452 Ddr2Cmd::Refresh => {
453 for (b, bank) in banks.iter().enumerate() {
454 if let Some(row) = bank.open {
455 return fail("REFRESH with an open bank", format!("bank {b} has row {row} open"));
456 }
457 if c.cycle < bank.ready_at {
458 return fail(bank.ready_rule, format!("bank {b} is ready at cycle {}", bank.ready_at));
459 }
460 }
461 for bank in banks.iter_mut() {
462 bank.ready_at = c.cycle + t.trfc;
463 bank.ready_rule = "tRFC";
464 }
465 refreshes += 1;
466 facts.refreshes += 1;
467 }
468 }
469 facts.last_cycle = c.cycle;
470 busy = busy.max(c.cycle);
471 }
472 if prev.is_none() {
473 return Ok(facts);
474 }
475 let at_end = |rule: &'static str, detail: String| Err(Violation { index: trace.len(), cycle: busy, rule, detail });
476 if owed(busy) > refreshes {
477 return at_end("refresh interval", format!("{refreshes} REFRESH by the trace's last busy cycle {busy}, {} owed", owed(busy)));
478 }
479 for (b, bank) in banks.iter().enumerate() {
480 if bank.open.is_some() && busy - bank.act_at > t.tras_max {
481 return at_end("tRAS max", format!("bank {b} open since cycle {} at the trace's end", bank.act_at));
482 }
483 }
484 facts.end = busy + 1;
485 Ok(facts)
486}
487
488#[cfg(test)]
489#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/ddr2_jedec/tests.rs"]
490mod tests;