use celox::{LoweringPhase, ParserError, SchedulerError, Simulator, SimulatorErrorKind};
fn assert_analyzer_or_sir(
result: Result<Simulator, celox::SimulatorError>,
sir_check: impl FnOnce(&celox::SimulatorError),
) {
let err = result.expect_err("Expected an error");
match err.kind() {
SimulatorErrorKind::Analyzer(_) => {} SimulatorErrorKind::SIRParser(_) => sir_check(&err),
other => panic!("Expected Analyzer or SIRParser error, got: {other:?}"),
}
}
fn assert_comb_loop_analyzer_or_sir(
result: Result<Simulator, celox::SimulatorError>,
expected_sir_blocks: Option<usize>,
) {
let err = result.expect_err("Expected a combinational-loop error");
match err.kind() {
SimulatorErrorKind::Analyzer(errors) => assert!(
errors
.iter()
.any(|err| matches!(err, veryl_analyzer::AnalyzerError::CombinationalLoop { .. })),
"Expected Analyzer CombinationalLoop error, got: {errors:?}"
),
SimulatorErrorKind::SIRParser(
ParserError::Scheduler(SchedulerError::CombinationalLoop { blocks })
| ParserError::SchedulerWithLocation {
error: SchedulerError::CombinationalLoop { blocks },
..
},
) => {
if let Some(expected) = expected_sir_blocks {
assert_eq!(blocks.len(), expected);
}
}
other => panic!("Expected CombinationalLoop error, got: {other:?}"),
}
}
fn assert_ff_non_progress_analyzer_or_runtime(code: &str) {
let mut sim = match Simulator::builder(code, "Top").build() {
Ok(sim) => sim,
Err(err) => {
match err.kind() {
SimulatorErrorKind::Analyzer(errors) => assert!(
errors.iter().any(|error| matches!(
error,
veryl_analyzer::AnalyzerError::InvalidForStep { .. }
)),
"Expected InvalidForStep analyzer error, got: {errors:?}"
),
other => panic!("Expected Analyzer or runtime loop error, got: {other:?}"),
}
return;
}
};
let clk = sim.event("clk");
let count = sim.signal("count");
sim.modify(|io| io.set(count, 4u8)).unwrap();
let err = sim.tick(clk).unwrap_err();
assert_eq!(
err.to_string(),
"Non-progressing for loop in always_ff (loop variable `i`): i"
);
}
#[test]
fn test_scheduler_loop_detection() {
let code = r#"
module Top (a: input logic, o: output logic) {
var x: logic;
var y: logic;
var z: logic;
assign x = y;
assign y = z;
assign z = x;
assign o = x;
}
"#;
assert_comb_loop_analyzer_or_sir(Simulator::builder(code, "Top").build(), Some(3));
}
#[test]
fn test_combinational_loop() {
let code = r#"
module Top () {
var y: logic;
var x: logic;
always_comb {
x = y;
}
always_comb {
y = x;
}
}
"#;
assert_comb_loop_analyzer_or_sir(Simulator::builder(code, "Top").build(), Some(2));
}
#[test]
fn test_combinational_loop_in_single_block() {
let code = r#"
module Top () {
var y: logic;
var x: logic;
always_comb {
x = y;
y = x;
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert_analyzer_or_sir(result, |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("CombinationalLoop") || msg.contains("unassign"),
"Expected loop or unassign error, got: {e:?}"
);
});
}
#[test]
fn test_dynamic_index_bit_disparity_bullying() {
let code = r#"
module Top (
j: input logic
) {
var x: logic[2,4];
always_comb{ x[j][0] = x[j][1]; }
always_comb{ x[j][1] = x[j][0]; }
}
"#;
let result = Simulator::builder(code, "Top").build();
assert_analyzer_or_sir(result, |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("CombinationalLoop") || msg.contains("MultipleDriver"),
"Expected loop or multiple-driver error, got: {e:?}"
);
});
}
#[test]
fn test_dynamic_access_with_static_precedence_is_ok() {
let code = r#"
module Top (j: input logic, a: input logic) {
var x: logic[2,4];
always_comb {
x[0][0] = a;
x[j][1] = x[0][0];
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"Should be OK because x[0][0] is defined before being read. but {:?}",
result.err()
);
}
#[test]
fn test_dynamic_access_self_loop_is_err() {
let code = r#"
module Top (j: input logic) {
var x: logic<8> [2];
always_comb {
x[j] = x[j] + 1;
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert_analyzer_or_sir(result, |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("CombinationalLoop") || msg.contains("MultipleDriver"),
"Expected loop or multiple-driver error, got: {e:?}"
);
});
}
#[test]
fn test_if_without_else_latch_loop() {
let code = r#"
module Top (sel: input logic, a: input logic) {
var x: logic;
var y: logic;
always_comb {
if sel {
x = a;
}
y = x;
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert_analyzer_or_sir(result, |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("CombinationalLoop") || msg.contains("unassign"),
"Expected loop or unassign error, got: {e:?}"
);
});
}
#[test]
fn test_default_assignment_with_if_is_ok() {
let code = r#"
module Top (sel: input logic, a: input logic<8>) {
var x: logic<8>;
always_comb {
x = 8'h00;
if sel {
x = a;
}
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(result.is_ok());
}
#[test]
fn test_dynamic_write_then_read_is_ok() {
let code = r#"
module Top (i: input logic<2>, j: input logic<2>, a: input logic<8>) {
var x: logic<8> [4];
var y: logic<8>;
always_comb {
x[i] = a;
y = x[j];
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"Dynamic sequence allowed due to analysis complexity"
);
}
#[test]
fn test_runtime_for_loop_state_does_not_trigger_scheduler_loop() {
let code = r#"
module Top (
count: input logic<8>,
o: output logic<8>
) {
var acc: logic<8>;
always_comb {
acc = 0;
for i in 0..count {
acc += i as 8;
}
}
always_comb {
o = acc + 1;
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"runtime ForFold self-state must not be treated as a scheduler loop: {:?}",
result.err()
);
}
#[test]
fn test_runtime_for_break_condition_on_accumulator_does_not_trigger_scheduler_loop() {
let code = r#"
module Top (
count: input logic<8>,
o: output logic<8>
) {
var sum: logic<8>;
always_comb {
sum = 0;
for i in 0..count {
sum += 1;
if sum == 3 {
break;
}
}
}
always_comb {
o = sum;
}
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"runtime ForFold break-state must not be treated as a scheduler loop: {:?}",
result.err()
);
}
#[test]
fn test_runtime_for_loop_external_feedback_is_still_scheduler_loop() {
let code = r#"
module Top (
count: input logic<8>,
o: output logic<8>
) {
var acc: logic<8>;
var y: logic<8>;
always_comb {
acc = 0;
for i in 0..count {
acc += y;
}
}
always_comb {
y = acc;
o = y;
}
}
"#;
assert_comb_loop_analyzer_or_sir(Simulator::builder(code, "Top").build(), Some(2));
}
#[test]
fn test_always_ff_runtime_for_non_progress_is_rejected() {
let code = r#"
module Top (
clk: input clock,
count: input logic<8>,
q: output logic<8>
) {
always_ff (clk) {
q = 0;
for i in 1..count step *= 1 {
q = i as 8;
}
}
}
"#;
assert_ff_non_progress_analyzer_or_runtime(code);
}
#[test]
fn test_always_ff_runtime_for_zero_start_mul_non_progress_is_rejected() {
let code = r#"
module Top (
clk: input clock,
count: input logic<8>,
q: output logic<8>
) {
always_ff (clk) {
q = 0;
for i in 0..count step *= 2 {
q = i as 8;
}
}
}
"#;
assert_ff_non_progress_analyzer_or_runtime(code);
}
#[test]
fn test_zero_step_for_loop_is_rejected_by_analyzer() {
let code = r#"
module Top (
count: input logic<8>,
o: output logic<8>
) {
always_comb {
o = 0;
for i in 0..count step += 0 {
o = i as 8;
}
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("a zero-step loop must be rejected");
match err.kind() {
SimulatorErrorKind::Analyzer(errors) => assert!(errors.iter().any(|error| matches!(
error,
veryl_analyzer::AnalyzerError::InvalidForStep {
cause: veryl_analyzer::analyzer_error::InvalidForStepKind::ZeroStep,
..
}
))),
other => panic!("Expected InvalidForStep::ZeroStep, got: {other:?}"),
}
}
#[test]
fn test_multiple_driver_error() {
let code = r#"
module Top (a: input logic, b: input logic, o: output logic) {
assign o = a;
assign o = b;
}
"#;
let result = Simulator::builder(code, "Top").build();
assert_analyzer_or_sir(result, |e| match e.kind() {
SimulatorErrorKind::SIRParser(
ParserError::Scheduler(SchedulerError::MultipleDriver { .. })
| ParserError::SchedulerWithLocation {
error: SchedulerError::MultipleDriver { .. },
..
},
) => {}
_ => panic!("Expected MultipleDriver error, got: {e:?}"),
});
}
#[test]
fn test_bit_level_false_loop_is_ok() {
let code = r#"
module Top (a: input logic, o: output logic) {
var x: logic<2>;
assign x[0] = x[1];
assign x[1] = a;
assign o = x[0];
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"Variable-level analysis would fail, but bit-level should pass"
);
}
#[test]
fn pass_comb_block_scheduling_combinational_loop() {
let code = r#"
module Top (a: input logic, o: output logic) {
var x: logic<2>;
var y: logic;
always_comb {
x[0] = a;
o = x[1];
}
assign y = x[0];
assign x[1] = y;
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"Block-level analysis would fail, but bit-level should pass"
);
}
#[test]
fn detect_hierarchical_true_concat_feedback_loop() {
let code = r#"
module Child (
a: input logic<3>,
lo: output logic,
) {
assign lo = a[2];
}
module Top (
out: output logic,
) {
var v: logic<3>;
var lo: logic;
inst c: Child (
a: v,
lo: lo,
);
// True loop at bit level: lo -> v[2] -> lo
assign v = {lo, 1'b0, 1'b1};
assign out = lo;
}
"#;
assert_comb_loop_analyzer_or_sir(Simulator::builder(code, "Top").build(), None);
}
#[test]
#[ignore = "Veryl 0.20.1 post-pass2 reports this conservative hierarchical comb loop before celox bit-level analysis"]
fn test_hierarchical_read_slice_feedback_should_not_form_loop() {
let code = r#"
module Child (
a: input logic<2>,
lo: output logic,
) {
assign lo = a[1];
}
module Top (
inp: input logic,
out: output logic,
) {
var v: logic<2>;
var lo: logic;
inst c: Child (
a: v,
lo: lo,
);
// Explicitly acyclic at bit level:
// v[1] = inp, lo = c(v)[=v[1]], v[0] = lo
assign v[1] = inp;
assign v[0] = lo;
assign out = v[0];
}
"#;
let result = Simulator::builder(code, "Top").build();
assert!(
result.is_ok(),
"Bit-level dependency is acyclic, but got error: {:?}",
result.err()
);
let mut sim = Simulator::builder(code, "Top").build().unwrap();
let inp = sim.signal("inp");
let out = sim.signal("out");
sim.modify(|io| io.set(inp, 0u8)).unwrap();
assert_eq!(sim.get(out), 0u8.into());
sim.modify(|io| io.set(inp, 1u8)).unwrap();
assert_eq!(sim.get(out), 1u8.into());
sim.modify(|io| io.set(inp, 0u8)).unwrap();
assert_eq!(sim.get(out), 0u8.into());
}
#[test]
fn test_interface_design_is_currently_accepted() {
let code = r#"
interface BusIf {
var data: logic<8>;
modport mp {
data: inout,
}
}
module Top () {
inst bus: BusIf;
}
"#;
let result = Simulator::builder(code, "Top").build();
match &result {
Err(_) => {} Ok(sim) => {
assert!(
!sim.warnings().is_empty(),
"Expected at least a warning for interface-only design"
);
}
}
}
#[test]
fn test_sv_module_instance_returns_unsupported_parser_error() {
let code = r#"
module Top (
i_clk : input logic,
i_rst_n: input logic,
i_d : input logic,
o_d : output logic,
) {
inst u0: $sv::delay (
i_clk,
i_rst_n,
i_d,
o_d,
);
}
"#;
let result = Simulator::builder(code, "Top").build();
match result.as_ref().map_err(|e| e.kind()) {
Err(SimulatorErrorKind::SIRParser(ParserError::Unsupported {
phase: LoweringPhase::SimulatorParser,
feature,
..
})) => {
assert_eq!(*feature, "systemverilog module instantiation")
}
Err(k) => panic!("expected Unsupported(SimulatorParser) for $sv module, got {k:?}"),
Ok(_) => panic!("expected Unsupported(SimulatorParser) for $sv module, got Ok"),
}
}
#[test]
fn test_testbench_helper_hierarchical_read_returns_error_without_panicking() {
let code = r#"
module Dut () {
var q: logic;
assign q = 1'b1;
}
#[test(t)]
module t {
inst dut: Dut ();
function read_q() -> logic {
return dut.q;
}
initial {
$assert(read_q());
$finish();
}
}
"#;
let outcome = std::panic::catch_unwind(|| Simulator::builder(code, "t").build());
let result = outcome.expect("helper-body hierarchical read must not panic");
match result.as_ref().map_err(|error| error.kind()) {
Err(SimulatorErrorKind::Analyzer(errors)) => assert!(
errors.iter().any(|error| matches!(
error,
veryl_analyzer::AnalyzerError::InvisibleIndentifier { .. }
)),
"expected Veryl InvisibleIndentifier for helper's hierarchical read, got {errors:?}"
),
Err(SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. })) => {
assert_eq!(*feature, "hierarchical variable reference");
}
Err(kind) => panic!(
"expected InvisibleIndentifier or IllegalContext for helper's hierarchical read, got {kind:?}"
),
Ok(_) => panic!(
"expected InvisibleIndentifier or IllegalContext for helper's hierarchical read, got Ok"
),
}
}
#[test]
fn test_selected_testbench_destination_out_of_range_is_rejected() {
let code = r#"
#[test(t)]
module t {
var word: logic<8>;
initial {
word[6 +: 4] = 4'hf;
$finish();
}
}
"#;
let err = Simulator::builder(code, "t")
.build()
.expect_err("out-of-range selected destination must be rejected");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(*feature, "testbench selected destination");
}
other => panic!("expected selected destination geometry error, got {other:?}"),
}
}
#[test]
fn test_expression_testbench_function_selected_destination_is_rejected() {
let code = r#"
module Driver (source: output logic<8>) {
assign source = 8'h05;
}
#[test(t)]
module t {
var source: logic<8>;
inst dut: Driver (source);
function update(x: input logic<8>) -> logic<8> {
var tmp: logic<8>;
tmp = 8'ha0;
tmp[3:0] = x;
return tmp;
}
initial {
$assert(update(source) == 8'ha5);
$finish();
}
}
"#;
let err = Simulator::builder(code, "t")
.build()
.expect_err("selected destination in an expression helper must be rejected");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(
*feature,
"selected destination in expression testbench function"
);
}
other => panic!("expected expression helper selected destination error, got {other:?}"),
}
}
#[test]
fn test_reset_compile_time_expression_duration_is_accepted() {
let code = r#"
#[test(t)]
module t {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen(clk);
initial {
rst.assert(1 + 2);
$finish();
}
}
"#;
Simulator::builder(code, "t")
.build()
.expect("compile-time reset duration expression should be accepted");
}
#[test]
fn test_top_not_found_returns_error() {
let code = r#"
module Foo (a: input logic, b: output logic) {
assign b = a;
}
"#;
let result = Simulator::builder(code, "NonExistentTop").build();
match result.as_ref().map_err(|e| e.kind()) {
Err(SimulatorErrorKind::SIRParser(ParserError::TopNotFound { name })) => {
assert_eq!(name, "NonExistentTop");
}
Err(k) => panic!("expected TopNotFound, got {k:?}"),
Ok(_) => panic!("expected TopNotFound, got Ok"),
}
}
#[test]
fn test_generic_top_returns_error() {
let code = r#"
module GenericPass::<T: type> (
a: input T,
b: output T,
) {
assign b = a;
}
"#;
let result = Simulator::builder(code, "GenericPass").build();
match result.as_ref().map_err(|e| e.kind()) {
Err(SimulatorErrorKind::SIRParser(ParserError::GenericTop { name })) => {
assert_eq!(name, "GenericPass");
}
Err(k) => panic!("expected GenericTop, got {k:?}"),
Ok(_) => panic!("expected GenericTop, got Ok"),
}
}
#[test]
fn test_module_param_type_generic_argument_error_has_hint() {
let code = r#"
interface Bus::<T: type> {
var data: T;
modport consumer {
data: input,
}
}
module Top #(
param T: type = logic<8>,
) (
bus: modport Bus::<T>::consumer,
) {}
"#;
let result = Simulator::builder(code, "Top").build();
let err = result.expect_err("expected analyzer error");
match err.kind() {
SimulatorErrorKind::Analyzer(errors) => {
assert!(
format!("{errors:?}").contains("UnresolvableGenericExpression"),
"expected UnresolvableGenericExpression, got: {errors:?}"
);
}
other => panic!("expected analyzer error, got: {other:?}"),
}
let rendered = err.to_string();
assert!(
rendered.contains("if this is a module `param T: type`"),
"expected Celox hint in rendered error, got:\n{rendered}"
);
assert!(
rendered.contains("module ModuleName::<T: type>"),
"expected generic parameter suggestion in rendered error, got:\n{rendered}"
);
}
#[test]
fn test_comb_function_body_rejects_system_function_call() {
let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
) -> logic<8> {
return $countones(x);
}
always_comb {
q = f(d);
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("system function call in comb function body")
|| msg.contains("unresolved factor in comb expression"),
"Expected system function call error, got: {e:?}"
);
});
}
#[test]
fn test_comb_function_body_rejects_dynamic_for_break() {
let code = r#"
module Top (
count: input logic<3>,
d: input logic<4>,
q: output logic<8>,
) {
function f (
n: input logic<3>,
x: input logic<4>,
) -> logic<8> {
var tmp: logic<8>;
tmp = 8'd0;
for i in 0..n {
if x[i] {
tmp = i + 8'd1;
break;
}
}
return tmp;
}
always_comb {
q = f(count, d);
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("break in dynamic function-local for"),
"Expected dynamic function-local for break error, got: {e:?}"
);
});
}
#[test]
fn test_comb_function_body_rejects_nested_break_in_dynamic_for_due_to_analyzer_unroll() {
let code = r#"
module Top (
count: input logic<3>,
d: input logic<4>,
q: output logic<8>,
) {
function f (
n: input logic<3>,
x: input logic<4>,
) -> logic<8> {
var tmp: logic<8>;
tmp = 8'd0;
for i in 0..n {
for j in 0..4 {
if x[j] {
break;
}
}
tmp = tmp + 8'd1;
}
return tmp;
}
always_comb {
q = f(count, d);
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("break in dynamic function-local for"),
"Expected nested break case to stay unsupported until analyzer preserves loop ownership, got: {e:?}"
);
});
}
#[test]
fn test_ff_function_call_rejects_packed_concat_for_unpacked_array_formal() {
let code = r#"
module Top (
clk: input clock,
in_hi: input logic<4>,
in_lo: input logic<4>,
out_q: output logic<4>
) {
function f (x: input logic<4>[2]) -> logic<4> {
return x[1];
}
always_ff (clk) {
out_q = f({in_hi, in_lo});
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("actual expression shape does not match unpacked array formal"),
"Expected unpacked array formal shape error, got: {e:?}"
);
});
}
#[test]
fn test_ff_function_call_rejects_wrapped_packed_concat_for_unpacked_array_formal() {
let code = r#"
module Top (
clk: input clock,
in_hi: input logic<4>,
in_lo: input logic<4>,
out_q: output logic<4>
) {
function f (x: input logic<4>[2]) -> logic<4> {
return x[1];
}
always_ff (clk) {
out_q = f(({in_hi, in_lo}) as u8);
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("actual expression shape does not match unpacked array formal"),
"Expected wrapped packed concat shape error, got: {e:?}"
);
});
}
#[test]
fn test_ff_function_call_rejects_mismatched_unpacked_array_shape() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>
) {
function f (x: input logic<8>[2, 2]) -> logic<8> {
return x[1][0];
}
always_ff (clk) {
out_q = f('{'{8'h11, 8'h22, 8'h33}});
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
let msg = format!("{e:?}");
assert!(
msg.contains("actual expression shape does not match unpacked array formal"),
"Expected mismatched unpacked shape error, got: {e:?}"
);
});
}
#[test]
fn test_comb_void_function_call_in_expression_is_illegal_context() {
let code = r#"
module Top (q: output logic) {
function f () {
}
always_comb {
q = f();
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
match e.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(*feature, "void function call in comb expression");
}
k => panic!("expected IllegalContext for void comb function expression, got {k:?}"),
}
});
}
#[test]
fn test_ff_void_function_call_in_expression_is_illegal_context() {
let code = r#"
module Top (clk: input clock, q: output logic) {
function f () {
}
always_ff (clk) {
q = f();
}
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
match e.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(*feature, "void function call in expression");
}
k => panic!("expected IllegalContext for void FF function expression, got {k:?}"),
}
});
}
#[test]
fn test_ff_array_literal_multiple_default_is_illegal_context() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>[2]
) {
var r: logic<8>[2];
always_ff (clk) {
r = '{default: 8'h11, default: 8'h22};
}
assign out_q = r;
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
match e.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(*feature, "array literal multiple default");
}
k => panic!("expected IllegalContext for multiple default array literal, got {k:?}"),
}
});
}
#[test]
fn test_ff_array_literal_non_constant_repeat_is_illegal_context() {
let code = r#"
module Top (
clk: input clock,
n: input logic<2>,
out_q: output logic<8>[2]
) {
var r: logic<8>[2];
always_ff (clk) {
r = '{8'h11 repeat n};
}
assign out_q = r;
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
match e.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(*feature, "array literal non-constant repeat");
}
k => panic!("expected IllegalContext for non-constant repeat array literal, got {k:?}"),
}
});
}
#[test]
fn test_ff_function_argument_array_literal_non_constant_repeat_is_rejected_by_analyzer() {
let code = r#"
module Top (
clk: input clock,
n: input logic<2>,
out_q: output logic<8>
) {
function f (x: input logic<8>[2]) -> logic<8> {
return x[1];
}
always_ff (clk) {
out_q = f('{8'h11 repeat n});
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("non-constant repeat must be rejected");
match err.kind() {
SimulatorErrorKind::Analyzer(errors) => assert!(
errors
.iter()
.any(|error| matches!(error, veryl_analyzer::AnalyzerError::InvalidOperand { .. })),
"expected analyzer InvalidOperand for non-constant repeat, got: {errors:?}"
),
other => panic!("expected analyzer InvalidOperand for non-constant repeat, got: {other:?}"),
}
}
#[test]
fn test_ff_function_argument_array_literal_multiple_default_is_rejected_by_analyzer() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>
) {
function f (x: input logic<8>[2]) -> logic<8> {
return x[1];
}
always_ff (clk) {
out_q = f('{default: 8'h11, default: 8'h22});
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("multiple defaults must be rejected");
match err.kind() {
SimulatorErrorKind::Analyzer(errors) => assert!(
errors.iter().any(|error| matches!(
error,
veryl_analyzer::AnalyzerError::MultipleDefault { .. }
)),
"expected analyzer MultipleDefault for array literal, got: {errors:?}"
),
other => panic!("expected analyzer MultipleDefault for array literal, got: {other:?}"),
}
}
#[test]
fn test_ff_function_call_rejects_unpacked_input_aliased_by_later_effect() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>
) {
var samples: logic<8>[2];
function pick (
values: input logic<8>[2],
ignored: input logic<8>
) -> logic<8> {
return values[0];
}
function update (value: output logic<8>) -> logic<8> {
value = 8'h00;
return 8'h00;
}
always_ff (clk) {
out_q = pick(samples, update(samples[0]));
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("an unpacked input must not observe a later aliased output effect lazily");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 43);
assert_eq!(*feature, "unpacked function argument aliases later effect");
}
other => panic!("expected unpacked input aliasing error, got: {other:?}"),
}
}
#[test]
fn test_ff_function_call_rejects_unpacked_input_aliased_by_later_callee_write() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>
) {
var samples: logic<8>[2];
function pick (
values: input logic<8>[2],
ignored: input logic<8>
) -> logic<8> {
return values[0];
}
function update () -> logic<8> {
samples[0] = 8'h00;
return 8'h00;
}
always_ff (clk) {
out_q = pick(samples, update());
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("an unpacked input must not observe a later callee write lazily");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 43);
assert_eq!(*feature, "unpacked function argument aliases later effect");
}
other => panic!("expected unpacked input aliasing error, got: {other:?}"),
}
}
#[test]
fn test_ff_function_call_rejects_selected_unpacked_input_before_callee_index_write() {
let code = r#"
module Top (
clk: input clock,
rows: input logic<8>[2, 2],
out_q: output logic<8>
) {
var index: logic;
function pick (values: input logic<8>[2]) -> logic<8> {
index = 1'b1;
return values[0];
}
always_ff (clk) {
out_q = pick(rows[index]);
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("a selected unpacked input must not observe a callee index write lazily");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 43);
assert_eq!(*feature, "unpacked function argument aliases later effect");
}
other => panic!("expected unpacked input aliasing error, got: {other:?}"),
}
}
#[test]
fn test_ff_function_call_rejects_unpacked_literal_aliased_by_output_index_effect() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>
) {
var changing: logic<8>;
var sink: logic<8>[2];
function pick (
values: input logic<8>[2],
result: output logic<8>
) -> logic<8> {
result = 8'h00;
return values[0];
}
function update (value: output logic<8>) -> logic {
value = 8'h00;
return 1'b0;
}
always_ff (clk) {
out_q = pick('{changing, default: 8'h00}, sink[update(changing)]);
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("an unpacked literal must not observe an output-index effect lazily");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 43);
assert_eq!(*feature, "unpacked function argument aliases later effect");
}
other => panic!("expected unpacked input aliasing error, got: {other:?}"),
}
}
#[test]
fn test_ff_function_runtime_effect_in_for_bound_is_detected() {
let code = r#"
module Top (
clk: input clock,
count: input logic<3>
) {
function observed (x: input logic<3>) -> logic<3> {
$display("bound=%0d", x);
return x;
}
function consume (n: input logic<3>) {
for i in observed(n)..n {}
}
always_ff (clk) {
consume(count);
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("runtime effect in a function-local for bound must not be discarded");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 66);
assert_eq!(
*feature,
"control flow around runtime effect in function body"
);
}
other => panic!("expected effectful for-bound error, got: {other:?}"),
}
}
#[test]
fn test_ff_function_runtime_effect_in_assignment_destination_is_detected() {
let code = r#"
module Top (
clk: input clock,
index: input logic<3>
) {
function observed (x: input logic<3>) -> logic<3> {
$display("index=%0d", x);
return x;
}
function consume (i: input logic<3>) {
var tmp: logic<8>;
tmp = 8'd0;
tmp[observed(i)] = 1'b1;
}
always_ff (clk) {
consume(index);
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("runtime effect in an assignment destination must not be discarded");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 66);
assert_eq!(
*feature,
"effectful assignment destination in function body"
);
}
other => panic!("expected effectful assignment-destination error, got: {other:?}"),
}
}
#[test]
fn test_ff_function_runtime_effect_in_statement_call_output_destination_is_detected() {
let code = r#"
module Top (
clk: input clock,
index: input logic<3>
) {
function observed (x: input logic<3>) -> logic<3> {
$display("index=%0d", x);
return x;
}
function set (value: output logic) {
value = 1'b1;
}
function consume (i: input logic<3>) {
var tmp: logic<8>;
tmp = 8'd0;
set(tmp[observed(i)]);
}
always_ff (clk) {
consume(index);
}
}
"#;
let err = Simulator::builder(code, "Top")
.build()
.expect_err("runtime effect in a call output destination must not be discarded");
match err.kind() {
SimulatorErrorKind::SIRParser(ParserError::Unsupported { issue, feature, .. }) => {
assert_eq!(*issue, 66);
assert_eq!(
*feature,
"effectful function call output destination in function body"
);
}
other => panic!("expected effectful call-output-destination error, got: {other:?}"),
}
}
#[test]
fn test_ff_array_literal_width_overflow_is_illegal_context() {
let code = r#"
module Top (
clk: input clock,
out_q: output logic<8>
) {
var r: logic<8>[1];
always_ff (clk) {
r = '{8'h11, 8'h22, default: 8'h33};
}
assign out_q = r[0];
}
"#;
assert_analyzer_or_sir(Simulator::builder(code, "Top").build(), |e| {
match e.kind() {
SimulatorErrorKind::SIRParser(ParserError::IllegalContext { feature, .. }) => {
assert_eq!(*feature, "array literal width overflow");
}
k => panic!("expected IllegalContext for array literal width overflow, got {k:?}"),
}
});
}