rust_hdl_sim/
ads8688_sim.rs

1use rust_hdl_core::prelude::*;
2use rust_hdl_widgets::prelude::*;
3
4#[derive(Copy, Clone, PartialEq, Debug, LogicState)]
5enum State {
6    Init,
7    Ready,
8    GettingCmd,
9    WaitSlaveIdle,
10    DoWrite,
11    DoRead,
12    DoNoop,
13    DoCommand,
14    ReadBack,
15    DoConvert,
16}
17
18#[derive(LogicBlock)]
19pub struct ADS8688Simulator {
20    pub wires: SPIWiresSlave,
21    pub clock: Signal<In, Clock>,
22    // RAM that stores the register contents (these are Program Registers)
23    reg_ram: RAM<Bits<8>, 6>,
24    // Used to split bits out of the SPI message
25    // Organized as: <cmd_flag> <reg_address> <r/w>
26    reg_address: Signal<Local, Bits<6>>,
27    rw_flag: Signal<Local, Bit>,
28    cmd_flag: Signal<Local, Bit>,
29    noop_flag: Signal<Local, Bit>,
30    // The SPI slave device
31    spi_slave: SPISlave<64>,
32    // FSM state
33    state: DFF<State>,
34    // FLOP to hold the register address
35    reg_address_flop: DFF<Bits<6>>,
36    // FLOP to hold the last written value
37    readback_flop: DFF<Bits<8>>,
38    // Rolling counter to emulate conversions
39    conversion_counter: DFF<Bits<12>>,
40    // Command register
41    command_register: DFF<Bits<8>>,
42    // Output register for reading
43    output_register: DFF<Bits<16>>,
44}
45
46fn ram_init_vec() -> Vec<u8> {
47    // Create the initial contents of the RAM.
48    // The following registers are 0xFF, all others are 0x00 by default
49    // 1, 0x16, 0x17, 0x16 + 5, 0x17 + 5, ... (8 times)
50    let mut ram_init = vec![0_u8; 0x40];
51    ram_init[1] = 0xFF;
52    for ch in 0..8 {
53        ram_init[0x15 + ch * 5 + 1] = 0xFF;
54        ram_init[0x15 + ch * 5 + 2] = 0xFF;
55    }
56    ram_init
57}
58
59#[test]
60fn test_ram_init_vec_is_correct() {
61    let vec = ram_init_vec();
62    assert_eq!(vec[1], 0xFF);
63    assert_eq!(vec[0x16], 0xFF);
64    assert_eq!(vec[0x17], 0xFF);
65    assert_eq!(vec[0x39], 0xFF);
66    assert_eq!(vec[0x3A], 0xFF);
67}
68
69impl ADS8688Simulator {
70    pub fn new(config: SPIConfig) -> Self {
71        assert!(config.clock_speed > 10 * config.speed_hz);
72        let reg_ram = ram_init_vec().iter().map(|x| x.to_bits()).into();
73        Self {
74            wires: Default::default(),
75            clock: Default::default(),
76            reg_ram,
77            reg_address: Default::default(),
78            rw_flag: Default::default(),
79            cmd_flag: Default::default(),
80            noop_flag: Default::default(),
81            spi_slave: SPISlave::new(config),
82            state: Default::default(),
83            reg_address_flop: Default::default(),
84            readback_flop: Default::default(),
85            conversion_counter: Default::default(),
86            command_register: Default::default(),
87            output_register: Default::default(),
88        }
89    }
90}
91
92impl Logic for ADS8688Simulator {
93    #[hdl_gen]
94    fn update(&mut self) {
95        // Connect the spi bus
96        SPIWiresSlave::link(&mut self.wires, &mut self.spi_slave.wires);
97        // Clock internal components
98        self.reg_ram.read_clock.next = self.clock.val();
99        self.reg_ram.write_clock.next = self.clock.val();
100        clock!(self, clock, spi_slave);
101        dff_setup!(
102            self,
103            clock,
104            state,
105            readback_flop,
106            reg_address_flop,
107            conversion_counter,
108            output_register,
109            command_register
110        );
111        // Set default values, and unpack the command word
112        self.spi_slave.start_send.next = false;
113        self.cmd_flag.next = self.spi_slave.data_inbound.val().get_bit(7);
114        self.reg_address.next = self.spi_slave.data_inbound.val().get_bits::<6>(1);
115        self.rw_flag.next = self.spi_slave.data_inbound.val().get_bit(0);
116        self.noop_flag.next = !self.spi_slave.data_inbound.val().get_bits::<8>(0).any();
117        self.reg_ram.read_address.next = self.reg_address_flop.q.val();
118        self.reg_ram.write_address.next = self.reg_address_flop.q.val();
119        self.spi_slave.continued_transaction.next = false;
120        self.spi_slave.bits.next = 0.into();
121        self.spi_slave.data_outbound.next = 0.into();
122        self.reg_ram.write_enable.next = false;
123        self.reg_ram.write_data.next = 0.into();
124        self.spi_slave.disabled.next = false;
125        match self.state.q.val() {
126            State::Init => {
127                if !self.spi_slave.busy.val() {
128                    self.state.d.next = State::Ready;
129                }
130            }
131            State::Ready => {
132                self.spi_slave.continued_transaction.next = true;
133                self.spi_slave.bits.next = 8.into();
134                self.spi_slave.data_outbound.next = 0x00.into();
135                self.spi_slave.start_send.next = true;
136                self.state.d.next = State::GettingCmd;
137            }
138            State::GettingCmd => {
139                self.reg_address_flop.d.next = self.reg_address.val();
140                if self.spi_slave.transfer_done.val() {
141                    self.state.d.next = State::WaitSlaveIdle;
142                    if self.noop_flag.val() {
143                        self.state.d.next = State::DoNoop;
144                    } else {
145                        if self.cmd_flag.val() {
146                            self.state.d.next = State::DoCommand;
147                        } else {
148                            if self.rw_flag.val() {
149                                self.spi_slave.continued_transaction.next = true;
150                                self.spi_slave.bits.next = 8.into();
151                                self.spi_slave.start_send.next = true;
152                                self.state.d.next = State::DoWrite;
153                            } else {
154                                self.spi_slave.continued_transaction.next = true;
155                                self.spi_slave.bits.next = 8.into();
156                                self.spi_slave.start_send.next = true;
157                                self.state.d.next = State::DoRead;
158                            }
159                        }
160                    }
161                }
162            }
163            State::WaitSlaveIdle => {
164                if !self.spi_slave.busy.val() {
165                    self.state.d.next = State::Ready;
166                }
167            }
168            State::DoRead => {
169                if self.spi_slave.transfer_done.val() {
170                    self.spi_slave.continued_transaction.next = true;
171                    self.spi_slave.bits.next = 8.into();
172                    self.spi_slave.data_outbound.next =
173                        bit_cast::<64, 8>(self.reg_ram.read_data.val());
174                    self.spi_slave.start_send.next = true;
175                    self.state.d.next = State::WaitSlaveIdle;
176                }
177            }
178            State::DoWrite => {
179                if self.spi_slave.transfer_done.val() {
180                    self.reg_ram.write_data.next =
181                        self.spi_slave.data_inbound.val().get_bits::<8>(0);
182                    self.readback_flop.d.next = self.spi_slave.data_inbound.val().get_bits::<8>(0);
183                    self.reg_ram.write_enable.next = true;
184                    self.state.d.next = State::ReadBack;
185                }
186            }
187            State::ReadBack => {
188                self.spi_slave.continued_transaction.next = true;
189                self.spi_slave.bits.next = 8.into();
190                self.spi_slave.data_outbound.next = bit_cast::<64, 8>(self.readback_flop.q.val());
191                self.spi_slave.start_send.next = true;
192                self.state.d.next = State::WaitSlaveIdle;
193            }
194            State::DoNoop => {
195                self.state.d.next = State::DoConvert;
196                self.output_register.d.next =
197                    bit_cast::<16, 3>(self.command_register.q.val().get_bits::<3>(2)) << 12
198                        | bit_cast::<16, 12>(self.conversion_counter.q.val());
199                self.conversion_counter.d.next = self.conversion_counter.q.val() + 1;
200            }
201            State::DoCommand => {
202                self.command_register.d.next = self.spi_slave.data_inbound.val().get_bits::<8>(0);
203                self.reg_ram.write_address.next = 0x3F.into();
204                self.reg_ram.write_data.next = self.spi_slave.data_inbound.val().get_bits::<8>(0);
205                self.reg_ram.write_enable.next = true;
206                self.state.d.next = State::DoConvert;
207                self.output_register.d.next =
208                    bit_cast::<16, 3>(self.command_register.q.val().get_bits::<3>(2)) << 12
209                        | bit_cast::<16, 12>(self.conversion_counter.q.val());
210                self.conversion_counter.d.next = self.conversion_counter.q.val() + 1;
211            }
212            State::DoConvert => {
213                self.spi_slave.data_outbound.next =
214                    bit_cast::<64, 16>(self.output_register.q.val());
215                self.spi_slave.continued_transaction.next = true;
216                self.spi_slave.bits.next = 16.into();
217                self.spi_slave.start_send.next = true;
218                self.state.d.next = State::WaitSlaveIdle;
219            }
220            _ => {
221                self.state.d.next = State::Init;
222            }
223        }
224    }
225}
226
227fn basic_spi_config() -> SPIConfig {
228    SPIConfig {
229        clock_speed: 1_000_000,
230        cs_off: true,
231        mosi_off: false,
232        speed_hz: 10_000,
233        cpha: false,
234        cpol: true,
235    }
236}
237
238#[test]
239fn test_ads8688_synthesizes() {
240    let mut uut = ADS8688Simulator::new(basic_spi_config());
241    uut.connect_all();
242    yosys_validate("ads8688", &generate_verilog(&uut)).unwrap();
243}
244
245#[derive(LogicBlock)]
246struct Test8688 {
247    clock: Signal<In, Clock>,
248    master: SPIMaster<64>,
249    adc: ADS8688Simulator,
250}
251
252impl Logic for Test8688 {
253    #[hdl_gen]
254    fn update(&mut self) {
255        clock!(self, clock, master, adc);
256        SPIWiresMaster::join(&mut self.master.wires, &mut self.adc.wires);
257    }
258}
259
260impl Default for Test8688 {
261    fn default() -> Self {
262        Self {
263            clock: Default::default(),
264            master: SPIMaster::new(basic_spi_config()),
265            adc: ADS8688Simulator::new(basic_spi_config()),
266        }
267    }
268}
269
270#[cfg(test)]
271fn mk_test8688() -> Test8688 {
272    let mut uut = Test8688::default();
273    uut.master.bits_outbound.connect();
274    uut.master.start_send.connect();
275    uut.master.continued_transaction.connect();
276    uut.master.data_outbound.connect();
277    uut.connect_all();
278    uut
279}
280
281#[test]
282fn test_yosys_validate_test_fixture() {
283    let uut = mk_test8688();
284    yosys_validate("ads8688_test_1", &generate_verilog(&uut)).unwrap();
285}
286
287#[cfg(test)]
288fn do_spi_txn(
289    bits: u16,
290    value: u64,
291    continued: bool,
292    mut x: Box<Test8688>,
293    sim: &mut Sim<Test8688>,
294) -> Result<(Bits<64>, Box<Test8688>), SimError> {
295    wait_clock_true!(sim, clock, x);
296    x.master.data_outbound.next = value.to_bits();
297    x.master.bits_outbound.next = bits.to_bits();
298    x.master.continued_transaction.next = continued;
299    x.master.start_send.next = true;
300    wait_clock_cycle!(sim, clock, x);
301    x.master.start_send.next = false;
302    x = sim
303        .watch(|x| x.master.transfer_done.val().into(), x)
304        .unwrap();
305    let ret = x.master.data_inbound.val();
306    for _ in 0..50 {
307        wait_clock_cycle!(sim, clock, x);
308    }
309    Ok((ret, x))
310}
311
312#[cfg(test)]
313fn reg_read(
314    reg_index: u32,
315    x: Box<Test8688>,
316    sim: &mut Sim<Test8688>,
317) -> Result<(u8, Box<Test8688>), SimError> {
318    // Shift the register index so that the lower 8 bits are free to hold the result
319    let cmd = (reg_index << 17).into();
320    let result = do_spi_txn(24, cmd, false, x, sim)?;
321    let reg_val = (result.0 & 0xFF).get_bits::<8>(0).to_u8();
322    Ok((reg_val, result.1))
323}
324
325#[cfg(test)]
326fn reg_write(
327    reg_index: u32,
328    reg_value: u32,
329    x: Box<Test8688>,
330    sim: &mut Sim<Test8688>,
331) -> Result<(u8, Box<Test8688>), SimError> {
332    // We will read back the value...
333    let cmd = ((reg_index << 17) | (1 << 16) | (reg_value << 8)).into();
334    let ret = do_spi_txn(24, cmd, false, x, sim)?;
335    Ok((ret.0.get_bits::<8>(0).to_u8(), ret.1))
336}
337
338#[cfg(test)]
339fn cmd_write(
340    cmd_value: u32,
341    x: Box<Test8688>,
342    sim: &mut Sim<Test8688>,
343) -> Result<Box<Test8688>, SimError> {
344    let cmd = (cmd_value << 8).into();
345    let ret = do_spi_txn(16, cmd, false, x, sim)?;
346    Ok(ret.1)
347}
348
349#[test]
350fn test_reg_reads() {
351    let uut = mk_test8688();
352    let mut sim = Simulation::new();
353    sim.add_clock(5, |x: &mut Box<Test8688>| x.clock.next = !x.clock.val());
354    sim.add_testbench(move |mut sim: Sim<Test8688>| {
355        let mut x = sim.init()?;
356        // Wait for reset to complete
357        wait_clock_cycles!(sim, clock, x, 20);
358        let expected = ram_init_vec()
359            .into_iter()
360            .map(|x| x as LiteralType)
361            .collect::<Vec<_>>();
362        let mut reg_val;
363        for ndx in 0..0x3F {
364            println!("Reading register index {}", ndx);
365            (reg_val, x) = reg_read(ndx, x, &mut sim)?;
366            println!("Value {} -> {:x}", ndx, reg_val);
367            sim_assert_eq!(sim, u64::from(reg_val), expected[ndx as usize], x);
368            wait_clock_true!(sim, clock, x);
369        }
370        sim.done(x)
371    });
372    sim.run(Box::new(uut), 1_000_000).unwrap();
373}
374
375#[test]
376fn test_reg_writes() {
377    let uut = mk_test8688();
378    let mut sim = Simulation::new();
379    sim.add_clock(5, |x: &mut Box<Test8688>| x.clock.next = !x.clock.val());
380    sim.add_testbench(move |mut sim: Sim<Test8688>| {
381        let mut x = sim.init()?;
382        // Wait for reset to complete
383        wait_clock_cycles!(sim, clock, x, 20);
384        // Do the first read to initialize the chip
385        let result = do_spi_txn(32, 0x00, false, x, &mut sim)?;
386        x = result.1;
387        let result = reg_write(5, 0xAF, x, &mut sim)?;
388        x = result.1;
389        println!("Write is {}", result.0);
390        sim_assert_eq!(sim, result.0, 0xAF, x);
391        let reg_val;
392        // Now read it back using a read command
393        (reg_val, x) = reg_read(5, x, &mut sim)?;
394        sim_assert_eq!(sim, reg_val, 0xAF, x);
395        sim.done(x)
396    });
397    sim.run(Box::new(uut), 1_000_000).unwrap();
398    //sim.run_to_file(Box::new(uut), 1_000_000, &vcd_path!("ads8688_write.vcd")).unwrap()
399}
400
401#[test]
402fn test_cmd_write() {
403    let uut = mk_test8688();
404    let mut sim = Simulation::new();
405    sim.add_clock(5, |x: &mut Box<Test8688>| x.clock.next = !x.clock.val());
406    sim.add_testbench(move |mut sim: Sim<Test8688>| {
407        let mut x = sim.init()?;
408        // Wait for reset to complete
409        wait_clock_cycles!(sim, clock, x, 20);
410        // Do the first read to initialize the chip
411        let result = do_spi_txn(32, 0x0, false, x, &mut sim)?;
412        x = result.1;
413        x = cmd_write(0xC4, x, &mut sim)?;
414        // Now read it back using a read command
415        let mut reg_val;
416        (reg_val, x) = reg_read(0x3F, x, &mut sim)?;
417        sim_assert_eq!(sim, reg_val, 0xC4, x);
418        // Issue a NOOP
419        x = cmd_write(0x00, x, &mut sim)?;
420        // Now read it back using a read command
421        (reg_val, x) = reg_read(0x3F, x, &mut sim)?;
422        sim_assert_eq!(sim, reg_val, 0xC4, x);
423        sim.done(x)
424    });
425    //    sim.run(Box::new(uut), 1_000_000).unwrap();
426    sim.run_to_file(
427        Box::new(uut),
428        1_000_000,
429        &vcd_path!("ads8688_cmd_write.vcd"),
430    )
431    .unwrap();
432}
433
434#[test]
435fn test_conversion() {
436    let uut = mk_test8688();
437    let mut sim = Simulation::new();
438    sim.add_clock(5, |x: &mut Box<Test8688>| x.clock.next = !x.clock.val());
439    sim.add_testbench(move |mut sim: Sim<Test8688>| {
440        let mut x = sim.init()?;
441        // Wait for reset to complete
442        wait_clock_cycles!(sim, clock, x, 20);
443        // Do the first read to initialize the chip
444        let result = do_spi_txn(32, 0x0, false, x, &mut sim)?;
445        x = result.1;
446        x = cmd_write(0xC8, x, &mut sim)?;
447        // Now read it back using a read command
448        let reg_val;
449        (reg_val, x) = reg_read(0x3F, x, &mut sim)?;
450        sim_assert_eq!(sim, reg_val, 0xC8, x);
451        let mut conversion;
452        for ndx in 0..4 {
453            (conversion, x) = do_spi_txn(24, 0x0, false, x, &mut sim)?;
454            println!("Conversion value {:x}", conversion);
455            sim_assert_eq!(sim, conversion, 0x2002 + ndx, x);
456        }
457        sim.done(x)
458    });
459    sim.run(Box::new(uut), 1_000_000).unwrap();
460    //    sim.run_to_file(Box::new(uut), 1_000_000, &vcd_path!("ads8688_conversion.vcd")).unwrap();
461}
462
463#[test]
464fn test_pipelined_conversion() {
465    let uut = mk_test8688();
466    let mut sim = simple_sim!(Test8688, clock, 200_000_000_000, sim, {
467        let mut x = sim.init()?;
468        // Wait for reset to complete
469        wait_clock_cycles!(sim, clock, x, 20);
470        // Do the first read to initialize the chip
471        let result = do_spi_txn(32, 0x0, false, x, &mut sim)?;
472        x = result.1;
473        // Issue the first command to read from channel 0 - ignore the result
474        x = cmd_write(0xC0, x, &mut sim)?;
475        // Loop over the channels.  For each one, initiate a command to select
476        // the next channel, and capture the current value as the previous
477        // channel conversion.
478        let mut conversion;
479        for ndx in 1..8 {
480            let cmd = (0xC0 + (ndx << 2)) << 16;
481            (conversion, x) = do_spi_txn(24, cmd, false, x, &mut sim)?;
482            println!("Conversion value [{}] -> {:x}", ndx, conversion);
483            sim_assert_eq!(sim, conversion & 0xFFFF, ((ndx - 1) << 12) + ndx + 1, x);
484        }
485        // To get the last channel, we send a noop
486        (conversion, x) = do_spi_txn(24, 0, false, x, &mut sim)?;
487        println!("Conversion tail -> {:x}", conversion);
488        sim_assert_eq!(sim, conversion & 0xFFFF, 0x7009, x);
489        sim.done(x)
490    });
491    //    sim.run(Box::new(uut), 10_000_000).unwrap();
492    sim.run_to_file(Box::new(uut), 1_000_000, &vcd_path!("ads8688_pipeline.vcd"))
493        .unwrap();
494}