1use super::ddr2_jedec::Violation;
32use super::dram::StreamDesc;
33use serde::Serialize;
34use std::collections::{BTreeMap, VecDeque};
35
36pub const REFRESH_POSTPONE_MAX: i64 = 8;
38pub const REFRESH_PULL_IN_MAX: i64 = 8;
39pub const BL: u64 = 8;
41pub const LOOKAHEAD: usize = 16;
44pub const D5005_EMIF_IP: &str = "OFS ofs-d5005 2024.1-1 ipss/mem/ip/emif_ddr4_no_ecc.ip";
46
47#[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 pub dq_bits: u32,
58 pub rate: u64,
60 pub mem_mhz: u64,
61 pub cl: u64,
62 pub cwl: u64,
63 pub pl: u64,
65 pub trcd_ps: u64,
66 pub trp_ps: u64,
67 pub tras_ps: u64,
68 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 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 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#[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 pub fn lines(&self) -> u64 {
180 u64::from(self.banks()) << (self.grade.row_bits + self.grade.col_bits - BL.trailing_zeros())
181 }
182
183 pub fn burst_bits(&self) -> u64 {
185 BL * u64::from(self.grade.dq_bits)
186 }
187
188 pub fn burst_ck(&self) -> u64 {
190 BL / 2
191 }
192
193 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 pub fn read_to_write(&self) -> u64 {
200 self.rl + BL / 2 + 2 - self.wl
201 }
202
203 pub fn write_to_precharge(&self) -> u64 {
205 self.wl + BL / 2 + self.twr
206 }
207
208 pub fn controller_ck(&self, mem_ck: u64) -> u64 {
210 mem_ck.div_ceil(self.grade.rate)
211 }
212
213 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#[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
228pub 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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258pub struct Ddr4Timed {
259 pub at: u64,
260 pub cmd: Ddr4Cmd,
261}
262
263#[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 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 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
303pub 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 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 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 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 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
550pub 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
559pub 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 last_use: u64,
582}
583
584pub struct Scheduler {
586 t: Ddr4Timing,
587 lookahead: usize,
588 pending: BTreeMap<u64, Ddr4Cmd>,
590 released: u64,
594 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 fn frontier(&self) -> u64 {
635 self.last_cas.map_or(0, |c| c + 1)
636 }
637
638 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 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 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 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 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 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 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
816pub 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
830pub 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
855pub const LOGGED_KINDS: [&str; 6] = ["ACT", "RD", "WR", "PRE", "PREA", "REF"];
857
858pub 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}