rust_hdl_widgets/fifo/
fifo_expander_n.rs

1use rust_hdl_core::prelude::*;
2
3use crate::{dff::DFF, dff_setup};
4
5#[derive(Copy, Clone, Debug, PartialEq)]
6pub enum WordOrder {
7    LeastSignificantFirst,
8    MostSignificantFirst,
9}
10
11#[derive(LogicBlock)]
12pub struct FIFOExpanderN<const DN: usize, const DW: usize> {
13    // Data comes by reading from the source FIFO
14    pub data_in: Signal<In, Bits<DN>>,
15    pub read: Signal<Out, Bit>,
16    pub empty: Signal<In, Bit>,
17    // Data is written to the output FIFO
18    pub data_out: Signal<Out, Bits<DW>>,
19    pub write: Signal<Out, Bit>,
20    pub full: Signal<In, Bit>,
21    // Synchronous design.  Assumes the same clock drives the
22    // corresponding interfaces of the input and output fifos.
23    pub clock: Signal<In, Clock>,
24    load_count: DFF<Bits<8>>,
25    loaded: Signal<Local, Bit>,
26    complete_data_available: Signal<Local, Bit>,
27    will_write: Signal<Local, Bit>,
28    will_consume: Signal<Local, Bit>,
29    data_store: DFF<Bits<DW>>,
30    offset: Constant<Bits<DW>>,
31    ratio: Constant<Bits<8>>,
32    placement: Constant<Bits<DW>>,
33    msw_first: Constant<bool>,
34}
35
36impl<const DN: usize, const DW: usize> Logic for FIFOExpanderN<DN, DW> {
37    #[hdl_gen]
38    fn update(&mut self) {
39        // Clocks and latch prevention for the DFFs
40        dff_setup!(self, clock, load_count, data_store);
41        // Loaded if we have shifted M-1 data elements into the data store
42        self.loaded.next = self.load_count.q.val() == self.ratio.val();
43        // Complete data is available if we have shifted M-1 data elements into
44        // the data store, and there is data available at the input
45        self.complete_data_available.next = self.loaded.val() & !self.empty.val();
46        // We will write if the write interface is not full and we have compelte data
47        self.will_write.next = self.complete_data_available.val() & !self.full.val();
48        // We will consume if there is data available and we will write or if we are not loaded
49        self.will_consume.next = !self.empty.val() & (self.will_write.val() | !self.loaded.val());
50        // If we will consume data and we are not loaded, then the data goes to the store
51        if self.will_consume.val() & !self.loaded.val() {
52            if self.msw_first.val() {
53                self.data_store.d.next = (self.data_store.q.val() << self.offset.val())
54                    | bit_cast::<DW, DN>(self.data_in.val());
55            } else {
56                self.data_store.d.next = (self.data_store.q.val() >> self.offset.val())
57                    | (bit_cast::<DW, DN>(self.data_in.val()) << self.placement.val());
58            }
59            self.load_count.d.next = self.load_count.q.val() + 1;
60        }
61        // The output FIFO always sees the data store shifted with the input or-ed in
62        if self.msw_first.val() {
63            self.data_out.next = bit_cast::<DW, DN>(self.data_in.val())
64                | (self.data_store.q.val() << self.offset.val());
65        } else {
66            self.data_out.next = (bit_cast::<DW, DN>(self.data_in.val()) << self.placement.val())
67                | (self.data_store.q.val() >> self.offset.val());
68        }
69        self.write.next = self.will_write.val();
70        self.read.next = self.will_consume.val();
71        if self.will_write.val() {
72            self.load_count.d.next = 0.into();
73        }
74    }
75}
76
77impl<const DN: usize, const DW: usize> FIFOExpanderN<DN, DW> {
78    pub fn new(order: WordOrder) -> Self {
79        assert!(DW > DN);
80        assert_eq!(DW % DN, 0);
81        Self {
82            data_in: Default::default(),
83            read: Default::default(),
84            empty: Default::default(),
85            data_out: Default::default(),
86            write: Default::default(),
87            full: Default::default(),
88            clock: Default::default(),
89            load_count: Default::default(),
90            loaded: Default::default(),
91            complete_data_available: Default::default(),
92            will_write: Default::default(),
93            will_consume: Default::default(),
94            data_store: Default::default(),
95            offset: Constant::new(DN.to_bits()),
96            ratio: Constant::new((DW / DN - 1).to_bits()),
97            placement: Constant::new((DN * (DW / DN - 1)).to_bits()),
98            msw_first: Constant::new(match order {
99                WordOrder::LeastSignificantFirst => false,
100                WordOrder::MostSignificantFirst => true,
101            }),
102        }
103    }
104}
105
106#[test]
107fn fifo_expandern_is_synthesizable() {
108    let mut dev = FIFOExpanderN::<4, 32>::new(WordOrder::MostSignificantFirst);
109    dev.connect_all();
110    yosys_validate("fifo_expandern", &generate_verilog(&dev)).unwrap();
111}