use celox::Simulation;
#[test]
fn test_can_delay_clock() {
let code = r#"
module Top (clk: input clock) {
always_ff (clk) {}
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", 10, 5);
assert_eq!(vsim.next_event_time(), Some(5));
vsim.step().unwrap();
assert_eq!(vsim.time(), 5);
}
#[test]
fn test_can_schedule_reset() {
let code = r#"
module Top (
clk: input clock,
rst: input reset_async_high,
cnt: output logic<8>
) {
var counter: logic<8>;
always_ff (clk, rst) {
if_reset {
counter = 8'd0;
} else {
counter = counter + 8'd1;
}
}
assign cnt = counter;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", 10, 5);
vsim.schedule("rst", 0, 1).unwrap();
vsim.schedule("rst", 20, 0).unwrap();
vsim.run_until(40).unwrap();
let cnt = vsim.signal("cnt");
assert_eq!(vsim.get(cnt), 2u8.into());
}
#[test]
fn test_cannot_schedule_non_event() {
let code = r#"
module Top (
in_data: input logic,
out_data: output logic
) {
assign out_data = in_data;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
let res = vsim.schedule("in_data", 100, 1);
assert!(res.is_err());
}
#[test]
fn test_mixed_edge_triggering() {
let code = r#"
module Top (
clk: input '_ clock,
clk_n: input '_ clock_negedge,
rst_n: input '_ reset_async_low,
out_p: output logic<8>,
out_n: output logic<8>
) {
var cnt_p: logic<8>;
var cnt_n: logic<8>;
always_ff (clk, rst_n) {
if_reset {
cnt_p = 8'd0;
} else {
cnt_p = cnt_p + 8'd1;
}
}
always_ff (clk_n) {
cnt_n = cnt_n + 8'd1;
}
assign out_p = cnt_p;
assign out_n = cnt_n;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", 10, 5);
vsim.add_clock("clk_n", 10, 5);
vsim.schedule("rst_n", 0, 0).unwrap();
vsim.schedule("rst_n", 12, 1).unwrap();
let out_p = vsim.signal("out_p");
let out_n = vsim.signal("out_n");
vsim.run_until(50).unwrap();
assert_eq!(vsim.get(out_p), 4u8.into());
assert_eq!(vsim.get(out_n), 5u8.into());
}
#[test]
fn test_multiple_clocks() {
let code = r#"
module Top (
clk0: input '_ clock,
clk1: input '_ clock,
cnt0: output logic<8>,
cnt1: output logic<8>
) {
var r_cnt0: logic<8>;
var r_cnt1: logic<8>;
always_ff (clk0) {
r_cnt0 = r_cnt0 + 8'd1;
}
always_ff (clk1) {
r_cnt1 = r_cnt1 + 8'd1;
}
assign cnt0 = r_cnt0;
assign cnt1 = r_cnt1;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk0", 10, 5); vsim.add_clock("clk1", 20, 5);
let cnt0 = vsim.signal("cnt0");
let cnt1 = vsim.signal("cnt1");
vsim.run_until(10).unwrap();
assert_eq!(vsim.get(cnt0), 1u8.into(), "t=10, cnt0");
assert_eq!(vsim.get(cnt1), 1u8.into(), "t=10, cnt1");
vsim.run_until(20).unwrap();
assert_eq!(vsim.get(cnt0), 2u8.into(), "t=20, cnt0");
assert_eq!(vsim.get(cnt1), 1u8.into(), "t=20, cnt1");
vsim.run_until(30).unwrap();
assert_eq!(vsim.get(cnt0), 3u8.into(), "t=30, cnt0");
assert_eq!(vsim.get(cnt1), 2u8.into(), "t=30, cnt1");
vsim.run_until(50).unwrap();
assert_eq!(vsim.get(cnt0), 5u8.into(), "t=50, cnt0");
assert_eq!(vsim.get(cnt1), 3u8.into(), "t=50, cnt1");
}
#[test]
fn test_regular_reset() {
let code = r#"
module Top (
clk: input clock,
rst: input reset,
cnt: output logic<8>
) {
var r_cnt: logic<8>;
always_ff (clk, rst) {
if_reset {
r_cnt = 8'd0;
} else {
r_cnt = r_cnt + 8'd1;
}
}
assign cnt = r_cnt;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", 10, 5);
let rst = vsim.signal("rst");
vsim.modify(|io| {
io.set(rst, 0u8);
})
.unwrap();
vsim.run_until(7).unwrap();
let cnt = vsim.signal("cnt");
assert_eq!(vsim.get(cnt), 0u8.into());
vsim.modify(|io| {
io.set(rst, 1u8);
})
.unwrap();
vsim.run_until(10).unwrap();
vsim.run_until(15).unwrap();
assert_eq!(vsim.get(cnt), 1u8.into());
vsim.modify(|io| {
io.set(rst, 0u8);
})
.unwrap();
assert_eq!(vsim.get(cnt), 1u8.into());
vsim.run_until(25).unwrap();
assert_eq!(vsim.get(cnt), 0u8.into());
vsim.run_until(35).unwrap();
vsim.modify(|io| {
io.set(rst, 1u8);
})
.unwrap();
vsim.run_until(45).unwrap();
assert_eq!(vsim.get(cnt), 1u8.into());
}
#[test]
fn test_odd_clock_period_is_exact() {
let code = r#"
module Top (
clk: input clock,
cnt: output logic<8>
) {
var r_cnt: logic<8>;
always_ff (clk) {
r_cnt = r_cnt + 8'd1;
}
assign cnt = r_cnt;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", 3, 0);
let clk = vsim.signal("clk");
let mut rising = Vec::new();
let mut falling = Vec::new();
while let Some(time) = vsim.next_event_time() {
if time > 30 {
break;
}
vsim.step().unwrap();
if vsim.get(clk) == 1u8.into() {
rising.push(time);
} else {
falling.push(time);
}
}
assert_eq!(
rising,
(0..=10).map(|edge| edge * 3).collect::<Vec<_>>(),
"rising edges of a period-3 clock"
);
assert_eq!(
falling,
(0..10).map(|edge| edge * 3 + 2).collect::<Vec<_>>(),
"the extra odd-period tick belongs to the high phase"
);
}
#[test]
fn test_degenerate_clock_period_still_advances() {
let code = r#"
module Top (
clk: input clock,
cnt: output logic<8>
) {
var r_cnt: logic<8>;
always_ff (clk) {
r_cnt = r_cnt + 8'd1;
}
assign cnt = r_cnt;
}
"#;
for period in [0, 1] {
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", period, 0);
assert_eq!(vsim.clock_periods()[0].1, 2);
let mut times = Vec::new();
for _ in 0..8 {
if let Some(time) = vsim.step().unwrap() {
times.push(time);
}
}
assert_eq!(times, (0..8).collect::<Vec<_>>(), "period {period}");
let cnt = vsim.signal("cnt");
assert_eq!(
vsim.get(cnt),
4u8.into(),
"a period-{period} clock rises at 0, 2, 4 and 6"
);
}
}
#[test]
fn test_run_until_an_earlier_time_changes_nothing() {
let code = r#"
module Top (
clk: input clock,
cnt: output logic<8>
) {
var r_cnt: logic<8>;
always_ff (clk) {
r_cnt = r_cnt + 8'd1;
}
assign cnt = r_cnt;
}
"#;
let mut vsim = Simulation::builder(code, "Top").build().unwrap();
vsim.add_clock("clk", 10, 5);
let cnt = vsim.signal("cnt");
vsim.run_until(40).unwrap();
assert_eq!(vsim.get(cnt), 4u8.into());
vsim.run_until(20).unwrap();
assert_eq!(vsim.time(), 40);
assert_eq!(vsim.get(cnt), 4u8.into());
vsim.run_until(50).unwrap();
assert_eq!(vsim.time(), 50);
assert_eq!(
vsim.get(cnt),
5u8.into(),
"time resumes from where it stopped"
);
}
#[test]
fn clock_time_overflow_is_a_runtime_error() {
let code = "module Top (clk: input clock) { always_ff (clk) {} }";
let mut sim = Simulation::builder(code, "Top").build().unwrap();
sim.add_clock("clk", 2, u64::MAX);
let error = sim.step().unwrap_err();
assert!(
matches!(error, celox::RuntimeErrorCode::Runtime { ref message, .. }
if message.contains("overflows simulation time"))
);
assert_eq!(sim.time(), u64::MAX);
}