Skip to main content

bitloom_sim/
ip_dual.rs

1//! FR103 — first-class IP dual-model matrix (NFR14 FIFO/UART/SPI/I2C/AXI).
2//!
3//! Cycle path = FrozenHir [`crate::Sim::tick`]. Functional path =
4//! [`crate::GeneratedFunctional`] (generation path) **or** handwritten
5//! [`SyncFifoFunctional`] when FR103 nails architectural FL for Mem-based FIFO.
6//! **FR126** adds handwritten [`GpioFunctional`]; **FR135** adds handwritten
7//! [`UartTxFunctional`] beyond Gpio / GeneratedFunctional alone.
8//!
9//! Design crates stay on `bitloom-prelude`; this module lives in the toolchain.
10
11use bitloom_hir::{FrozenHir, PortValues};
12
13use crate::{
14    AbstractionView, EquivStatus, FormalEquivProduct, PortMismatch, Sim, check_functional_equiv,
15    compare_port_values,
16};
17
18/// Depth-4 SyncFifo functional model (architectural PortValues).
19///
20/// FR103 completion face for FIFO. **FR112** separately deepens
21/// `GeneratedFunctional` MemRead≡tick; that path does not replace this
22/// handwritten SyncFifo FL (see `docs/fr103-ip-dual-model.md`).
23#[derive(Debug, Clone, Default)]
24pub struct SyncFifoFunctional {
25    ram: [u64; 4],
26    wr_ptr: u8,
27    rd_ptr: u8,
28    count: u8,
29    dout: u64,
30}
31
32impl SyncFifoFunctional {
33    pub fn new() -> Self {
34        Self::default()
35    }
36}
37
38impl AbstractionView for SyncFifoFunctional {
39    fn cycle(&mut self, inputs: &PortValues) -> PortValues {
40        let rst = inputs.get("rst").unwrap_or(0) != 0;
41        let wr_en = inputs.get("wr_en").unwrap_or(0) != 0;
42        let rd_en = inputs.get("rd_en").unwrap_or(0) != 0;
43        let data_in = inputs.get("data_in").unwrap_or(0);
44
45        if rst {
46            self.ram = [0; 4];
47            self.wr_ptr = 0;
48            self.rd_ptr = 0;
49            self.count = 0;
50            self.dout = 0;
51        } else {
52            // Match SyncFifo HIR order: mem write (if can_wr), then async
53            // dout := ram[rd_ptr], then advance pointers/count.
54            let full = self.count >= 4;
55            let empty = self.count == 0;
56            let can_wr = wr_en && !full;
57            let can_rd = rd_en && !empty;
58
59            if can_wr {
60                self.ram[self.wr_ptr as usize % 4] = data_in & 0xff;
61            }
62            // Unconditional async-style head peek (assign_reg_d_mem_read on declare_mem).
63            self.dout = self.ram[self.rd_ptr as usize % 4];
64
65            if can_wr {
66                self.wr_ptr = self.wr_ptr.wrapping_add(1) & 0b11;
67            }
68            if can_rd {
69                self.rd_ptr = self.rd_ptr.wrapping_add(1) & 0b11;
70            }
71            match (can_wr, can_rd) {
72                (true, true) => {}
73                (true, false) => self.count = self.count.saturating_add(1).min(4),
74                (false, true) => self.count = self.count.saturating_sub(1),
75                (false, false) => {}
76            }
77        }
78
79        let mut out = inputs.clone();
80        out.set("full", u64::from(self.count >= 4));
81        out.set("empty", u64::from(self.count == 0));
82        out.set("data_out", self.dout);
83        out
84    }
85}
86
87/// Documented SyncFifo dual-model stimulus (reset, push, pop) — FR103 fixture.
88pub fn sync_fifo_dual_stimulus() -> Vec<PortValues> {
89    let mut out = Vec::new();
90    let mut frame = |rst: u64, wr_en: u64, rd_en: u64, data_in: u64| {
91        let mut pv = PortValues::default();
92        pv.set("rst", rst);
93        pv.set("wr_en", wr_en);
94        pv.set("rd_en", rd_en);
95        pv.set("data_in", data_in);
96        out.push(pv);
97    };
98    frame(1, 0, 0, 0);
99    frame(0, 1, 0, 0x11);
100    frame(0, 1, 0, 0x22);
101    frame(0, 0, 1, 0);
102    frame(0, 0, 1, 0);
103    frame(0, 0, 0, 0);
104    out
105}
106
107/// Handwritten `Gpio` FL (FR126) — beyond FR103 SyncFifo / GeneratedFunctional UART–AXI.
108///
109/// Models architectural ports `pad_out` / `rd_data` ≡ `Sim::tick` on
110/// [`gpio_dual_stimulus`]. Not GeneratedFunctional; not FR112 MemRead≡tick;
111/// not FR119 sby alone.
112#[derive(Debug, Clone, Default)]
113pub struct GpioFunctional {
114    out_r: u64,
115}
116
117impl GpioFunctional {
118    pub fn new() -> Self {
119        Self::default()
120    }
121}
122
123impl AbstractionView for GpioFunctional {
124    fn cycle(&mut self, inputs: &PortValues) -> PortValues {
125        let rst = inputs.get("rst").unwrap_or(0) != 0;
126        let dir = inputs.get("dir").unwrap_or(0) & 0xff;
127        let wr_en = inputs.get("wr_en").unwrap_or(0) != 0;
128        let wr_data = inputs.get("wr_data").unwrap_or(0) & 0xff;
129        let wr_mask = inputs.get("wr_mask").unwrap_or(0) & 0xff;
130        let pad_in = inputs.get("pad_in").unwrap_or(0) & 0xff;
131
132        if rst {
133            self.out_r = 0;
134        } else if wr_en {
135            let kept = self.out_r & (!wr_mask & 0xff);
136            let newt = wr_data & wr_mask;
137            self.out_r = (kept | newt) & 0xff;
138        }
139
140        let pad_out = self.out_r & dir;
141        let rd_data = (self.out_r & dir) | (pad_in & (!dir & 0xff));
142
143        let mut out = inputs.clone();
144        out.set("pad_out", pad_out);
145        out.set("rd_data", rd_data);
146        out
147    }
148}
149
150/// Documented Gpio dual-model stimulus (reset, masked write, pad read) — FR126.
151pub fn gpio_dual_stimulus() -> Vec<PortValues> {
152    let mut out = Vec::new();
153    let mut frame = |rst: u64, dir: u64, wr_en: u64, wr_data: u64, wr_mask: u64, pad_in: u64| {
154        let mut pv = PortValues::default();
155        pv.set("rst", rst);
156        pv.set("dir", dir);
157        pv.set("wr_en", wr_en);
158        pv.set("wr_data", wr_data);
159        pv.set("wr_mask", wr_mask);
160        pv.set("pad_in", pad_in);
161        out.push(pv);
162    };
163    frame(1, 0xff, 0, 0, 0, 0);
164    frame(0, 0xff, 1, 0xa5, 0xff, 0);
165    frame(0, 0xff, 0, 0, 0, 0);
166    frame(0, 0x0f, 0, 0, 0, 0xf0); // lower nybble out, upper from pad
167    frame(0, 0x0f, 1, 0x03, 0x0f, 0xf0);
168    frame(0, 0x0f, 0, 0, 0, 0xaa);
169    out
170}
171
172/// Handwritten `UartTx` FL (FR135) — beyond FR126 Gpio / FR103 SyncFifo / GeneratedFunctional.
173///
174/// Models architectural ports `tx` / `tx_byte` / `tx_busy` ≡ `Sim::settle`+`tick` on
175/// [`uart_tx_dual_stimulus`]. Not GeneratedFunctional; not Gpio alone; not SyncFifo alone.
176#[derive(Debug, Clone, Default)]
177pub struct UartTxFunctional {
178    hold: u64,
179    shift_reg: u64,
180    busy: u64,
181    bit_idx: u64,
182    baud_cnt: u64,
183}
184
185impl UartTxFunctional {
186    pub fn new() -> Self {
187        Self::default()
188    }
189}
190
191impl AbstractionView for UartTxFunctional {
192    fn cycle(&mut self, inputs: &PortValues) -> PortValues {
193        let rst = inputs.get("rst").unwrap_or(0) != 0;
194        let wr_en = inputs.get("wr_en").unwrap_or(0) != 0;
195        let wr_data = inputs.get("wr_data").unwrap_or(0) & 0xff;
196        let baud_div = inputs.get("baud_div").unwrap_or(0) & 0xff;
197
198        if rst {
199            self.hold = 0;
200            self.shift_reg = 0;
201            self.busy = 0;
202            self.bit_idx = 0;
203            self.baud_cnt = 0;
204        } else {
205            let busy = self.busy != 0;
206            let accept = wr_en && !busy;
207            let is_start = self.bit_idx == 0;
208            let is_stop = self.bit_idx == 9;
209            let baud_eq = self.baud_cnt == baud_div;
210            let baud_tick = busy && baud_eq;
211
212            let baud_cnt_busy = if baud_eq {
213                0
214            } else {
215                self.baud_cnt.wrapping_add(1) & 0xff
216            };
217            let baud_cnt_busy_or_idle = if busy { baud_cnt_busy } else { 0 };
218            let next_baud_cnt = if accept { 0 } else { baud_cnt_busy_or_idle };
219
220            let busy_after_tick = if is_stop { 0 } else { 1 };
221            let busy_when_busy = if baud_tick { busy_after_tick } else { 1 };
222            let busy_when_busy_or_idle = if busy { busy_when_busy } else { 0 };
223            let next_busy = if accept { 1 } else { busy_when_busy_or_idle };
224
225            let bit_idx_p1 = (self.bit_idx.wrapping_add(1)) & 0xf;
226            let bit_after_tick = if is_stop { 0 } else { bit_idx_p1 };
227            let bit_when_busy = if baud_tick {
228                bit_after_tick
229            } else {
230                self.bit_idx
231            };
232            let bit_when_busy_or_idle = if busy { bit_when_busy } else { 0 };
233            let next_bit_idx = if accept { 0 } else { bit_when_busy_or_idle };
234
235            let do_shift = busy && !is_start && !is_stop;
236            let do_shift_tick = do_shift && baud_eq;
237            let shift_shr = (self.shift_reg >> 1) & 0xff;
238            let shift_after_tick = if do_shift_tick {
239                shift_shr
240            } else {
241                self.shift_reg
242            };
243            let next_shift_busy = if busy {
244                shift_after_tick
245            } else {
246                self.shift_reg
247            };
248            let next_shift_final = if accept { wr_data } else { next_shift_busy };
249            let next_hold = if accept { wr_data } else { self.hold };
250
251            self.busy = next_busy;
252            self.bit_idx = next_bit_idx;
253            self.shift_reg = next_shift_final;
254            self.hold = next_hold;
255            self.baud_cnt = next_baud_cnt;
256        }
257
258        let busy = self.busy != 0;
259        let is_start = self.bit_idx == 0;
260        let is_stop = self.bit_idx == 9;
261        let data_bit = (self.shift_reg & 1) != 0;
262        let tx_data_or_stop = if is_stop { 1 } else { u64::from(data_bit) };
263        let tx_active = if is_start { 0 } else { tx_data_or_stop };
264        let tx = if busy { tx_active } else { 1 };
265
266        let mut out = inputs.clone();
267        out.set("tx", tx);
268        out.set("tx_byte", self.hold & 0xff);
269        out.set("tx_busy", self.busy & 1);
270        out
271    }
272}
273
274/// Documented UartTx dual-model stimulus (reset, 8N1 frame, baud_div hold) — FR135.
275pub fn uart_tx_dual_stimulus() -> Vec<PortValues> {
276    let mut out = Vec::new();
277    let mut frame = |rst: u64, wr_en: u64, wr_data: u64, baud_div: u64| {
278        let mut pv = PortValues::default();
279        pv.set("rst", rst);
280        pv.set("wr_en", wr_en);
281        pv.set("wr_data", wr_data);
282        pv.set("baud_div", baud_div);
283        out.push(pv);
284    };
285    // baud_div=0: 1 clk/bit — full 0xA5 frame + idle clear
286    frame(1, 0, 0, 0);
287    frame(0, 0, 0, 0);
288    frame(0, 1, 0xa5, 0); // accept → start
289    for _ in 0..9 {
290        frame(0, 0, 0, 0); // data…stop
291    }
292    frame(0, 0, 0, 0); // clear busy
293    // baud_div=1: 2 clk/bit — start held then LSB
294    frame(1, 0, 0, 1);
295    frame(0, 1, 0x01, 1);
296    frame(0, 0, 0, 1); // start held
297    frame(0, 0, 0, 1); // LSB
298    // busy ignore: latch 0x3C, wr_en with 0xFF must not replace
299    frame(1, 0, 0, 0);
300    frame(0, 1, 0x3c, 0);
301    frame(0, 1, 0xff, 0);
302    frame(0, 0, 0, 0);
303    out
304}
305
306/// Architectural ports compared for SyncFifo dual-model (avoid internal wires).
307const SYNC_FIFO_ARCH_PORTS: &[&str] = &["full", "empty", "data_out"];
308
309/// Architectural ports for Gpio handwritten FL (FR126).
310const GPIO_ARCH_PORTS: &[&str] = &["pad_out", "rd_data"];
311
312/// Architectural ports for UartTx handwritten FL (FR135).
313const UART_TX_ARCH_PORTS: &[&str] = &["tx", "tx_byte", "tx_busy"];
314/// FR103 product entry: co-verify functional + cycle models for the NFR14 IP set.
315#[derive(Debug, Clone, Default)]
316pub struct IpDualModelMatrix;
317
318impl IpDualModelMatrix {
319    pub fn new() -> Self {
320        Self
321    }
322
323    /// SyncFifo: handwritten FL vs `settle`+`tick` on pinned stimulus.
324    pub fn verify_sync_fifo(&self, hir: FrozenHir) -> EquivStatus {
325        let mut sim = Sim::new(hir);
326        let mut fl = SyncFifoFunctional::new();
327        let mut cycles = 0usize;
328        for inputs in sync_fifo_dual_stimulus() {
329            sim.set_inputs(inputs.clone());
330            sim.settle();
331            sim.tick();
332            let abs_out = fl.cycle(&inputs);
333            if let Err(mismatches) =
334                compare_named_ports(sim.ports(), &abs_out, SYNC_FIFO_ARCH_PORTS)
335            {
336                return EquivStatus::Fail {
337                    cycle: cycles,
338                    mismatches,
339                };
340            }
341            cycles += 1;
342        }
343        EquivStatus::Pass { cycles }
344    }
345
346    /// Deliberate mismatch ATDD for SyncFifo.
347    pub fn verify_sync_fifo_with<A: AbstractionView>(
348        &self,
349        hir: FrozenHir,
350        abs: &mut A,
351    ) -> EquivStatus {
352        let mut sim = Sim::new(hir);
353        let mut cycles = 0usize;
354        for inputs in sync_fifo_dual_stimulus() {
355            sim.set_inputs(inputs.clone());
356            sim.settle();
357            sim.tick();
358            let abs_out = abs.cycle(&inputs);
359            if let Err(mismatches) =
360                compare_named_ports(sim.ports(), &abs_out, SYNC_FIFO_ARCH_PORTS)
361            {
362                return EquivStatus::Fail {
363                    cycle: cycles,
364                    mismatches,
365                };
366            }
367            cycles += 1;
368        }
369        EquivStatus::Pass { cycles }
370    }
371
372    /// FR126: handwritten `Gpio` FL ≡ tick on [`gpio_dual_stimulus`].
373    pub fn verify_gpio_handwritten(&self, hir: FrozenHir) -> EquivStatus {
374        let mut sim = Sim::new(hir);
375        let mut fl = GpioFunctional::new();
376        let mut cycles = 0usize;
377        for inputs in gpio_dual_stimulus() {
378            sim.set_inputs(inputs.clone());
379            sim.settle();
380            sim.tick();
381            let abs_out = fl.cycle(&inputs);
382            if let Err(mismatches) = compare_named_ports(sim.ports(), &abs_out, GPIO_ARCH_PORTS) {
383                return EquivStatus::Fail {
384                    cycle: cycles,
385                    mismatches,
386                };
387            }
388            cycles += 1;
389        }
390        EquivStatus::Pass { cycles }
391    }
392
393    /// FR135: handwritten `UartTx` FL ≡ tick on [`uart_tx_dual_stimulus`].
394    pub fn verify_uart_tx_handwritten(&self, hir: FrozenHir) -> EquivStatus {
395        let mut sim = Sim::new(hir);
396        let mut fl = UartTxFunctional::new();
397        let mut cycles = 0usize;
398        for inputs in uart_tx_dual_stimulus() {
399            sim.set_inputs(inputs.clone());
400            sim.settle();
401            sim.tick();
402            let abs_out = fl.cycle(&inputs);
403            if let Err(mismatches) = compare_named_ports(sim.ports(), &abs_out, UART_TX_ARCH_PORTS)
404            {
405                return EquivStatus::Fail {
406                    cycle: cycles,
407                    mismatches,
408                };
409            }
410            cycles += 1;
411        }
412        EquivStatus::Pass { cycles }
413    }
414
415    /// Deliberate mismatch / alternate FL ATDD for UartTx (FR135).
416    pub fn verify_uart_tx_handwritten_with<A: AbstractionView>(
417        &self,
418        hir: FrozenHir,
419        abs: &mut A,
420    ) -> EquivStatus {
421        let mut sim = Sim::new(hir);
422        let mut cycles = 0usize;
423        for inputs in uart_tx_dual_stimulus() {
424            sim.set_inputs(inputs.clone());
425            sim.settle();
426            sim.tick();
427            let abs_out = abs.cycle(&inputs);
428            if let Err(mismatches) = compare_named_ports(sim.ports(), &abs_out, UART_TX_ARCH_PORTS)
429            {
430                return EquivStatus::Fail {
431                    cycle: cycles,
432                    mismatches,
433                };
434            }
435            cycles += 1;
436        }
437        EquivStatus::Pass { cycles }
438    }
439
440    /// UART/SPI/I2C/AXI: generated FL ≡ tick via FormalEquivProduct (rst alphabet).
441    pub fn verify_generated_rst_compare(&self, hir: FrozenHir) -> EquivStatus {
442        FormalEquivProduct::new(0xC0FFEE, 8)
443            .with_boolean_ports(&["rst"])
444            .check_random_compare(hir)
445    }
446
447    /// Convenience: handwritten equiv path (for fixtures that supply their own FL).
448    pub fn verify_handwritten<A: AbstractionView>(
449        &self,
450        hir: FrozenHir,
451        abs: &mut A,
452        stimuli: impl IntoIterator<Item = PortValues>,
453    ) -> EquivStatus {
454        check_functional_equiv(hir, abs, stimuli)
455    }
456}
457
458fn compare_named_ports(
459    left: &PortValues,
460    right: &PortValues,
461    names: &[&str],
462) -> Result<(), Vec<PortMismatch>> {
463    let mut l = PortValues::default();
464    let mut r = PortValues::default();
465    for name in names {
466        if let Some(v) = left.get(name) {
467            l.set(*name, v);
468        }
469        if let Some(v) = right.get(name) {
470            r.set(*name, v);
471        }
472    }
473    compare_port_values(&l, &r)
474}