rust_hdl_widgets/spi/
master.rs

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