Skip to main content

rhdl_primitives/
lib.rs

1#![doc = "Reusable synthesis-aware RHDL primitives for the `HashSigs` datapaths."]
2#![forbid(unsafe_code)]
3
4//! Wide cryptographic pipelines do not need a known value in every data register
5//! after reset. Their valid bits provide the ownership boundary: data is only
6//! observed when the corresponding valid bit is set. Resetting thousands of
7//! otherwise don't-care data bits wastes routing and can prevent FPGA register
8//! packing, so this crate provides [`NoResetDff`]. Control state must continue to
9//! use an explicitly reset primitive such as [`rhdl_fpga::core::dff::DFF`].
10
11use quote::format_ident;
12use rhdl::{
13    core::{ScopedName, circuit::descriptor::SyncKind},
14    prelude::*,
15};
16use syn::parse_quote;
17
18/// A positive-edge-triggered, resetless register for datapath storage.
19///
20/// `NoResetDff<T>` deliberately ignores the reset member of RHDL's mandatory
21/// [`ClockReset`] interface. The emitted HDL contains only a clock extraction
22/// and an unconditional nonblocking assignment on the rising edge. It contains
23/// neither an initialization block nor a reset branch.
24///
25/// The value before the first rising edge is unspecified. A design must pair
26/// this register with separately reset validity or allocation state and must not
27/// consume the output until that state says the value is valid.
28///
29/// This is a native RHDL synchronous component. Its Verilog is constructed with
30/// RHDL's HDL AST in the same way as the pinned `rhdl-fpga` DFF; generated RTL is
31/// never textually rewritten.
32#[derive(PartialEq, Debug, Clone, Copy, Default)]
33pub struct NoResetDff<T: Digital> {
34    _marker: core::marker::PhantomData<T>,
35}
36
37impl<T: Digital> NoResetDff<T> {
38    /// Constructs a resetless register.
39    #[must_use]
40    pub const fn new() -> Self {
41        Self {
42            _marker: core::marker::PhantomData,
43        }
44    }
45}
46
47impl<T: Digital> SynchronousIO for NoResetDff<T> {
48    type I = T;
49    type O = T;
50    type Kernel = NoSynchronousKernel<ClockReset, T, (), (T, ())>;
51}
52
53impl<T: Digital> SynchronousDQ for NoResetDff<T> {
54    type D = ();
55    type Q = ();
56}
57
58#[derive(PartialEq, Debug, Digital, Clone, Copy)]
59#[doc(hidden)]
60pub struct NoResetDffState<T: Digital> {
61    clock_reset: ClockReset,
62    current: T,
63    next: T,
64}
65
66impl<T: Digital> Synchronous for NoResetDff<T> {
67    type S = NoResetDffState<T>;
68
69    fn init(&self) -> Self::S {
70        Self::S::dont_care()
71    }
72
73    fn sim(&self, clock_reset: ClockReset, input: Self::I, state: &mut Self::S) -> Self::O {
74        trace_push_path("no_reset_dff");
75        trace("input", &input);
76        let clock = clock_reset.clock;
77        if !clock.raw() {
78            state.next = input;
79        }
80        if clock.raw() && !state.clock_reset.clock.raw() {
81            state.current = state.next;
82        }
83        state.clock_reset = clock_reset;
84        trace("output", &state.current);
85        trace_pop_path();
86        state.current
87    }
88
89    fn descriptor(&self, scoped_name: ScopedName) -> Result<Descriptor<SyncKind>, RHDLError> {
90        let name = scoped_name.to_string();
91        Descriptor::<SyncKind> {
92            name: scoped_name,
93            input_kind: Self::I::static_kind(),
94            output_kind: Self::O::static_kind(),
95            d_kind: Kind::Empty,
96            q_kind: Kind::Empty,
97            kernel: None,
98            hdl: Some(Self::hdl(&name)?),
99            netlist: None,
100            _phantom: core::marker::PhantomData,
101        }
102        .with_netlist_black_box()
103    }
104}
105
106impl<T: Digital> NoResetDff<T> {
107    fn hdl(name: &str) -> Result<HDLDescriptor, RHDLError> {
108        let module_name = format_ident!("{name}");
109        let data_width: vlog::BitRange = (0..T::static_kind().bits()).into();
110        let clock_reset = ClockReset::dont_care();
111        let clock_index = bit_range(ClockReset::static_kind(), &path!(clock_reset.clock))?;
112        let clock_index = syn::Index::from(clock_index.0.start);
113        let module: vlog::ModuleDef = parse_quote! {
114            module #module_name(
115                input wire [1:0] clock_reset,
116                input wire [#data_width] i,
117                output reg [#data_width] o
118            );
119                wire clock;
120                assign clock = clock_reset[#clock_index];
121                always @(posedge clock) begin
122                    o <= i;
123                end
124            endmodule
125        };
126        Ok(HDLDescriptor {
127            name: name.into(),
128            modules: module.into(),
129        })
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn emitted_storage_has_no_reset_or_initialization_branch() -> Result<(), RHDLError> {
139        let hdl = NoResetDff::<b32>::hdl("datapath_register")?
140            .modules
141            .pretty();
142
143        assert!(hdl.contains("always @(posedge clock)"));
144        assert!(hdl.contains("o <= i;"));
145        assert!(!hdl.contains("wire  reset"));
146        assert!(!hdl.contains("if (reset)"));
147        assert!(!hdl.contains("initial begin"));
148        assert_eq!(hdl.matches("always @").count(), 1);
149        Ok(())
150    }
151
152    #[test]
153    fn descriptor_is_a_native_rhdl_black_box() -> Result<(), RHDLError> {
154        let descriptor = NoResetDff::<b32>::new().descriptor("datapath_register".into())?;
155        let hdl = descriptor.hdl()?.modules.pretty();
156        assert!(hdl.contains("module datapath_register"));
157        assert!(!hdl.contains("if (reset)"));
158        Ok(())
159    }
160}