Skip to main content

hdl_cat_std/
counter.rs

1//! `N`-bit free-running counter.
2//!
3//! Builds a [`hdl_cat_sync::Sync`] machine whose state is a
4//! `Bits<N>` counter that increments by 1 each cycle.  The
5//! machine has no data input and outputs the current count.
6
7use hdl_cat_bits::Bits;
8use hdl_cat_circuit::{CircuitUnit, Obj};
9use hdl_cat_error::{Error, Width};
10use hdl_cat_ir::{BinOp, HdlGraphBuilder, Op, WireTy};
11use hdl_cat_kind::BitSeq;
12use hdl_cat_sync::{machine, Sync};
13
14/// A free-running `N`-bit counter.
15///
16/// State: `Bits<N>` starting at 0.
17/// Input: nothing ([`CircuitUnit`]).
18/// Output: the current count (before increment).
19pub type CounterSync<const N: usize> = Sync<Obj<Bits<N>>, CircuitUnit, Obj<Bits<N>>>;
20
21/// Construct a free-running `N`-bit counter.
22///
23/// # Errors
24///
25/// Returns [`Error::Overflow`] when `N` exceeds `u32::MAX`.
26///
27/// # Examples
28///
29/// ```
30/// # fn main() -> Result<(), hdl_cat_error::Error> {
31/// use hdl_cat_std::counter;
32/// let c = counter::<8>()?;
33/// assert_eq!(c.state_wire_count(), 1);
34/// assert_eq!(c.initial_state().len(), 8);
35/// # Ok(()) }
36/// ```
37pub fn counter<const N: usize>() -> Result<CounterSync<N>, Error> {
38    let width = u32::try_from(N).map_err(|_| Error::Overflow {
39        width: Width::new(u32::MAX),
40    })?;
41    let ty = WireTy::Bits(width);
42
43    let (bld, state) = HdlGraphBuilder::new().with_wire(ty.clone());
44    let (bld, one) = bld.with_wire(ty.clone());
45    let (bld, next_state) = bld.with_wire(ty.clone());
46
47    let bld = bld.with_instruction(
48        Op::Const {
49            bits: one_bitseq(N),
50            ty: ty.clone(),
51        },
52        vec![],
53        one,
54    )?;
55    let bld = bld.with_instruction(Op::Bin(BinOp::Add), vec![state, one], next_state)?;
56
57    let graph = bld.build();
58    let initial_state = zero_bitseq(N);
59
60    // input_wires: [state]  (no data input; CircuitUnit contributes 0 wires)
61    // output_wires: [next_state, state]  (state itself serves as the data output;
62    //   it is an input wire so its value is taken from the incoming state each cycle)
63    Ok(machine::from_raw(
64        graph,
65        vec![state],
66        vec![next_state, state],
67        initial_state,
68        1,
69    ))
70}
71
72fn zero_bitseq(n: usize) -> BitSeq {
73    (0..n).map(|_| false).collect()
74}
75
76fn one_bitseq(n: usize) -> BitSeq {
77    (0..n).map(|i| i == 0).collect()
78}
79
80#[cfg(test)]
81mod tests {
82    use super::counter;
83
84    #[test]
85    fn counter_has_one_state_wire() -> Result<(), hdl_cat_error::Error> {
86        let c = counter::<8>()?;
87        assert_eq!(c.state_wire_count(), 1);
88        Ok(())
89    }
90
91    #[test]
92    fn counter_initial_state_is_zero() -> Result<(), hdl_cat_error::Error> {
93        let c = counter::<8>()?;
94        assert_eq!(c.initial_state().len(), 8);
95        assert!(c.initial_state().as_slice().iter().all(|b| !*b));
96        Ok(())
97    }
98
99    #[test]
100    fn counter_has_two_instructions() -> Result<(), hdl_cat_error::Error> {
101        let c = counter::<4>()?;
102        // Const + Add = 2 instructions.
103        assert_eq!(c.graph().instructions().len(), 2);
104        Ok(())
105    }
106}