use rand::Rng; use rust_hdl::prelude::*; #[derive(LogicBlock)] struct ControllerTest { to_cpu: FIFOReadController>, from_cpu: FIFOWriteController>, to_cpu_fifo: SyncFIFO, 6, 7, 1>, from_cpu_fifo: SyncFIFO, 6, 7, 1>, controller: BaseController<2>, bridge: Bridge<16, 2, 2>, port: MOSIPort<16>, iport: MISOPort<16>, clock: Signal, } impl Default for ControllerTest { fn default() -> Self { Self { to_cpu: Default::default(), from_cpu: Default::default(), to_cpu_fifo: Default::default(), from_cpu_fifo: Default::default(), controller: Default::default(), bridge: Bridge::new(["port", "iport"]), port: Default::default(), iport: Default::default(), clock: Default::default(), } } } impl Logic for ControllerTest { #[hdl_gen] fn update(&mut self) { // Connect the clocks clock!(self, clock, to_cpu_fifo, from_cpu_fifo, controller); // Connect the test interfaces FIFOWriteController::>::join( &mut self.from_cpu, &mut self.from_cpu_fifo.bus_write, ); FIFOReadResponder::>::join( &mut self.from_cpu_fifo.bus_read, &mut self.controller.from_cpu, ); FIFOReadController::>::join(&mut self.to_cpu, &mut self.to_cpu_fifo.bus_read); FIFOWriteResponder::>::join( &mut self.to_cpu_fifo.bus_write, &mut self.controller.to_cpu, ); // Connect the controller to the bridge SoCBusController::<16, 2>::join(&mut self.controller.bus, &mut self.bridge.upstream); // Connect the MOSI port to node 0 of the bridge SoCPortController::<16>::join(&mut self.bridge.nodes[0], &mut self.port.bus); SoCPortController::<16>::join(&mut self.bridge.nodes[1], &mut self.iport.bus); self.port.ready.next = true; } } #[cfg(test)] fn make_controller_test() -> ControllerTest { let mut uut = ControllerTest::default(); uut.clock.connect(); uut.from_cpu.data.connect(); uut.from_cpu.write.connect(); uut.to_cpu.read.connect(); uut.iport.port_in.connect(); uut.iport.ready_in.connect(); uut.connect_all(); uut } #[test] fn test_controller_test_synthesizes() { let uut = make_controller_test(); let vlog = generate_verilog(&uut); yosys_validate("controller", &vlog).unwrap(); } #[test] fn test_ping_works() { let uut = make_controller_test(); let mut sim = Simulation::new(); sim.add_clock(5, |x: &mut Box| { x.clock.next = !x.clock.val() }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; // Send a PING command wait_clock_true!(sim, clock, x); for iter in 0..10 { wait_clock_cycles!(sim, clock, x, 5); // A ping is 0x01XX, where XX is the code returned by the controller x.from_cpu.data.next = (0x0167 + iter).into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; wait_clock_cycles!(sim, clock, x, 5); // Insert a NOOP x.from_cpu.data.next = 0.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; wait_clock_cycles!(sim, clock, x, 5); } sim.done(x) }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; wait_clock_true!(sim, clock, x); for iter in 0..10 { x = sim.watch(|x| !x.to_cpu.empty.val(), x)?; sim_assert!(sim, x.to_cpu.data.val() == (0x0167 + iter), x); x.to_cpu.read.next = true; wait_clock_cycle!(sim, clock, x); x.to_cpu.read.next = false; } sim.done(x) }); sim.run_traced( Box::new(uut), 5000, std::fs::File::create(vcd_path!("controller_ping.vcd")).unwrap(), ) .unwrap(); } #[test] fn test_write_command_works() { let uut = make_controller_test(); let mut sim = Simulation::new(); sim.add_clock(5, |x: &mut Box| { x.clock.next = !x.clock.val() }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; // Send a PING command wait_clock_true!(sim, clock, x); for iter in 0..10 { wait_clock_cycles!(sim, clock, x, 5); // A write command looks like 0x03XXYYYY, where XX is the address, YYYY is the count // followed by count data elements. // Write the command x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = 0x0300.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Then the count x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = (iter + 1).into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Then the data elements for ndx in 0..(iter + 1) { x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = (0x7870 + ndx).into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; } // Insert a NOOPd x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = 0.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; wait_clock_cycles!(sim, clock, x, 5); } sim.done(x) }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; wait_clock_true!(sim, clock, x); for iter in 0..10 { for ndx in 0..(iter + 1) { x = sim.watch(|x| x.port.strobe_out.val(), x)?; sim_assert!(sim, x.port.port_out.val() == (0x7870 + ndx), x); wait_clock_cycle!(sim, clock, x); } } sim.done(x) }); sim.run_traced( Box::new(uut), 5000, std::fs::File::create(vcd_path!("controller_write.vcd")).unwrap(), ) .unwrap(); } #[test] fn test_read_command_works() { let uut = make_controller_test(); let mut sim = Simulation::new(); sim.add_clock(5, |x: &mut Box| { x.clock.next = !x.clock.val() }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; // Send a PING command wait_clock_true!(sim, clock, x); for iter in 0..10 { wait_clock_cycles!(sim, clock, x, 5); // A read command looks like 0x02XXYYYY, where XX is the address, YYYY is the count // Write the command x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = 0x0201.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Then the count x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = (iter + 1).into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Then wait for the data elements to come back to the CPU for ndx in 0..(iter + 1) { x = sim.watch(|x| !x.to_cpu.empty.val(), x)?; sim_assert_eq!(sim, x.to_cpu.data.val(), 0xBEE0 + ndx, x); x.to_cpu.read.next = true; wait_clock_cycle!(sim, clock, x); x.to_cpu.read.next = false; } // Wait 1 clock cycle, and then issue a POLL command wait_clock_cycle!(sim, clock, x); x.from_cpu.data.next = 0x0401.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Read the result of the poll back x = sim.watch(|x| !x.to_cpu.empty.val(), x)?; // Port should always be ready sim_assert_eq!(sim, x.to_cpu.data.val(), 0xFF01, x); x.to_cpu.read.next = true; wait_clock_cycle!(sim, clock, x); x.to_cpu.read.next = false; wait_clock_cycles!(sim, clock, x, 5); } sim.done(x) }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; wait_clock_true!(sim, clock, x); for iter in 0..10 { wait_clock_cycles!(sim, clock, x, 10); for ndx in 0..(iter + 1) { x.iport.port_in.next = (0xBEE0 + ndx).into(); x.iport.ready_in.next = true; x = sim.watch(|x| x.iport.strobe_out.val(), x)?; wait_clock_cycle!(sim, clock, x); } } sim.done(x) }); sim.run_traced( Box::new(uut), 20000, std::fs::File::create(vcd_path!("controller_read.vcd")).unwrap(), ) .unwrap(); } #[test] fn test_stream_command_works() { let uut = make_controller_test(); let mut sim = Simulation::new(); sim.add_clock(5, |x: &mut Box| { x.clock.next = !x.clock.val() }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; // Send a PING command wait_clock_true!(sim, clock, x); wait_clock_cycles!(sim, clock, x, 5); // A stream command looks like 0x05XX, where XX is the address to stream from // Write the command x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = 0x0501.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Wait until we have collected 100 items for iter in 0..100 { x = sim.watch(|x| !x.to_cpu.empty.val(), x)?; sim_assert!(sim, x.to_cpu.data.val() == 0xBAB0 + iter, x); x.to_cpu.read.next = true; wait_clock_cycle!(sim, clock, x); x.to_cpu.read.next = false; } // Send a stop command (anything non-zero) x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = 0x0501.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // There may be extra data that comes, so discard data until the // CPU fifo is empty... while !x.to_cpu.empty.val() { x.to_cpu.read.next = true; wait_clock_cycle!(sim, clock, x); x.to_cpu.read.next = false; } // Send a ping x = sim.watch(|x| !x.from_cpu.full.val(), x)?; x.from_cpu.data.next = 0x01FF.into(); x.from_cpu.write.next = true; wait_clock_cycle!(sim, clock, x); x.from_cpu.write.next = false; // Wait for it to return x = sim.watch(|x| !x.to_cpu.empty.val(), x)?; sim_assert!(sim, x.to_cpu.data.val() == 0x01FF, x); wait_clock_cycles!(sim, clock, x, 10); sim.done(x) }); sim.add_testbench(move |mut sim: Sim| { let mut x = sim.init()?; wait_clock_true!(sim, clock, x); for ndx in 0..100 { x.iport.port_in.next = (0xBAB0 + ndx).into(); x.iport.ready_in.next = true; x = sim.watch(|x| x.iport.strobe_out.val(), x)?; wait_clock_cycle!(sim, clock, x); x.iport.ready_in.next = false; if rand::thread_rng().gen::() < 0.3 { for _ in 0..(rand::thread_rng().gen::() % 40) { wait_clock_cycle!(sim, clock, x); } } } sim.done(x) }); sim.run_traced( Box::new(uut), 50000, std::fs::File::create(vcd_path!("controller_stream.vcd")).unwrap(), ) .unwrap(); }