rust_hdl_widgets/fifo/
fifo_logic.rs

1use rust_hdl_core::prelude::*;
2
3use crate::{dff::DFF, dff_setup};
4
5// The read side of the circuitry for the FIFO.  Manages the read
6// address
7#[derive(LogicBlock)]
8pub struct FIFOReadLogic<D: Synth, const N: usize, const NP1: usize, const BLOCK_SIZE: u32> {
9    // Clock
10    pub clock: Signal<In, Clock>,
11    // FIFO facing interface
12    pub read: Signal<In, Bit>,
13    pub data_out: Signal<Out, D>,
14    pub empty: Signal<Out, Bit>,
15    pub almost_empty: Signal<Out, Bit>,
16    pub underflow: Signal<Out, Bit>,
17    // Delayed write address
18    pub write_address_delayed: Signal<In, Bits<NP1>>,
19    // RAM read interface (connect to RAM)
20    pub ram_read_address: Signal<Out, Bits<N>>,
21    pub ram_read_data: Signal<In, D>,
22    pub ram_read_clock: Signal<Out, Clock>,
23    // Read address
24    pub read_address_out: Signal<Out, Bits<NP1>>,
25    pub fill_level: Signal<Out, Bits<NP1>>,
26    // Internal details
27    read_address: DFF<Bits<NP1>>,
28    is_empty: Signal<Local, Bit>,
29    is_full: Signal<Local, Bit>,
30    dff_underflow: DFF<Bit>,
31    fifo_address_mask: Constant<Bits<NP1>>,
32    fifo_size: Constant<Bits<NP1>>,
33    block_size: Constant<Bits<NP1>>,
34}
35
36impl<D: Synth, const N: usize, const NP1: usize, const BLOCK_SIZE: u32> Logic
37    for FIFOReadLogic<D, N, NP1, BLOCK_SIZE>
38{
39    #[hdl_gen]
40    fn update(&mut self) {
41        dff_setup!(self, clock, read_address, dff_underflow);
42        // Connect the clocks.
43        self.ram_read_clock.next = self.clock.val();
44        // Compute the is empty flag
45        self.is_empty.next = (self.read_address.q.val() == self.write_address_delayed.val()).into();
46        self.is_full.next = !self.is_empty.val()
47            & ((self.read_address.q.val() & self.fifo_address_mask.val())
48                == (self.write_address_delayed.val() & self.fifo_address_mask.val()));
49        // Estimate the fill level
50        self.fill_level.next = ((self.write_address_delayed.val() & self.fifo_address_mask.val())
51            + self.fifo_size.val()
52            - (self.read_address.q.val() & self.fifo_address_mask.val()))
53            & self.fifo_address_mask.val();
54        if self.is_full.val() {
55            self.fill_level.next = self.fifo_size.val();
56        }
57        // Compute the almost empty signal
58        self.almost_empty.next = (self.fill_level.val() < self.block_size.val()).into();
59        // Propagate the empty signal.
60        self.empty.next = self.is_empty.val();
61        // Set the RAM read address by masking off the lower N bits of the pointer.
62        self.ram_read_address.next =
63            bit_cast::<N, NP1>(self.read_address.q.val() & self.fifo_address_mask.val());
64        // Forward the output of the RAM read to the FIFO interface.
65        self.data_out.next = self.ram_read_data.val();
66        // Assign the read advance based on the outside request
67        // and our availability to read.
68        if self.read.val() & !self.is_empty.val() {
69            self.read_address.d.next = self.read_address.q.val() + 1;
70            // We "forward" by a cycle so that we don't loose a cycle waiting for the
71            // update to propagate through the flip flop.
72            self.ram_read_address.next =
73                bit_cast::<N, NP1>((self.read_address.q.val() + 1) & self.fifo_address_mask.val());
74        } else {
75            self.read_address.d.next = self.read_address.q.val();
76        }
77        self.dff_underflow.d.next =
78            self.dff_underflow.q.val() | (self.is_empty.val() & self.read.val());
79        self.underflow.next = self.dff_underflow.q.val();
80        self.read_address_out.next = self.read_address.q.val();
81    }
82}
83
84impl<D: Synth, const N: usize, const NP1: usize, const BLOCK_SIZE: u32> Default
85    for FIFOReadLogic<D, N, NP1, BLOCK_SIZE>
86{
87    fn default() -> Self {
88        Self {
89            clock: Default::default(),
90            read: Default::default(),
91            data_out: Default::default(),
92            empty: Default::default(),
93            almost_empty: Default::default(),
94            underflow: Default::default(),
95            write_address_delayed: Default::default(),
96            ram_read_address: Default::default(),
97            ram_read_data: Default::default(),
98            ram_read_clock: Default::default(),
99            read_address_out: Default::default(),
100            read_address: Default::default(),
101            is_empty: Default::default(),
102            is_full: Default::default(),
103            fill_level: Default::default(),
104            dff_underflow: Default::default(),
105            fifo_address_mask: Constant::new(((1_u32 << (N)) - 1).to_bits()),
106            fifo_size: Constant::new(Bits::<N>::count().to_bits()),
107            block_size: Constant::new(BLOCK_SIZE.to_bits()),
108        }
109    }
110}
111
112#[test]
113fn fifo_read_is_synthesizable() {
114    let mut dev: FIFOReadLogic<Bits<8>, 8, 9, 4> = Default::default();
115    dev.connect_all();
116    yosys_validate("fifo_read", &generate_verilog(&dev)).unwrap();
117}
118
119#[derive(LogicBlock)]
120pub struct FIFOWriteLogic<D: Synth, const N: usize, const NP1: usize, const BLOCK_SIZE: u32> {
121    pub write: Signal<In, Bit>,
122    pub data_in: Signal<In, D>,
123    pub full: Signal<Out, Bit>,
124    pub almost_full: Signal<Out, Bit>,
125    pub overflow: Signal<Out, Bit>,
126    pub clock: Signal<In, Clock>,
127    pub ram_write_address: Signal<Out, Bits<N>>,
128    pub ram_write_clock: Signal<Out, Clock>,
129    pub ram_write_data: Signal<Out, D>,
130    pub ram_write_enable: Signal<Out, Bit>,
131    pub read_address: Signal<In, Bits<NP1>>,
132    pub write_address_delayed: Signal<Out, Bits<NP1>>,
133    write_address: DFF<Bits<NP1>>,
134    dff_write_address_delay: DFF<Bits<NP1>>,
135    is_empty: Signal<Local, Bit>,
136    is_full: Signal<Local, Bit>,
137    pub fill_level: Signal<Out, Bits<NP1>>,
138    dff_overflow: DFF<Bit>,
139    fifo_address_mask: Constant<Bits<NP1>>,
140    fifo_size: Constant<Bits<NP1>>,
141    almost_full_level: Constant<Bits<NP1>>,
142}
143
144impl<D: Synth, const N: usize, const NP1: usize, const BLOCK_SIZE: u32> Default
145    for FIFOWriteLogic<D, N, NP1, BLOCK_SIZE>
146{
147    fn default() -> Self {
148        assert_eq!(N + 1, NP1);
149        assert!(NP1 < 32);
150        Self {
151            write: Default::default(),
152            data_in: Default::default(),
153            full: Default::default(),
154            almost_full: Default::default(),
155            overflow: Default::default(),
156            clock: Default::default(),
157            ram_write_address: Default::default(),
158            ram_write_clock: Default::default(),
159            ram_write_data: Default::default(),
160            ram_write_enable: Default::default(),
161            read_address: Default::default(),
162            write_address: Default::default(),
163            write_address_delayed: Default::default(),
164            dff_write_address_delay: Default::default(),
165            is_empty: Default::default(),
166            is_full: Default::default(),
167            fill_level: Default::default(),
168            dff_overflow: Default::default(),
169            fifo_address_mask: Constant::new(((1_u32 << (N)) - 1).to_bits()),
170            fifo_size: Constant::new(Bits::<N>::count().to_bits()),
171            almost_full_level: Constant::new((Bits::<N>::count() - (BLOCK_SIZE as u128)).to_bits()),
172        }
173    }
174}
175
176impl<D: Synth, const N: usize, const NP1: usize, const BLOCK_SIZE: u32> Logic
177    for FIFOWriteLogic<D, N, NP1, BLOCK_SIZE>
178{
179    #[hdl_gen]
180    fn update(&mut self) {
181        dff_setup!(
182            self,
183            clock,
184            dff_overflow,
185            write_address,
186            dff_write_address_delay
187        );
188        self.ram_write_clock.next = self.clock.val();
189        // We need a 1 cycle delay on the write address
190        // This ensures we do not try to read a data element on the same
191        // cycle it is written.
192        self.dff_write_address_delay.d.next = self.write_address.q.val();
193        self.write_address_delayed.next = self.dff_write_address_delay.q.val();
194        // Default to not writing
195        self.ram_write_enable.next = false.into();
196        // Calculate the empty field - this is used to determine if we
197        // can read
198        self.is_empty.next =
199            (self.read_address.val() == self.dff_write_address_delay.q.val()).into();
200        // Calculate the is full field.  If the FIFO is not empty, and
201        // the lower N bits of the addresses agree, the FIFO is full
202        self.is_full.next = !self.is_empty.val()
203            & ((self.read_address.val() & self.fifo_address_mask.val())
204                == (self.write_address.q.val() & self.fifo_address_mask.val()));
205        // Compute the fill level - we add N first, since we are subtracting.  And
206        // we mask out the lower N bits, since we are ignoring the wrap levels.
207        // Note that if the FIFO is empty, this calculation will give the wrong
208        // answer, so we need to check the is_empty flag (which uses all N+1 bits).
209        self.fill_level.next = ((self.dff_write_address_delay.q.val()
210            & self.fifo_address_mask.val())
211            + self.fifo_size.val()
212            - (self.read_address.val() & self.fifo_address_mask.val()))
213            & self.fifo_address_mask.val();
214        if self.is_full.val() {
215            self.fill_level.next = self.fifo_size.val();
216        }
217        self.almost_full.next = (self.fill_level.val() >= self.almost_full_level.val()).into();
218        self.full.next = self.is_full.val();
219        // Connect our write address register to the RAM write address
220        self.ram_write_address.next =
221            bit_cast::<N, NP1>(self.write_address.q.val() & self.fifo_address_mask.val());
222        self.ram_write_data.next = self.data_in.val();
223        // Assign the enable for the write based on the outside
224        // request and our availability to write
225        if self.write.val() & !self.is_full.val() {
226            self.write_address.d.next = self.write_address.q.val() + 1;
227            self.ram_write_enable.next = true;
228        } else {
229            self.write_address.d.next = self.write_address.q.val();
230            self.ram_write_enable.next = false;
231        }
232        // Compute the overflow signal - it is latched
233        self.dff_overflow.d.next =
234            self.dff_overflow.q.val() | (self.is_full.val() & self.write.val());
235        self.overflow.next = self.dff_overflow.q.val();
236    }
237}
238
239#[test]
240fn fifo_write_is_synthesizable() {
241    let mut dev: FIFOWriteLogic<Bits<8>, 8, 9, 4> = Default::default();
242    dev.connect_all();
243    yosys_validate("fifo_write", &generate_verilog(&dev)).unwrap();
244}