SmithsDigitalForge/rust-hdl

Want a Syntax for separating Sequential Logic and Combinatorial Logic

Opened this issue · 0 comments

Hello, I tried to use this attractive project for generating Verilog code but found it behave strange in sequential logic design (with state machine).

From the generated top.v file, I saw both Combinatorial Logic and Sequential Logic were mixed together into one single always @(*) begin /* . . . */ end statement block. Simulation showed that this block is triggered as many times as possible in just one clock (in a self-loop way).

I have checked up the entire document and some key parts of your source code, but failed to find any clue to this problem. Am I missing something important to get it work? Thanks very much!

The Rust code (using a FIFO and a state machine)

use rust_hdl::core::prelude::*;
use rust_hdl::docs::vcd2svg::vcd_to_svg;
use rust_hdl::prelude::SyncFIFO;
use rust_hdl::widgets::prelude::*;

#[derive(Copy, Clone, PartialEq, Debug, LogicState)]
enum State {
    Idle,
    Running,
    Valid,
}

#[derive(LogicBlock)]
pub struct LineBuffer {
    pub reset: Signal<In, Bit>,
    pub enabled: Signal<In, Bit>,
    pub clock: Signal<In, Clock>,
    pub din: Signal<In, Bits<8>>,
    pub dout: Signal<Out, Bits<8>>,
    pub valid: Signal<Out, Bit>,

    fifo: SyncFIFO<Bits<8>, 11, 12, 2048>,
    pub counter: Signal<Local, Bits<11>>,
    pub write_en: Signal<Local, Bit>,
    pub read_en: Signal<Local, Bit>,
    state: DFF<State>,
}

impl Logic for LineBuffer {
    #[hdl_gen]
    fn update(&mut self) {
        dff_setup!(self, clock, state); // <- setup the DFF

        // reset
        if !self.reset.val() {
            self.counter.next = 0.into();
            self.write_en.next = false.into();
            self.read_en.next = false.into();
            self.dout.next = 0.into();
            self.state.d.next = State::Idle;
            self.valid.next = false.into();
            // connect up fifo
            self.fifo.clock.next = self.clock.val();
            self.fifo.bus_write.data.next = self.din.val();
            self.fifo.bus_write.write.next = self.write_en.val();
            self.dout.next = self.fifo.bus_read.data.val();
            self.fifo.bus_read.read.next = self.read_en.val();
        } else if !self.enabled.val() {
            self.state.d.next = self.state.q.val();
            self.valid.next = false.into();
        } else {
            self.write_en.next = true.into();
            match self.state.q.val() {
                State::Idle => {
                    self.state.d.next = State::Running;
                    self.read_en.next = false.into();
                    self.valid.next = false.into();
                }
                State::Running => {
                    if self.counter.val() < (10_u32 - 1).to_bits() {
                        self.counter.next = self.counter.val() + 1;
                        self.state.d.next = State::Running;
                        self.read_en.next = false.into();
                    } else {
                        self.state.d.next = State::Valid;
                        self.read_en.next = true.into();
                    }
                    self.valid.next = false.into();
                }
                State::Valid => {
                    self.state.d.next = State::Valid;
                    self.read_en.next = true.into();
                    self.valid.next = true.into();
                }
            }
        }
        // connect up fifo
        self.fifo.clock.next = self.clock.val();
        self.fifo.bus_write.data.next = self.din.val();
        self.fifo.bus_write.write.next = self.write_en.val();
        self.dout.next = self.fifo.bus_read.data.val();
        self.fifo.bus_read.read.next = self.read_en.val();
    }
}

impl Default for LineBuffer {
    fn default() -> Self {
        Self {
            reset: Default::default(),
            enabled: Default::default(),
            clock: Default::default(),
            din: Default::default(),
            dout: Default::default(),
            valid: Default::default(),
            fifo: Default::default(),
            counter: Default::default(),
            write_en: Default::default(),
            read_en: Default::default(),
            state: Default::default(),
        }
    }
}

fn main() {
    // Build a set of test cases for the circuit.  Use Wrapping to emulate hardware.
    let test_cases = (0..512)
        .map(|k| {
            let reset = if k < 2 { false } else { true };
            let enabled = if k < 3 { false } else { true };
            let data = (k % 256) as u8;
            (reset, enabled, data.to_bits::<8>())
        })
        .collect::<Vec<_>>();

    // v--- construct the circuit
    let mut uut = LineBuffer::default();
    uut.connect_all();
    let veri = generate_verilog(&uut);

    // yosys_validate("", veri.as_str());

    let mut sim = simple_sim!(LineBuffer, clock, 100_000_000, ep, {
        let mut x = ep.init()?; // Get the circuit
        for test_case in &test_cases {
            // +--  This should look familiar.  Same rules as for HDL kernels
            // v    Write to .next, read from .val()
            x.reset.next = test_case.0;
            x.enabled.next = test_case.1;
            // Helpful macro to delay the simulate by 1 clock cycle (to allow the output to update)
            wait_clock_cycle!(ep, clock, x);
            // You can use any standard Rust stuff inside the testbench.
            println!(
                "[din] {:?}, [dout] {:?}, [v] {:?}, [rd] {:?}, [wr] {:?}, [en] {:?}, [rs] {:?}), [ct] {:?}",
                test_case.2,
                x.dout.val(),
                x.valid.val(),
                x.read_en.val(),
                x.write_en.val(),
                x.enabled.val(),
                x.reset.val(),
                x.counter.val(),
            );
        }
        // When the testbench is complete, call done on the endpoint, and pass the circuit back.
        ep.done(x)
    });

    sim.run_to_file(Box::new(uut), 6 * 1000000, "line_buffer.vcd")
        .unwrap();
    vcd_to_svg(
        "line_buffer.vcd",
        "images/line_buffer_all.svg",
        &["uut.clock", "uut.din", "uut.dout", "uut.valid"],
        0,
        4_000_000_000_000,
    )
    .unwrap();
    vcd_to_svg(
        "line_buffer.vcd",
        "images/line_buffer_pulse.svg",
        &["uut.clock", "uut.din", "uut.dout", "uut.valid"],
        900_000_000_000,
        1_500_000_000_000,
    )
    .unwrap();
}