1use crate::sched::SchedTime;
21use crate::sched::nba::NbaLog;
22use crate::state::{
23 ChangeDelta, EdgeKind, FourStateValue, SignalAccess, StateCellId, ValueChangeSink,
24};
25use vj_abi::{Bit4, Region};
26
27fn lsb_bit(value: &FourStateValue) -> Bit4 {
34 match value {
35 FourStateValue::Narrow(word) => {
36 let val = (word & 1) != 0;
37 let unk = ((word >> 64) & 1) != 0;
38 Bit4::from_val_unk(val, unk)
39 }
40 FourStateValue::Wide { aval, bval } => {
41 let a = aval.first().copied().unwrap_or(0) & 1 != 0;
42 let b = bval.first().copied().unwrap_or(0) & 1 != 0;
43 Bit4::from_aval_bval(a, b)
44 }
45 }
46}
47
48pub fn infer_edge(old: &FourStateValue, new: &FourStateValue) -> EdgeKind {
71 edge_between(lsb_bit(old), lsb_bit(new))
72}
73
74pub fn edge_between(old: Bit4, new: Bit4) -> EdgeKind {
77 use Bit4::*;
78 match (old, new) {
79 (Zero, Zero) => EdgeKind::None,
80 (Zero, One) => EdgeKind::Posedge,
81 (Zero, X) => EdgeKind::Posedge,
82 (Zero, Z) => EdgeKind::Posedge,
83 (One, Zero) => EdgeKind::Negedge,
84 (One, One) => EdgeKind::None,
85 (One, X) => EdgeKind::Negedge,
86 (One, Z) => EdgeKind::Negedge,
87 (X, Zero) => EdgeKind::Negedge,
88 (X, One) => EdgeKind::Posedge,
89 (X, X) => EdgeKind::None,
90 (X, Z) => EdgeKind::None,
91 (Z, Zero) => EdgeKind::Negedge,
92 (Z, One) => EdgeKind::Posedge,
93 (Z, X) => EdgeKind::None,
94 (Z, Z) => EdgeKind::None,
95 }
96}
97
98pub fn commit_nba(
103 log: &mut NbaLog,
104 arena: &mut dyn SignalAccess,
105 at: SchedTime,
106 sink: &mut dyn ValueChangeSink,
107) -> usize {
108 let entries = log.take();
109 let mut masked = log.take_masked();
110 if masked.iter().any(|m| m.obj.is_some()) {
113 let (mut objs, rest): (Vec<_>, Vec<_>) = masked.into_iter().partition(|m| m.obj.is_some());
114 masked = rest;
115 objs.sort_unstable_by_key(|m| m.order);
116 for m in objs {
117 if let Some(w) = m.obj {
118 if w.class_event {
119 super::class_event::trigger(w.h, at.ticks, &mut |req| {
122 super::class_event::defer_wake(req);
123 });
124 } else {
125 w.commit();
126 }
127 }
128 }
129 }
130 if !masked.is_empty() {
131 return commit_nba_with_partial_writes(entries, masked, arena, at, sink);
132 }
133
134 let mut entries = entries;
147 entries.sort_by_key(|e| (e.cell, e.order));
148
149 let mut changed_count = 0usize;
150 let mut last_changed: Option<StateCellId> = None;
151 for entry in entries {
152 let cell = entry.cell;
153 let old = arena.read(cell);
154 let new_for_edge = entry.value.clone();
155 if arena.write(cell, entry.value) {
156 if sink.wants_delta(cell) {
157 sink.note_delta(cell, ChangeDelta::between(&old, &new_for_edge));
158 }
159 notify_nba_change(sink, cell, infer_edge(&old, &new_for_edge), at);
160 if last_changed != Some(cell) {
161 changed_count += 1;
162 last_changed = Some(cell);
163 }
164 }
165 for (cell, edge) in alias_edges(arena) {
166 notify_nba_change(sink, cell, edge, at);
167 }
168 }
169 changed_count
170}
171
172fn alias_edges(arena: &mut dyn SignalAccess) -> Vec<(StateCellId, EdgeKind)> {
175 arena
176 .take_alias_changes()
177 .into_iter()
178 .map(|c| {
179 let edge = if c.no_edge {
180 EdgeKind::None
181 } else {
182 infer_edge(&c.old, &c.new)
183 };
184 (c.cell, edge)
185 })
186 .collect()
187}
188
189fn notify_nba_change(
191 sink: &mut dyn ValueChangeSink,
192 cell: StateCellId,
193 edge: EdgeKind,
194 at: SchedTime,
195) {
196 sink.notify_change(cell, edge, at.to_abi_display(0), Region::Nba);
197}
198
199fn merge_masked(cur: &FourStateValue, new: &FourStateValue, mask: &[u64]) -> FourStateValue {
203 let pick = |c: u64, n: u64, m: u64| (c & !m) | (n & m);
204 match (cur, new) {
205 (FourStateValue::Narrow(c), FourStateValue::Narrow(n)) => {
206 let m = mask.first().copied().unwrap_or(0);
207 let val = pick(*c as u64, *n as u64, m);
208 let unk = pick((*c >> 64) as u64, (*n >> 64) as u64, m);
209 FourStateValue::Narrow(u128::from(val) | (u128::from(unk) << 64))
210 }
211 (
212 FourStateValue::Wide { aval: ca, bval: cb },
213 FourStateValue::Wide { aval: na, bval: nb },
214 ) => {
215 let words = |c: &[u64], n: &[u64]| -> Vec<u64> {
216 c.iter()
217 .enumerate()
218 .map(|(i, &cw)| {
219 let nw = n.get(i).copied().unwrap_or(0);
220 pick(cw, nw, mask.get(i).copied().unwrap_or(0))
221 })
222 .collect()
223 };
224 FourStateValue::Wide {
225 aval: words(ca, na),
226 bval: words(cb, nb),
227 }
228 }
229 (FourStateValue::Wide { aval, .. }, FourStateValue::Narrow(n)) => {
233 let (val, unk) = (*n as u64, (*n >> 64) as u64);
234 let mut na = vec![0u64; aval.len()];
235 let mut nb = vec![0u64; aval.len()];
236 if let (Some(a), Some(b)) = (na.first_mut(), nb.first_mut()) {
237 (*a, *b) = vj_abi::bit4::narrow_word_to_wide(val, unk);
238 }
239 merge_masked(cur, &FourStateValue::Wide { aval: na, bval: nb }, mask)
240 }
241 (FourStateValue::Narrow(_), FourStateValue::Wide { aval, bval }) => {
242 let a = aval.first().copied().unwrap_or(0);
243 let b = bval.first().copied().unwrap_or(0);
244 let narrow = FourStateValue::Narrow(u128::from(a ^ b) | (u128::from(b) << 64));
245 merge_masked(cur, &narrow, mask)
246 }
247 }
248}
249
250fn commit_nba_with_partial_writes(
257 entries: Vec<super::nba::NbaUpdate>,
258 masked: Vec<super::nba::MaskedNbaUpdate>,
259 arena: &mut dyn SignalAccess,
260 at: SchedTime,
261 sink: &mut dyn ValueChangeSink,
262) -> usize {
263 enum Op {
264 Whole(FourStateValue),
265 Part(FourStateValue, Vec<u64>, Option<u32>),
268 Pulse,
271 Coll(Vec<u64>, super::nba::CollWrite),
274 }
275 type Target = (StateCellId, Option<u64>);
277 let mut items: Vec<(Target, u32, Op)> = Vec::with_capacity(entries.len() + masked.len());
281 for e in entries {
282 items.push(((e.cell, None), e.order, Op::Whole(e.value)));
283 }
284 for m in masked {
285 let op = if m.pulse {
286 Op::Pulse
287 } else if let Some(c) = m.coll {
288 Op::Coll(m.mask, c)
289 } else {
290 Op::Part(m.value, m.mask, m.window)
291 };
292 items.push(((m.cell, m.row), m.order, op));
293 }
294 items.sort_unstable_by_key(|(target, order, _)| (*target, *order));
295
296 struct Update {
301 cell: StateCellId,
302 edge: EdgeKind,
303 delta: Option<ChangeDelta>,
304 }
305 let mut updates: Vec<Update> = Vec::new();
306 let mut changed_cells = 0usize;
307 let mut alias_notes: Vec<(StateCellId, EdgeKind)> = Vec::new();
308 let mut items = items.into_iter().peekable();
309 while let Some(((cell, row), _, first)) = items.next() {
310 let mut group = vec![first];
311 while let Some((_, _, op)) = items.next_if(|(t, _, _)| *t == (cell, row)) {
312 group.push(op);
313 }
314 let mark = updates.len();
315 let wants = sink.wants_delta(cell);
316 if row.is_none() && group.iter().all(|op| matches!(op, Op::Part(_, _, Some(_)))) {
320 let mut spans: Vec<(usize, usize)> = group
324 .iter()
325 .filter_map(|op| match op {
326 Op::Part(FourStateValue::Wide { aval, .. }, _, Some(w)) => {
327 Some((*w as usize, *w as usize + aval.len()))
328 }
329 _ => None,
330 })
331 .collect();
332 spans.sort_unstable();
333 let mut segments: Vec<(usize, usize)> = Vec::new();
334 for (l, h) in spans {
335 match segments.last_mut() {
336 Some(seg) if l <= seg.1 => seg.1 = seg.1.max(h),
337 _ => segments.push((l, h)),
338 }
339 }
340 let mut segs: Vec<(usize, usize, Vec<u64>, Vec<u64>)> = segments
344 .iter()
345 .map(|&(lo, hi)| {
346 let (a, b) = arena.read_wide_window(cell, lo, hi - lo);
347 (lo, hi, a, b)
348 })
349 .collect();
350 for op in &group {
351 let Op::Part(FourStateValue::Wide { aval, bval }, mask, Some(w)) = op else {
352 continue;
353 };
354 let w = *w as usize;
355 let Some((lo, _, a, b)) = segs
356 .iter_mut()
357 .find(|(lo, hi, _, _)| w >= *lo && w + mask.len() <= *hi)
358 else {
359 continue;
360 };
361 let at = w - *lo;
362 let n = mask.len();
363 let (old_a, old_b) = (a[at..at + n].to_vec(), b[at..at + n].to_vec());
364 for (k, &m) in mask.iter().enumerate() {
365 a[at + k] = (a[at + k] & !m) | (aval[k] & m);
366 b[at + k] = (b[at + k] & !m) | (bval[k] & m);
367 }
368 if old_a[..] == a[at..at + n] && old_b[..] == b[at..at + n] {
369 continue;
370 }
371 let edge = if w == 0 {
374 edge_between(
375 Bit4::from_aval_bval(old_a[0] & 1 != 0, old_b[0] & 1 != 0),
376 Bit4::from_aval_bval(a[0] & 1 != 0, b[0] & 1 != 0),
377 )
378 } else {
379 EdgeKind::None
380 };
381 let delta = wants.then(|| ChangeDelta {
382 lo_word: w,
383 old_a,
384 old_b,
385 new_a: a[at..at + n].to_vec(),
386 new_b: b[at..at + n].to_vec(),
387 });
388 updates.push(Update { cell, edge, delta });
389 }
390 let mut changed = false;
391 let mut forced = false;
392 for (lo, hi, a, b) in &segs {
393 if arena.write_wide_window(cell, *lo, a, b) {
394 changed = true;
395 } else if updates.len() > mark {
396 let (ra, rb) = arena.read_wide_window(cell, *lo, hi - lo);
399 forced |= ra != *a || rb != *b;
400 }
401 }
402 if forced {
403 updates.truncate(mark);
404 }
405 if changed || updates.len() > mark {
406 changed_cells += 1;
407 }
408 continue;
409 }
410 let old = match row {
411 Some(r) => arena.read_row(cell, r),
412 None => arena.read(cell),
413 };
414 let apply = |cur: &FourStateValue, op: Op| match op {
415 Op::Whole(v) => v,
416 Op::Pulse => triggered_event_value(cur, at),
417 Op::Coll(elem, c) => {
422 let h = match cur {
423 FourStateValue::Narrow(v) => *v as u64,
424 FourStateValue::Wide { aval, .. } => aval.first().copied().unwrap_or(0),
425 };
426 let new = match c.key {
427 Some(k) => crate::collections::store(&c.shape, h, k, &elem),
428 None => crate::collections::set(&c.shape, h, c.index, &elem, c.queue),
429 };
430 match cur {
431 FourStateValue::Narrow(_) => FourStateValue::Narrow(u128::from(new)),
432 FourStateValue::Wide { aval, bval } => {
433 let (mut a, b) = (aval.clone(), vec![0; bval.len()]);
434 a[0] = new;
435 FourStateValue::Wide { aval: a, bval: b }
436 }
437 }
438 }
439 Op::Part(v, mask, None) => merge_masked(cur, &v, &mask),
440 Op::Part(v, mask, Some(w)) => {
441 let (FourStateValue::Wide { aval, bval }, FourStateValue::Wide { aval: ca, .. }) =
444 (&v, cur)
445 else {
446 unreachable!("a windowed NBA targets a wide cell")
447 };
448 let n = ca.len();
449 let at = w as usize;
450 let (mut fa, mut fb, mut fm) = (vec![0u64; n], vec![0u64; n], vec![0u64; n]);
451 fa[at..at + aval.len()].copy_from_slice(aval);
452 fb[at..at + bval.len()].copy_from_slice(bval);
453 fm[at..at + mask.len()].copy_from_slice(&mask);
454 merge_masked(cur, &FourStateValue::Wide { aval: fa, bval: fb }, &fm)
455 }
456 };
457 let mut cur = old.clone();
458 for op in group {
459 let next = apply(&cur, op);
460 if !same_value(&cur, &next) {
461 let (edge, delta) = if row.is_some() {
462 (EdgeKind::None, None)
463 } else {
464 (
465 infer_edge(&cur, &next),
466 wants.then(|| ChangeDelta::between(&cur, &next)),
467 )
468 };
469 updates.push(Update { cell, edge, delta });
470 }
471 cur = next;
472 }
473 let stored = match row {
474 Some(r) => arena.write_row(cell, r, cur.clone()),
475 None => arena.write(cell, cur.clone()),
476 };
477 if updates.len() > mark {
478 if !stored {
479 let now = match row {
481 Some(r) => arena.read_row(cell, r),
482 None => arena.read(cell),
483 };
484 if !same_value(&now, &cur) {
485 updates.truncate(mark);
486 }
487 }
488 if updates.len() > mark {
489 changed_cells += 1;
490 }
491 }
492 alias_notes.extend(alias_edges(arena));
493 }
494 for u in updates {
495 if let Some(d) = u.delta {
496 sink.note_delta(u.cell, d);
497 }
498 notify_nba_change(sink, u.cell, u.edge, at);
499 }
500 for (cell, edge) in alias_notes {
501 notify_nba_change(sink, cell, edge, at);
502 }
503 changed_cells
504}
505
506fn triggered_event_value(cur: &FourStateValue, at: SchedTime) -> FourStateValue {
511 let count = match cur {
512 FourStateValue::Wide { aval, .. } => aval.first().copied().unwrap_or(0),
513 FourStateValue::Narrow(n) => *n as u64,
514 };
515 FourStateValue::Wide {
516 aval: vec![count.wrapping_add(1), at.ticks.wrapping_add(1)],
517 bval: vec![0, 0],
518 }
519}
520
521fn same_value(a: &FourStateValue, b: &FourStateValue) -> bool {
523 match (a, b) {
524 (FourStateValue::Narrow(x), FourStateValue::Narrow(y)) => x == y,
525 (
526 FourStateValue::Wide { aval: a1, bval: b1 },
527 FourStateValue::Wide { aval: a2, bval: b2 },
528 ) => a1 == a2 && b1 == b2,
529 _ => a.planes() == b.planes(),
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::super::nba::NbaLog;
536 use crate::sched::{ProcessId, SchedTime};
537 use crate::state::{
538 EdgeKind, FourStateValue, ListenerRole, SignalAccess, StateCellId, ValueChangeSink,
539 };
540 use std::cell::RefCell;
541 use std::collections::{HashMap, HashSet};
542 use std::rc::Rc;
543 use vj_abi::{Region, SimTime};
544
545 struct FakeArenaState {
560 cells: HashMap<StateCellId, FourStateValue>,
561 notifications: Vec<(StateCellId, EdgeKind, SimTime, Region)>,
562 deltas: Vec<super::ChangeDelta>,
563 }
564
565 #[derive(Clone)]
566 struct FakeArena(Rc<RefCell<FakeArenaState>>);
567
568 struct ArenaHandle(Rc<RefCell<FakeArenaState>>);
569 struct SinkHandle(Rc<RefCell<FakeArenaState>>);
570
571 fn four_state_eq(a: &FourStateValue, b: &FourStateValue) -> bool {
572 match (a, b) {
573 (FourStateValue::Narrow(x), FourStateValue::Narrow(y)) => x == y,
574 (
575 FourStateValue::Wide { aval: a1, bval: b1 },
576 FourStateValue::Wide { aval: a2, bval: b2 },
577 ) => a1 == a2 && b1 == b2,
578 _ => false,
579 }
580 }
581
582 impl FakeArena {
583 fn new() -> Self {
584 Self(Rc::new(RefCell::new(FakeArenaState {
585 cells: HashMap::new(),
586 notifications: Vec::new(),
587 deltas: Vec::new(),
588 })))
589 }
590
591 fn seed(&self, cell: StateCellId, value: FourStateValue) {
592 self.0.borrow_mut().cells.insert(cell, value);
593 }
594
595 fn read_cell(&self, cell: StateCellId) -> FourStateValue {
596 self.0
597 .borrow()
598 .cells
599 .get(&cell)
600 .cloned()
601 .unwrap_or(FourStateValue::Narrow(0))
602 }
603
604 fn notifications(&self) -> Vec<(StateCellId, EdgeKind, SimTime, Region)> {
605 self.0.borrow().notifications.clone()
606 }
607
608 fn arena_handle(&self) -> ArenaHandle {
609 ArenaHandle(self.0.clone())
610 }
611
612 fn sink_handle(&self) -> SinkHandle {
613 SinkHandle(self.0.clone())
614 }
615 }
616
617 impl SignalAccess for ArenaHandle {
618 fn read(&self, cell: StateCellId) -> FourStateValue {
619 self.0
620 .borrow()
621 .cells
622 .get(&cell)
623 .cloned()
624 .unwrap_or(FourStateValue::Narrow(0))
625 }
626
627 fn write(&mut self, cell: StateCellId, value: FourStateValue) -> bool {
628 let mut state = self.0.borrow_mut();
629 let changed = match state.cells.get(&cell) {
630 Some(existing) => !four_state_eq(existing, &value),
631 None => true,
632 };
633 state.cells.insert(cell, value);
634 changed
635 }
636
637 fn subscribe(
638 &mut self,
639 _cell: StateCellId,
640 _role: ListenerRole,
641 _sink: Box<dyn ValueChangeSink + Send>,
642 ) {
643 }
646 }
647
648 impl ValueChangeSink for SinkHandle {
649 fn notify_change(
650 &mut self,
651 cell: StateCellId,
652 edge: EdgeKind,
653 at: SimTime,
654 region: Region,
655 ) {
656 self.0
657 .borrow_mut()
658 .notifications
659 .push((cell, edge, at, region));
660 }
661
662 fn wants_delta(&self, _cell: StateCellId) -> bool {
663 true
664 }
665
666 fn note_delta(&mut self, _cell: StateCellId, delta: super::ChangeDelta) {
667 self.0.borrow_mut().deltas.push(delta);
668 }
669 }
670
671 const PROC_A: ProcessId = 10;
672 const PROC_B: ProcessId = 5;
673
674 #[test]
675 fn two_nba_writes_same_cell_deterministically_commit_the_last_logged_one() {
676 let cell = StateCellId(1);
677
678 let fake1 = FakeArena::new();
684 let mut arena1 = fake1.arena_handle();
685 let mut sink1 = fake1.sink_handle();
686 let mut log1 = NbaLog::new();
687 log1.stage(cell, FourStateValue::Narrow(1), PROC_A);
688 log1.stage(cell, FourStateValue::Narrow(2), PROC_B);
689 super::commit_nba(&mut log1, &mut arena1, SchedTime { ticks: 0 }, &mut sink1);
690 match fake1.read_cell(cell) {
691 FourStateValue::Narrow(v) => assert_eq!(v, 2, "last-pushed write (B) must win"),
692 FourStateValue::Wide { .. } => panic!("expected Narrow"),
693 }
694
695 let fake2 = FakeArena::new();
700 let mut arena2 = fake2.arena_handle();
701 let mut sink2 = fake2.sink_handle();
702 let mut log2 = NbaLog::new();
703 log2.stage(cell, FourStateValue::Narrow(2), PROC_B);
704 log2.stage(cell, FourStateValue::Narrow(1), PROC_A);
705 super::commit_nba(&mut log2, &mut arena2, SchedTime { ticks: 0 }, &mut sink2);
706 match fake2.read_cell(cell) {
707 FourStateValue::Narrow(v) => assert_eq!(v, 1, "last-pushed write (A) must win"),
708 FourStateValue::Wide { .. } => panic!("expected Narrow"),
709 }
710 }
711
712 #[test]
713 fn cancelling_nbas_are_two_update_events_in_staging_order() {
714 let cell = StateCellId(3);
718 let fake = FakeArena::new();
719 fake.seed(cell, FourStateValue::Narrow(0));
720 let mut arena = fake.arena_handle();
721 let mut sink = fake.sink_handle();
722 let mut log = NbaLog::new();
723 log.stage(cell, FourStateValue::Narrow(1), PROC_A);
724 log.stage(cell, FourStateValue::Narrow(0), PROC_A);
725 super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 0 }, &mut sink);
726 let edges: Vec<EdgeKind> = fake.notifications().into_iter().map(|n| n.1).collect();
727 assert_eq!(edges, vec![EdgeKind::Posedge, EdgeKind::Negedge]);
728 match fake.read_cell(cell) {
729 FourStateValue::Narrow(v) => assert_eq!(v, 0),
730 FourStateValue::Wide { .. } => panic!("expected Narrow"),
731 }
732 }
733
734 #[test]
735 fn cancelling_partial_nbas_are_two_update_events_each_with_its_bits() {
736 let cell = StateCellId(4);
739 let fake = FakeArena::new();
740 fake.seed(
741 cell,
742 FourStateValue::Wide {
743 aval: vec![0, 0],
744 bval: vec![0, 0],
745 },
746 );
747 let mut arena = fake.arena_handle();
748 let mut sink = fake.sink_handle();
749 let mut log = NbaLog::new();
750 for bit in [1u64, 0] {
751 log.stage_masked(
752 cell,
753 None,
754 FourStateValue::Wide {
755 aval: vec![bit << 9, 0],
756 bval: vec![0, 0],
757 },
758 vec![1 << 9, 0],
759 None,
760 PROC_A,
761 );
762 }
763 super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 0 }, &mut sink);
764 let n = fake.notifications();
765 assert_eq!(n.len(), 2, "one update event per value-changing write");
766 let deltas = fake.0.borrow().deltas.clone();
767 assert_eq!(deltas.len(), 2);
768 assert_eq!(deltas[0].bit(9), Some(((false, false), (true, false))));
769 assert_eq!(deltas[1].bit(9), Some(((true, false), (false, false))));
770 }
771
772 #[test]
773 fn a_staged_pulse_triggers_the_event_cell_in_the_nba_region() {
774 let ev = StateCellId(6);
778 let data = StateCellId(7);
779 let fake = FakeArena::new();
780 fake.seed(
781 ev,
782 FourStateValue::Wide {
783 aval: vec![3, 4],
784 bval: vec![0, 0],
785 },
786 );
787 fake.seed(data, FourStateValue::Narrow(0));
788 let mut arena = fake.arena_handle();
789 let mut sink = fake.sink_handle();
790 let mut log = NbaLog::new();
791 log.stage(data, FourStateValue::Narrow(1), PROC_A);
792 log.stage_pulse(ev, None, PROC_A);
793 log.stage_pulse(ev, None, PROC_B);
794 super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 9 }, &mut sink);
795 match fake.read_cell(ev) {
796 FourStateValue::Wide { aval, bval } => {
797 assert_eq!(aval, vec![5, 10], "count 3 -> 5, stamped tick 9 + 1");
798 assert_eq!(bval, vec![0, 0]);
799 }
800 FourStateValue::Narrow(_) => panic!("an event cell is wide"),
801 }
802 let n = fake.notifications();
803 assert_eq!(n.iter().filter(|x| x.0 == ev).count(), 2);
804 assert!(n.iter().all(|x| x.3 == Region::Nba));
805 assert_eq!(n.iter().filter(|x| x.0 == data).count(), 1);
806 }
807
808 #[test]
809 fn unchanged_commit_produces_no_notify_change_call() {
810 let cell = StateCellId(2);
811 let fake = FakeArena::new();
812 fake.seed(cell, FourStateValue::Narrow(5));
813 let mut arena = fake.arena_handle();
814 let mut sink = fake.sink_handle();
815
816 let mut log = NbaLog::new();
817 log.stage(cell, FourStateValue::Narrow(5), PROC_A);
818
819 let changed = super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 3 }, &mut sink);
820
821 assert_eq!(changed, 0);
822 assert!(
823 fake.notifications().is_empty(),
824 "an NBA write equal to the current value must not call notify_change"
825 );
826 }
827
828 #[test]
829 fn changed_commit_calls_notify_change_exactly_once_per_changed_cell() {
830 let cell_changed_1 = StateCellId(10);
831 let cell_changed_2 = StateCellId(11);
832 let cell_unchanged = StateCellId(12);
833
834 let fake = FakeArena::new();
835 fake.seed(cell_changed_1, FourStateValue::Narrow(0));
836 fake.seed(cell_changed_2, FourStateValue::Narrow(0));
837 fake.seed(cell_unchanged, FourStateValue::Narrow(7));
838 let mut arena = fake.arena_handle();
839 let mut sink = fake.sink_handle();
840
841 let mut log = NbaLog::new();
842 log.stage(cell_changed_1, FourStateValue::Narrow(1), PROC_A);
843 log.stage(cell_changed_2, FourStateValue::Narrow(1), PROC_A);
844 log.stage(cell_unchanged, FourStateValue::Narrow(7), PROC_A);
845
846 let changed = super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 0 }, &mut sink);
847
848 assert_eq!(changed, 2);
849 let notified: Vec<StateCellId> = fake.notifications().into_iter().map(|n| n.0).collect();
850 assert_eq!(notified.len(), 2, "no duplicate notify_change calls");
851 let notified_set: HashSet<StateCellId> = notified.into_iter().collect();
852 assert!(notified_set.contains(&cell_changed_1));
853 assert!(notified_set.contains(&cell_changed_2));
854 assert!(!notified_set.contains(&cell_unchanged));
855 }
856
857 #[test]
858 fn changed_cell_diffing_is_the_sole_wakeup_source() {
859 let cell_a = StateCellId(20);
860 let cell_b = StateCellId(21);
861 let cell_c = StateCellId(22);
862 let cell_d = StateCellId(23);
863
864 let fake = FakeArena::new();
865 fake.seed(cell_a, FourStateValue::Narrow(0));
866 fake.seed(cell_b, FourStateValue::Narrow(9));
867 fake.seed(cell_c, FourStateValue::Narrow(0));
868 fake.seed(cell_d, FourStateValue::Narrow(4));
869 let mut arena = fake.arena_handle();
870 let mut sink = fake.sink_handle();
871
872 let mut log = NbaLog::new();
873 log.stage(cell_a, FourStateValue::Narrow(1), PROC_A);
876 log.stage(cell_b, FourStateValue::Narrow(9), PROC_A);
877 log.stage(cell_c, FourStateValue::Narrow(2), PROC_A);
878 log.stage(cell_d, FourStateValue::Narrow(4), PROC_A);
879
880 super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 0 }, &mut sink);
881
882 let notified_set: HashSet<StateCellId> =
883 fake.notifications().into_iter().map(|n| n.0).collect();
884 let expected: HashSet<StateCellId> = [cell_a, cell_c].into_iter().collect();
885 assert_eq!(notified_set, expected);
889 }
890
891 #[test]
892 fn multi_bit_change_with_unchanged_bit0_still_wakes_an_any_change_static_entry() {
893 use crate::sched::StaticSensitivity;
903 use crate::sched::sensitivity::{SensitivityLookupSink, SensitivityTable};
904
905 let cell = StateCellId(30);
906 let fake = FakeArena::new();
907 fake.seed(cell, FourStateValue::Narrow(0b10));
908 let mut arena = fake.arena_handle();
909
910 let mut table = SensitivityTable::new();
911 const PROC_COMB: ProcessId = 99;
912 table.install_static(
913 &StaticSensitivity {
914 edges: vec![(cell, None)],
915 },
916 PROC_COMB,
917 );
918 let mut woken = Vec::new();
919 let mut sink = SensitivityLookupSink::new(&table, &mut woken);
920
921 let mut log = NbaLog::new();
922 log.stage(cell, FourStateValue::Narrow(0b110), PROC_A);
923
924 let changed = super::commit_nba(&mut log, &mut arena, SchedTime { ticks: 0 }, &mut sink);
925
926 assert_eq!(changed, 1, "Narrow(0b10) -> Narrow(0b110) is a real change");
927 assert_eq!(
928 woken,
929 vec![PROC_COMB],
930 "an any-change Static entry must fire on a real multi-bit change even when bit 0 (infer_edge's only input) doesn't toggle"
931 );
932 }
933
934 #[test]
935 fn infer_edge_covers_the_four_state_transition_table() {
936 use vj_abi::Bit4;
937
938 fn bit_value(b: Bit4) -> FourStateValue {
939 let (val, unk) = b.to_val_unk();
940 FourStateValue::Narrow((val as u128) | ((unk as u128) << 64))
941 }
942
943 let cases: &[(Bit4, Bit4, EdgeKind)] = &[
944 (Bit4::Zero, Bit4::Zero, EdgeKind::None),
945 (Bit4::One, Bit4::One, EdgeKind::None),
946 (Bit4::X, Bit4::X, EdgeKind::None),
947 (Bit4::Z, Bit4::Z, EdgeKind::None),
948 (Bit4::Zero, Bit4::One, EdgeKind::Posedge),
949 (Bit4::One, Bit4::Zero, EdgeKind::Negedge),
950 (Bit4::Zero, Bit4::X, EdgeKind::Posedge),
954 (Bit4::X, Bit4::One, EdgeKind::Posedge),
955 (Bit4::Z, Bit4::One, EdgeKind::Posedge),
956 (Bit4::Zero, Bit4::Z, EdgeKind::Posedge),
957 (Bit4::One, Bit4::X, EdgeKind::Negedge),
958 (Bit4::One, Bit4::Z, EdgeKind::Negedge),
959 (Bit4::X, Bit4::Zero, EdgeKind::Negedge),
960 (Bit4::Z, Bit4::Zero, EdgeKind::Negedge),
961 (Bit4::X, Bit4::Z, EdgeKind::None),
966 (Bit4::Z, Bit4::X, EdgeKind::None),
967 ];
968
969 for (old, new, expected) in cases {
970 let old_v = bit_value(*old);
971 let new_v = bit_value(*new);
972 let got = super::infer_edge(&old_v, &new_v);
973 assert_eq!(
974 got, *expected,
975 "infer_edge({:?} -> {:?}) should be {:?}, got {:?}",
976 old, new, expected, got
977 );
978 }
979 }
980
981 #[test]
982 fn nba_write_lowering_shapes_both_append_to_the_log() {
983 let mut log_a = NbaLog::new();
986 log_a.stage(StateCellId(9), FourStateValue::Narrow(42), 3);
987 let from_procedural_assign = log_a.take();
988
989 let mut log_b = NbaLog::new();
992 log_b.stage(StateCellId(9), FourStateValue::Narrow(42), 3);
993 let from_expression_statement = log_b.take();
994
995 assert_eq!(from_procedural_assign.len(), 1);
996 assert_eq!(from_expression_statement.len(), 1);
997
998 let a = &from_procedural_assign[0];
999 let b = &from_expression_statement[0];
1000 assert_eq!(a.cell, b.cell);
1004 assert_eq!(a.source, b.source);
1005 assert_eq!(a.order, b.order);
1006 match (&a.value, &b.value) {
1007 (FourStateValue::Narrow(x), FourStateValue::Narrow(y)) => assert_eq!(x, y),
1008 _ => panic!("expected Narrow"),
1009 }
1010 }
1011}