use super::*;
use crate::comb_loop_detect::{
analysis_size, reset_analysis_size, with_partition_extra_atom_limit,
};
fn crossing_writes(count: usize) -> String {
let mut writes = String::new();
for row in 0..count {
writes += &format!("o[{row}] = data[{row}];\n");
}
for bit in 0..count {
writes += &format!("o[index][{bit}] = 1'b0;\n");
}
format!(
"module Fragmented(index: input u32, data: input logic<{count}>[{count}],
o: output logic<{count}>[{count}]) {{
always_comb {{ o = data; {writes} }}
}}"
)
}
#[test]
fn partition_limit_marks_residual_write_expansion_incomplete_and_preserves_parent_cycles() {
for count in [8, 64] {
let code = format!(
"{}
module Top(index: input u32, data: input logic<{count}>[{count}],
o: output logic<{count}>[{count}], independent: output logic) {{
inst child: Fragmented(index, data, o);
assign independent = ~independent;
}}",
crossing_writes(count)
);
with_partition_extra_atom_limit(0, || {
let errors = analyze(&code);
assert_eq!(comb_loop_analysis_is_complete(&code), count == 8);
assert!(
errors.iter().all(|error| match error {
AnalyzerError::CombinationalLoop { identifier, .. }
| AnalyzerError::UnassignVariable { identifier, .. } =>
identifier == "independent",
_ => false,
}),
"{errors:#?}"
);
assert_eq!(errors.iter().filter(|error| matches!(error,
AnalyzerError::CombinationalLoop { identifier, .. } if identifier == "independent"
)).count(), 1, "{errors:#?}");
});
}
}
#[test]
fn partition_limit_does_not_invent_feedback_or_leak_between_modules() {
let code = crossing_writes(64);
with_partition_extra_atom_limit(0, || {
let errors = analyze(&code);
assert!(errors.is_empty(), "{errors:#?}");
assert!(!comb_loop_analysis_is_complete(&code));
let simple =
"module Top(i: input logic<1000003>, o: output logic<1000003>) { assign o = i; }";
assert!(analyze(simple).is_empty());
assert!(comb_loop_analysis_is_complete(simple));
});
assert!(analyze(&code).is_empty());
assert!(comb_loop_analysis_is_complete(&code));
}
#[test]
fn partition_limit_keeps_optimized_read_views_and_rotations_complete() {
with_partition_extra_atom_limit(0, || {
let count = 256;
let reads = (0..count)
.map(|bit| format!("assign o[{bit}] = mem[{bit}][{bit}] ^ mem[index][{bit}];"))
.collect::<Vec<_>>()
.join("\n");
let code = format!(
"module Top(index: input u32, mem: input logic<{count}>[{count}], o: output logic<{count}>) {{ {reads} }}"
);
assert!(analyze(&code).is_empty());
assert!(comb_loop_analysis_is_complete(&code));
let code = "module Top(o: output logic<1000003>) { var a: logic<1000003>; assign a = {o[999999:0], o[1000002:1000000]}; assign o = a; }";
let errors = analyze(code);
assert!(
matches!(errors.as_slice(), [AnalyzerError::CombinationalLoop { .. }]),
"{errors:#?}"
);
assert!(comb_loop_analysis_is_complete(code));
});
}
#[test]
fn partition_limit_default_skips_a_legal_generated_write_matrix_without_diagnostics() {
let code = crossing_writes(1088);
reset_analysis_size();
let errors = analyze(&code);
assert!(errors.is_empty(), "{errors:#?}");
assert_eq!(
analysis_size(),
(0, 0, 0),
"the unfinished partition must not reach graph construction"
);
assert!(!comb_loop_analysis_is_complete(&code));
let whole =
"module Top(i: input logic<1088>[1088], o: output logic<1088>[1088]) { assign o = i; }";
with_partition_extra_atom_limit(0, || {
assert!(analyze(whole).is_empty());
assert!(comb_loop_analysis_is_complete(whole));
});
}
#[test]
fn partition_limit_default_bounds_byte_enabled_memory_next_state() {
for whole_copy in [false, true] {
let initialization = if whole_copy {
"next_data = data;"
} else {
"for row in 0..32768 { next_data[row] = data[row]; }"
};
let code = format!(
r#"module ByteWrite (
data: input logic<512>[32768],
address: input logic<15>,
write_data: input logic<512>,
byte_enable: input logic<64>,
next_data: output logic<512>[32768],
) {{
always_comb {{
{initialization}
for lane in 0..64 {{
if byte_enable[lane] {{
next_data[address][lane * 8 +: 8] = write_data[lane * 8 +: 8];
}}
}}
}}
}}
"#
);
reset_analysis_size();
let errors = analyze(&code);
assert!(errors.is_empty(), "whole_copy={whole_copy}: {errors:#?}");
if whole_copy {
assert_eq!(analysis_size().0, 67);
} else {
assert_eq!(analysis_size(), (0, 0, 0));
}
assert_eq!(comb_loop_analysis_is_complete(&code), whole_copy);
}
}