use super::write::{ChangeResult, PreWriteHook, SlotLocation};
use super::{FourStateValue, SignalAccess, SlotClass, StateCellId};
use crate::state::instance::StateEngine;
pub const EVENT_CELL_WIDTH: u32 = 128;
impl StateEngine {
pub fn alloc_event_cell(&mut self) -> StateCellId {
let word_offset = unsafe { self.arena.alloc_inline_word() };
let loc = SlotLocation {
class: SlotClass::Inline,
word_offset,
bit_width: 1,
elem_word_count: 1,
row_stride_words: 0,
num_rows: 0,
};
let cell = self.alloc_cell(loc);
self.event_cells.insert(cell);
cell
}
pub fn trigger_event(
&mut self,
cell: StateCellId,
hook: &mut dyn PreWriteHook,
) -> ChangeResult {
debug_assert!(
self.event_cells.contains(&cell),
"trigger_event: {cell:?} was never allocated via StateEngine::alloc_event_cell -- \
an ordinary variable/net cell has no LRM 'trigger' semantics to fire"
);
let current = match self.read(cell) {
FourStateValue::Narrow(word) => (word as u64) & 1 != 0,
FourStateValue::Wide { .. } => unreachable!(
"event cell {cell:?} decoded as FourStateValue::Wide -- \
alloc_event_cell only ever allocates SlotClass::Inline cells"
),
};
let next = FourStateValue::Narrow(if current { 0u128 } else { 1u128 });
self.commit_write(cell, next, hook)
}
pub fn clear_event_pulses(&mut self) {
let cells: Vec<StateCellId> = self.event_cells.iter().copied().collect();
for cell in cells {
self.commit_write_no_hook(cell, FourStateValue::Narrow(0u128));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::instance::TemplateRegistry;
fn fresh_engine() -> StateEngine {
StateEngine::new(TemplateRegistry::new())
}
#[test]
fn alloc_event_cell_starts_at_zero_and_is_tracked() {
let mut engine = fresh_engine();
let ev = engine.alloc_event_cell();
assert!(engine.event_cells.contains(&ev));
match engine.read(ev) {
FourStateValue::Narrow(w) => assert_eq!(w as u64 & 1, 0),
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
}
#[test]
fn trigger_event_flips_the_cell_and_reports_a_change() {
let mut engine = fresh_engine();
let ev = engine.alloc_event_cell();
let result = engine.trigger_event(ev, &mut ());
assert!(result.changed);
match engine.read(ev) {
FourStateValue::Narrow(w) => assert_eq!(w as u64 & 1, 1),
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
}
#[test]
fn trigger_event_reports_a_change_on_every_consecutive_trigger_with_no_intervening_clear() {
let mut engine = fresh_engine();
let ev = engine.alloc_event_cell();
let first = engine.trigger_event(ev, &mut ());
assert!(first.changed, "first trigger (0 -> 1) must report a change");
let second = engine.trigger_event(ev, &mut ());
assert!(
second.changed,
"second consecutive trigger with no intervening clear must ALSO report a change \
(it must flip 1 -> 0, never re-write the same value 1 -> 1)"
);
match engine.read(ev) {
FourStateValue::Narrow(w) => assert_eq!(
w as u64 & 1,
0,
"the second trigger must flip the cell back to 0"
),
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
let third = engine.trigger_event(ev, &mut ());
assert!(
third.changed,
"a third consecutive trigger must flip 0 -> 1 and report a change again"
);
}
#[test]
fn clear_event_pulses_resets_every_tracked_cell_to_zero() {
let mut engine = fresh_engine();
let a = engine.alloc_event_cell();
let b = engine.alloc_event_cell();
engine.trigger_event(a, &mut ());
engine.trigger_event(b, &mut ());
engine.clear_event_pulses();
for cell in [a, b] {
match engine.read(cell) {
FourStateValue::Narrow(w) => assert_eq!(w as u64 & 1, 0),
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
}
}
}