Want a Syntax for separating Sequential Logic and Combinatorial Logic
Opened this issue · 0 comments
wangxiaochuTHU commented
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();
}