rust_hdl_widgets/spi/
master_dynamic_mode.rs

1use crate::dff_setup;
2use crate::spi::master::SPIWiresMaster;
3use crate::synchronizer::BitSynchronizer;
4use crate::{dff::DFF, dff_with_init::DFFWithInit, spi::master::SPIConfig, strobe::Strobe};
5use rust_hdl_core::prelude::*;
6
7#[derive(Copy, Clone, PartialEq, Debug, LogicState)]
8enum SPIState {
9    Idle,
10    SetMode,
11    Activate,
12    Dwell,
13    LoadBit,
14    MActive,
15    SampleMISO,
16    MIdle,
17    Finish,
18}
19
20#[derive(Copy, Clone)]
21pub struct SPIConfigDynamicMode {
22    pub clock_speed: u64,
23    pub cs_off: bool,
24    pub mosi_off: bool,
25    pub speed_hz: u64,
26}
27
28impl From<SPIConfig> for SPIConfigDynamicMode {
29    fn from(x: SPIConfig) -> Self {
30        SPIConfigDynamicMode {
31            clock_speed: x.clock_speed,
32            cs_off: x.cs_off,
33            mosi_off: x.mosi_off,
34            speed_hz: x.speed_hz,
35        }
36    }
37}
38
39impl Into<SPIConfig> for SPIConfigDynamicMode {
40    fn into(self) -> SPIConfig {
41        SPIConfig {
42            clock_speed: self.clock_speed,
43            cs_off: self.cs_off,
44            mosi_off: self.mosi_off,
45            speed_hz: self.speed_hz,
46            cpha: false,
47            cpol: false,
48        }
49    }
50}
51
52#[derive(LogicBlock)]
53pub struct SPIMasterDynamicMode<const N: usize> {
54    pub clock: Signal<In, Clock>,
55    pub bits_outbound: Signal<In, Bits<16>>,
56    pub data_outbound: Signal<In, Bits<N>>,
57    pub data_inbound: Signal<Out, Bits<N>>,
58    pub start_send: Signal<In, Bit>,
59    pub transfer_done: Signal<Out, Bit>,
60    pub continued_transaction: Signal<In, Bit>,
61    pub wires: SPIWiresMaster,
62    pub busy: Signal<Out, Bit>,
63    register_out: DFF<Bits<N>>,
64    register_in: DFF<Bits<N>>,
65    state: DFF<SPIState>,
66    strobe: Strobe<32>,
67    pointer: DFF<Bits<16>>,
68    pointerm1: Signal<Local, Bits<16>>,
69    clock_state: DFF<Bit>,
70    done_flop: DFF<Bit>,
71    msel_flop: DFFWithInit<Bit>,
72    mosi_flop: DFF<Bit>,
73    miso_synchronizer: BitSynchronizer,
74    continued_save: DFF<Bit>,
75    cs_off: Constant<Bit>,
76    mosi_off: Constant<Bit>,
77    cpha_flop: DFF<Bit>,
78    cpol_flop: DFF<Bit>,
79}
80
81impl<const N: usize> SPIMasterDynamicMode<N> {
82    pub fn new(config: SPIConfigDynamicMode) -> Self {
83        assert!(8 * config.speed_hz <= config.clock_speed);
84        Self {
85            clock: Default::default(),
86            bits_outbound: Default::default(),
87            data_outbound: Default::default(),
88            data_inbound: Default::default(),
89            start_send: Default::default(),
90            transfer_done: Default::default(),
91            continued_transaction: Default::default(),
92            wires: Default::default(),
93            busy: Default::default(),
94            register_out: Default::default(),
95            register_in: Default::default(),
96            state: Default::default(),
97            strobe: Strobe::new(config.clock_speed, 4.0 * config.speed_hz as f64),
98            pointer: Default::default(),
99            pointerm1: Default::default(),
100            clock_state: Default::default(),
101            done_flop: Default::default(),
102            msel_flop: DFFWithInit::new(config.cs_off),
103            mosi_flop: Default::default(),
104            miso_synchronizer: Default::default(),
105            continued_save: Default::default(),
106            cs_off: Constant::new(config.cs_off),
107            mosi_off: Constant::new(config.mosi_off),
108            cpha_flop: Default::default(),
109            cpol_flop: Default::default(),
110        }
111    }
112}
113
114impl<const N: usize> Logic for SPIMasterDynamicMode<N> {
115    #[hdl_gen]
116    fn update(&mut self) {
117        // Wire up the clocks.
118        dff_setup!(
119            self,
120            clock,
121            register_out,
122            register_in,
123            state,
124            pointer,
125            clock_state,
126            done_flop,
127            msel_flop,
128            mosi_flop,
129            continued_save,
130            cpha_flop,
131            cpol_flop
132        );
133        clock!(self, clock, miso_synchronizer, strobe);
134        // Activate the baud strobe
135        self.strobe.enable.next = true;
136        // Connect the MISO synchronizer to the input line
137        self.miso_synchronizer.sig_in.next = self.wires.miso.val();
138        // Connect the rest of the SPI lines to the flops
139        self.wires.mclk.next = self.clock_state.q.val();
140        self.wires.mosi.next = self.mosi_flop.q.val();
141        self.wires.msel.next = self.msel_flop.q.val();
142        // Connect the output signals to the internal registers
143        self.data_inbound.next = self.register_in.q.val();
144        self.transfer_done.next = self.done_flop.q.val();
145        // Latch prevention
146        self.done_flop.d.next = false;
147        self.pointerm1.next = self.pointer.q.val() - 1;
148        self.busy.next = self.state.q.val() != SPIState::Idle;
149        // The main state machine
150        match self.state.q.val() {
151            SPIState::Idle => {
152                self.clock_state.d.next = self.cpol_flop.q.val();
153                if self.start_send.val() {
154                    // Capture the outgoing data in our register
155                    self.register_out.d.next = self.data_outbound.val();
156                    self.state.d.next = SPIState::SetMode; // Transition to the SetMode state - allows the clock to settle
157                    self.pointer.d.next = self.bits_outbound.val() & 0x00FF; // set bit pointer to number of bit to send (1 based)
158                                                                             // We bind the top two bits of the outbound register to the SPI mode.
159                    self.cpha_flop.d.next = self.bits_outbound.val().get_bit(9);
160                    self.cpol_flop.d.next = self.bits_outbound.val().get_bit(8);
161                    self.register_in.d.next = 0.into(); // Clear out the input store register
162                    self.continued_save.d.next = self.continued_transaction.val();
163                } else {
164                    if !self.continued_save.q.val() {
165                        self.msel_flop.d.next = self.cs_off.val(); // Set the chip select signal to be "off"
166                    }
167                }
168                self.mosi_flop.d.next = self.mosi_off.val(); // Set the mosi signal to be "off"
169            }
170            SPIState::SetMode => {
171                self.clock_state.d.next = self.cpol_flop.q.val();
172                // Wait for the clock polarity to settle
173                if self.strobe.strobe.val() {
174                    self.state.d.next = SPIState::Activate;
175                }
176            }
177            SPIState::Activate => {
178                if self.strobe.strobe.val() {
179                    self.msel_flop.d.next = !self.cs_off.val(); // Activate the chip select
180                    self.state.d.next = SPIState::Dwell;
181                }
182            }
183            SPIState::Dwell => {
184                if self.strobe.strobe.val() {
185                    // Dwell timeout has reached zero
186                    self.state.d.next = SPIState::LoadBit; // Transition to the loadbit state
187                }
188            }
189            SPIState::LoadBit => {
190                if self.pointer.q.val().any() {
191                    // We have data to send
192                    self.mosi_flop.d.next = self
193                        .register_out
194                        .q
195                        .val()
196                        .get_bit(self.pointerm1.val().index()); // Fetch the corresponding bit out of the register
197                    self.pointer.d.next = self.pointerm1.val(); // Decrement the pointer
198                    self.state.d.next = SPIState::MActive; // Move to the hold mclock low state
199                    self.clock_state.d.next = self.cpol_flop.q.val() ^ self.cpha_flop.q.val();
200                } else {
201                    self.mosi_flop.d.next = self.mosi_off.val(); // Set the mosi signal to be "off"
202                    self.clock_state.d.next = self.cpol_flop.q.val();
203                    self.state.d.next = SPIState::Finish; // No data, go back to idle
204                }
205            }
206            SPIState::MActive => {
207                if self.strobe.strobe.val() {
208                    self.state.d.next = SPIState::SampleMISO;
209                }
210            }
211            SPIState::SampleMISO => {
212                self.register_in.d.next = self.register_in.q.val().replace_bit(
213                    self.pointer.q.val().index(),
214                    self.miso_synchronizer.sig_out.val(),
215                );
216                self.clock_state.d.next = !self.clock_state.q.val();
217                self.state.d.next = SPIState::MIdle;
218            }
219            SPIState::MIdle => {
220                if self.strobe.strobe.val() {
221                    self.state.d.next = SPIState::LoadBit;
222                }
223            }
224            SPIState::Finish => {
225                if self.strobe.strobe.val() {
226                    self.done_flop.d.next = true; // signal the transfer is complete
227                    self.state.d.next = SPIState::Idle;
228                }
229            }
230            _ => {
231                self.state.d.next = SPIState::Idle;
232            }
233        }
234    }
235}
236
237#[test]
238fn test_spi_master_dynamic_mode_is_synthesizable() {
239    let config = SPIConfigDynamicMode {
240        clock_speed: 48_000_000,
241        cs_off: true,
242        mosi_off: false,
243        speed_hz: 1_000_000,
244    };
245    let mut dev = SPIMasterDynamicMode::<64>::new(config);
246    dev.connect_all();
247    yosys_validate("spi_master_dyn_mode", &generate_verilog(&dev)).unwrap();
248}