rust_hdl_widgets/fifo/
fifo_reducer.rs

1use rust_hdl_core::prelude::*;
2
3use crate::{dff::DFF, dff_setup};
4
5#[derive(LogicBlock)]
6pub struct FIFOReducer<const DW: usize, const DN: usize, const REVERSE: bool> {
7    // Data comes by reading from the source FIFO
8    pub data_in: Signal<In, Bits<DW>>,
9    pub read: Signal<Out, Bit>,
10    pub empty: Signal<In, Bit>,
11    // Data is written to the output FIFO
12    pub data_out: Signal<Out, Bits<DN>>,
13    pub write: Signal<Out, Bit>,
14    pub full: Signal<In, Bit>,
15    // This is a synchronous design.  The clock is assumed
16    // to be shared with both the input and output fifos.
17    pub clock: Signal<In, Clock>,
18    loaded: DFF<Bit>,
19    data_available: Signal<Local, Bit>,
20    can_write: Signal<Local, Bit>,
21    will_run: Signal<Local, Bit>,
22    data_to_write: Signal<Local, Bits<DN>>,
23    offset: Constant<Bits<DW>>,
24    reverse: Constant<Bit>,
25}
26
27impl<const DW: usize, const DN: usize, const REVERSE: bool> Default
28    for FIFOReducer<DW, DN, REVERSE>
29{
30    fn default() -> Self {
31        assert_eq!(DW, DN * 2);
32        Self {
33            data_in: Default::default(),
34            read: Default::default(),
35            empty: Default::default(),
36            data_out: Default::default(),
37            write: Default::default(),
38            full: Default::default(),
39            clock: Default::default(),
40            loaded: Default::default(),
41            data_available: Default::default(),
42            can_write: Default::default(),
43            will_run: Default::default(),
44            data_to_write: Default::default(),
45            offset: Constant::new(DN.to_bits()),
46            reverse: Constant::new(REVERSE),
47        }
48    }
49}
50
51impl<const DW: usize, const DN: usize, const REVERSE: bool> Logic for FIFOReducer<DW, DN, REVERSE> {
52    #[hdl_gen]
53    fn update(&mut self) {
54        // Connect the clock
55        dff_setup!(self, clock, loaded);
56        // Input data is available if we are loaded or if the read interface is not empty
57        self.data_available.next = self.loaded.q.val() || !self.empty.val();
58        // Output space is available if the write interface is not full and we have data available
59        self.can_write.next = self.data_available.val() && !self.full.val();
60        // This signal indicates the reducer will actually do something
61        self.will_run.next = self.data_available.val() && self.can_write.val();
62        // If data is available we select which piece of the input based on the loaded flag
63        if self.reverse.val() ^ self.loaded.q.val() {
64            self.data_to_write.next = self.data_in.val().get_bits::<DN>(self.offset.val().index());
65        } else {
66            self.data_to_write.next = self.data_in.val().get_bits::<DN>(0);
67        }
68        // The input to the output fifo is always data_to_write (although it may not be valid)
69        self.data_out.next = self.data_to_write.val();
70        // If we have data and we can write, then write!
71        self.write.next = self.can_write.val();
72        // Toggle loaded if we have data available and can write
73        self.loaded.d.next = self.loaded.q.val() ^ self.will_run.val();
74        // Advance the read interface if it is not empty and we wont be loaded
75        self.read.next = self.loaded.q.val() && self.will_run.val() && !self.empty.val();
76    }
77}
78
79#[test]
80fn fifo_reducer_is_synthesizable() {
81    let mut dev: FIFOReducer<8, 4, false> = Default::default();
82    dev.connect_all();
83    yosys_validate("fifo_reducer", &generate_verilog(&dev)).unwrap();
84}