use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Write;
use crate::lcsre::longest_repeated_substring;
#[derive(Debug, Clone, PartialEq)]
pub enum CsdMultiplierError {
InvalidCharacter,
LengthMismatch,
EmptyCoefficients,
WidthMismatch,
}
pub struct CsdMultiplier {
csd: String,
n: usize,
m: usize,
}
#[derive(Debug, Clone)]
pub struct MultiplierSpec {
pub name: String,
pub csd: String,
pub input_width: usize,
pub max_power: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum TermOp {
Add,
Sub,
}
fn parse_terms(
csd_str: &str,
max_power: usize,
) -> Result<Vec<(usize, TermOp)>, CsdMultiplierError> {
let mut terms = Vec::new();
for (i, c) in csd_str.chars().enumerate() {
let power = max_power - i;
match c {
'+' => terms.push((power, TermOp::Add)),
'-' => terms.push((power, TermOp::Sub)),
'0' => {}
_ => return Err(CsdMultiplierError::InvalidCharacter),
}
}
Ok(terms)
}
fn build_range_expr(csd_str: &str, start: usize, length: usize, max_power: usize) -> String {
let mut expr = String::new();
let mut first = true;
let end = start.saturating_add(length).min(csd_str.len());
for (i, c) in csd_str[start..end].char_indices() {
let power = max_power - (start + i);
match c {
'+' => {
if first {
write!(expr, "x_shift{}", power).unwrap();
first = false;
} else {
write!(expr, " + x_shift{}", power).unwrap();
}
}
'-' => {
if first {
write!(expr, "-x_shift{}", power).unwrap();
first = false;
} else {
write!(expr, " - x_shift{}", power).unwrap();
}
}
_ => {}
}
}
expr
}
fn output_width(input_width: usize, max_power: usize) -> usize {
input_width + max_power
}
impl CsdMultiplier {
pub fn new(csd: &str, n: usize, m: usize) -> Result<Self, CsdMultiplierError> {
if !csd.chars().all(|c| matches!(c, '+' | '-' | '0')) {
return Err(CsdMultiplierError::InvalidCharacter);
}
if csd.len() != m + 1 {
return Err(CsdMultiplierError::LengthMismatch);
}
Ok(Self {
csd: csd.to_string(),
n,
m,
})
}
fn decimal_value(&self) -> i32 {
self.csd.chars().fold(0, |acc, c| {
let acc = acc << 1;
match c {
'+' => acc + 1,
'-' => acc - 1,
'0' => acc,
_ => unreachable!(),
}
})
}
pub fn generate_verilog(&self) -> String {
let mut output = String::new();
self.generate_header(&mut output);
self.generate_wires(&mut output);
self.generate_result_lcsre(&mut output);
writeln!(output, "endmodule").unwrap();
output
}
fn generate_header(&self, output: &mut String) {
writeln!(
output,
"// CSD Multiplier for pattern: {} (value: {})",
self.csd,
self.decimal_value()
)
.unwrap();
writeln!(
output,
"module csd_multiplier (
input signed [{}:0] x, // Input value (signed)
output signed [{}:0] result // Result (signed)
);",
self.n - 1,
self.n + self.m - 1
)
.unwrap();
}
fn get_unique_powers(&self) -> Vec<usize> {
let mut powers: Vec<usize> = self
.csd
.char_indices()
.filter(|(_, c)| *c != '0')
.map(|(i, _)| self.m - i)
.collect();
powers.sort_unstable_by(|a, b| b.cmp(a));
powers.dedup();
powers
}
fn generate_wires(&self, output: &mut String) {
let shift_powers = self.get_unique_powers();
if shift_powers.is_empty() {
return;
}
writeln!(
output,
"\n // Signed shifted versions (Verilog handles sign extension)"
)
.unwrap();
for &power in &shift_powers {
let padding = self.m - power;
writeln!(
output,
" wire signed [{}:0] x_shift{} = $signed({{ {{{}{{x[{}]}}}}, x}}) << {};",
self.n + self.m - 1,
power,
padding,
self.n - 1,
power
)
.unwrap();
}
}
fn generate_result_lcsre(&self, output: &mut String) {
let terms = parse_terms(&self.csd, self.m).unwrap_or_default();
if terms.is_empty() {
writeln!(output, "\n // CSD implementation").unwrap();
writeln!(output, " assign result = 0;").unwrap();
return;
}
let repeated = longest_repeated_substring(&self.csd);
let pat_positions = if repeated.len() > 1 {
let pat_nnz = repeated.chars().filter(|c| *c == '+' || *c == '-').count();
if pat_nnz >= 2 {
let pos = find_pattern_occurrences(&self.csd, &repeated);
if pos.len() >= 2 {
Some((repeated, pos))
} else {
None
}
} else {
None
}
} else {
None
};
if let Some((ref pat, ref positions)) = pat_positions {
let base_pos = positions[0];
let ow = output_width(self.n, self.m);
let pat_expr = build_range_expr(&self.csd, base_pos, pat.len(), self.m);
writeln!(output, "\n // LCSRe: repeated pattern \"{}\"", pat).unwrap();
writeln!(
output,
" wire signed [{}:0] _pat = {};",
ow - 1,
pat_expr
)
.unwrap();
let mut expr = String::new();
let mut cur = 0;
for &pos in positions {
if pos > cur {
let gap = build_range_expr(&self.csd, cur, pos - cur, self.m);
if !gap.is_empty() {
if expr.is_empty() {
expr = gap;
} else {
write!(expr, " + {}", gap).unwrap();
}
}
}
let shift = pos as isize - base_pos as isize;
let pat_ref = if shift == 0 {
"_pat".to_string()
} else {
format!("(_pat >>> {})", shift)
};
if expr.is_empty() {
expr = pat_ref;
} else {
write!(expr, " + {}", pat_ref).unwrap();
}
cur = pos + pat.len();
}
if cur < self.csd.len() {
let suffix = build_range_expr(&self.csd, cur, self.csd.len() - cur, self.m);
if !suffix.is_empty() {
write!(expr, " + {}", suffix).unwrap();
}
}
writeln!(output, "\n // CSD implementation (LCSRe optimized)").unwrap();
writeln!(output, " assign result = {};", expr).unwrap();
} else {
writeln!(output, "\n // CSD implementation with signed arithmetic").unwrap();
let (first_power, first_op) = terms[0];
let mut expr = format!(
"{}x_shift{}",
if first_op == TermOp::Sub { "-" } else { "" },
first_power
);
for (power, op) in &terms[1..] {
match op {
TermOp::Add => write!(expr, " + x_shift{}", power).unwrap(),
TermOp::Sub => write!(expr, " - x_shift{}", power).unwrap(),
}
}
writeln!(output, " assign result = {};", expr).unwrap();
}
}
}
fn find_pattern_occurrences(csd_str: &str, pattern: &str) -> Vec<usize> {
let mut positions = Vec::new();
let mut pos = 0;
while let Some(found) = csd_str[pos..].find(pattern) {
let absolute = pos + found;
positions.push(absolute);
pos = absolute + pattern.len();
}
positions
}
fn count_nnz(s: &str) -> usize {
s.chars().filter(|c| *c == '+' || *c == '-').count()
}
fn build_coeff_expr(
csd: &str,
max_power: usize,
pattern: &str,
cse_base_pos: usize,
cse_name: &str,
) -> String {
if pattern.is_empty() {
return build_range_expr(csd, 0, csd.len(), max_power);
}
let positions = find_pattern_occurrences(csd, pattern);
let mut parts: Vec<String> = Vec::new();
let mut cur = 0;
for pos in positions {
if pos > cur {
let gap = build_range_expr(csd, cur, pos - cur, max_power);
if !gap.is_empty() {
parts.push(gap);
}
}
let shift = pos as isize - cse_base_pos as isize;
if shift == 0 {
parts.push(cse_name.to_string());
} else {
parts.push(format!("({} >>> {})", cse_name, shift));
}
cur = pos + pattern.len();
}
if cur < csd.len() {
let gap = build_range_expr(csd, cur, csd.len() - cur, max_power);
if !gap.is_empty() {
parts.push(gap);
}
}
if parts.is_empty() {
return String::new();
}
let mut result = parts[0].clone();
for p in &parts[1..] {
write!(result, " + {}", p).unwrap();
}
result
}
fn find_cross_patterns(csd_list: &[String]) -> HashMap<String, Vec<(usize, usize)>> {
let mut patterns: HashMap<String, Vec<(usize, usize)>> = HashMap::new();
for (ci, csd) in csd_list.iter().enumerate() {
let n = csd.len();
for i in 0..n {
for j in (i + 2)..=n {
let sub: String = csd[i..j].to_string();
if count_nnz(&sub) >= 2 {
patterns.entry(sub).or_default().push((ci, i));
}
}
}
}
patterns.retain(|_, occ: &mut Vec<(usize, usize)>| {
let unique: HashSet<usize> = occ.iter().map(|(ci, _)| *ci).collect();
unique.len() >= 2
});
patterns
}
pub fn generate_csd_multiplier(
csd_str: &str,
input_width: usize,
max_power: usize,
) -> Result<String, CsdMultiplierError> {
let len = csd_str.len();
if len != max_power + 1 {
return Err(CsdMultiplierError::LengthMismatch);
}
for c in csd_str.chars() {
if c != '+' && c != '-' && c != '0' {
return Err(CsdMultiplierError::InvalidCharacter);
}
}
let terms = parse_terms(csd_str, max_power)?;
let ow = output_width(input_width, max_power);
let mut verilog = String::new();
writeln!(verilog).unwrap();
writeln!(verilog, "module csd_multiplier (").unwrap();
writeln!(
verilog,
" input signed [{}:0] x, // Input value",
input_width - 1
)
.unwrap();
writeln!(
verilog,
" output signed [{}:0] result // Result of multiplication",
ow - 1
)
.unwrap();
writeln!(verilog, ");").unwrap();
if !terms.is_empty() {
writeln!(verilog).unwrap();
writeln!(verilog, " // Create shifted versions of input").unwrap();
let mut powers_needed: BTreeSet<usize> = BTreeSet::new();
for (p, _) in &terms {
powers_needed.insert(*p);
}
for p in powers_needed.into_iter().rev() {
writeln!(
verilog,
" wire signed [{}:0] x_shift{} = x <<< {};",
ow - 1,
p,
p
)
.unwrap();
}
}
let repeated = longest_repeated_substring(csd_str);
let pat_positions: Vec<usize> = if repeated.len() > 1 {
let pat_nnz = count_nnz(&repeated);
if pat_nnz >= 2 {
let pos = find_pattern_occurrences(csd_str, &repeated);
if pos.len() >= 2 {
pos
} else {
Vec::new()
}
} else {
Vec::new()
}
} else {
Vec::new()
};
let use_opt = !pat_positions.is_empty();
if terms.is_empty() {
writeln!(verilog).unwrap();
writeln!(verilog, " // CSD implementation").unwrap();
writeln!(verilog, " assign result = 0;").unwrap();
} else if use_opt {
let base_pos = pat_positions[0];
let pat_expr = build_range_expr(csd_str, base_pos, repeated.len(), max_power);
writeln!(verilog).unwrap();
writeln!(verilog, " // LCSRe: repeated pattern \"{}\"", repeated).unwrap();
writeln!(
verilog,
" wire signed [{}:0] _pat = {};",
ow - 1,
pat_expr
)
.unwrap();
let mut expr = String::new();
let mut cur = 0;
for &pos in &pat_positions {
if pos > cur {
let gap = build_range_expr(csd_str, cur, pos - cur, max_power);
if !gap.is_empty() {
if expr.is_empty() {
expr = gap;
} else {
write!(expr, " + {}", gap).unwrap();
}
}
}
let shift = pos as isize - base_pos as isize;
let pat_ref = if shift == 0 {
"_pat".to_string()
} else {
format!("(_pat >>> {})", shift)
};
if expr.is_empty() {
expr = pat_ref;
} else {
write!(expr, " + {}", pat_ref).unwrap();
}
cur = pos + repeated.len();
}
if cur < csd_str.len() {
let suffix = build_range_expr(csd_str, cur, csd_str.len() - cur, max_power);
if !suffix.is_empty() {
write!(expr, " + {}", suffix).unwrap();
}
}
writeln!(verilog).unwrap();
writeln!(verilog, " // CSD implementation (LCSRe optimized)").unwrap();
writeln!(verilog, " assign result = {};", expr).unwrap();
} else {
writeln!(verilog).unwrap();
writeln!(verilog, " // CSD implementation").unwrap();
let mut expr = String::new();
for (i, (power, op)) in terms.iter().enumerate() {
if i == 0 {
if *op == TermOp::Sub {
write!(expr, "-").unwrap();
}
write!(expr, "x_shift{}", power).unwrap();
} else {
match op {
TermOp::Add => write!(expr, " + x_shift{}", power).unwrap(),
TermOp::Sub => write!(expr, " - x_shift{}", power).unwrap(),
}
}
}
writeln!(verilog, " assign result = {};", expr).unwrap();
}
writeln!(verilog, "endmodule").unwrap();
Ok(verilog)
}
pub fn generate_csd_multipliers(
coeffs: &[MultiplierSpec],
module_name: &str,
) -> Result<String, CsdMultiplierError> {
if coeffs.is_empty() {
return Err(CsdMultiplierError::EmptyCoefficients);
}
let input_width = coeffs[0].input_width;
let max_power = coeffs[0].max_power;
for spec in coeffs {
if spec.input_width != input_width || spec.max_power != max_power {
return Err(CsdMultiplierError::WidthMismatch);
}
let len = spec.csd.len();
if len != max_power + 1 {
return Err(CsdMultiplierError::LengthMismatch);
}
for c in spec.csd.chars() {
if c != '+' && c != '-' && c != '0' {
return Err(CsdMultiplierError::InvalidCharacter);
}
}
}
let ow = output_width(input_width, max_power);
let mut all_powers: BTreeSet<usize> = BTreeSet::new();
for spec in coeffs {
for (i, c) in spec.csd.char_indices() {
if c != '0' {
all_powers.insert(max_power - i);
}
}
}
let csd_strings: Vec<String> = coeffs.iter().map(|s| s.csd.clone()).collect();
let cross = find_cross_patterns(&csd_strings);
let mut best_pattern = String::new();
let mut best_occurrences: Vec<(usize, usize)> = Vec::new();
let mut best_score = 0;
for (pat, occ) in &cross {
let nnz = count_nnz(pat);
let score = (nnz.saturating_sub(1)) * (occ.len().saturating_sub(1));
if score > best_score {
best_score = score;
best_pattern.clone_from(pat);
best_occurrences.clone_from(occ);
}
}
let cse_base_pos = if best_pattern.is_empty() {
0
} else {
best_occurrences
.iter()
.map(|(_, pos)| *pos)
.min()
.unwrap_or(0)
};
let mut verilog = String::new();
writeln!(verilog).unwrap();
writeln!(verilog, "module {} (", module_name).unwrap();
writeln!(
verilog,
" input signed [{}:0] x, // Input value",
input_width - 1
)
.unwrap();
for spec in coeffs {
let ow_spec = output_width(spec.input_width, spec.max_power);
writeln!(
verilog,
" output signed [{}:0] {}",
ow_spec - 1,
spec.name
)
.unwrap();
}
writeln!(verilog, ");").unwrap();
if !all_powers.is_empty() {
writeln!(verilog).unwrap();
writeln!(verilog, " // Create shifted versions of input").unwrap();
for p in all_powers.iter().rev() {
writeln!(
verilog,
" wire signed [{}:0] x_shift{} = x <<< {};",
ow - 1,
p,
p
)
.unwrap();
}
}
let cse_name = "_cse_0";
if !best_pattern.is_empty() {
let cse_expr = build_range_expr(
&best_pattern,
0,
best_pattern.len(),
max_power.saturating_sub(cse_base_pos),
);
writeln!(verilog).unwrap();
writeln!(
verilog,
" // Cross-CSE: shared pattern \"{}\"",
best_pattern
)
.unwrap();
writeln!(
verilog,
" wire signed [{}:0] {} = {};",
ow - 1,
cse_name,
cse_expr
)
.unwrap();
}
let cse_coeffs: HashSet<usize> = best_occurrences.iter().map(|(ci, _)| *ci).collect();
for (idx, spec) in coeffs.iter().enumerate() {
writeln!(verilog).unwrap();
writeln!(verilog, " // {}: {}", spec.name, spec.csd).unwrap();
let has_cse = !best_pattern.is_empty() && cse_coeffs.contains(&idx);
let expr = if has_cse {
build_coeff_expr(&spec.csd, max_power, &best_pattern, cse_base_pos, cse_name)
} else {
build_coeff_expr(&spec.csd, max_power, "", 0, "")
};
if expr.is_empty() {
writeln!(verilog, " assign {} = 0;", spec.name).unwrap();
} else {
writeln!(verilog, " assign {} = {};", spec.name, expr).unwrap();
}
}
writeln!(verilog, "endmodule").unwrap();
Ok(verilog)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_csd() {
let csd = "+00-00+0+";
let multiplier = CsdMultiplier::new(csd, 8, 8).unwrap();
assert_eq!(multiplier.decimal_value(), 229);
}
#[test]
fn test_decimal_value() {
let multiplier = CsdMultiplier::new("+", 8, 0).unwrap();
assert_eq!(multiplier.decimal_value(), 1);
let multiplier = CsdMultiplier::new("-", 8, 0).unwrap();
assert_eq!(multiplier.decimal_value(), -1);
let multiplier = CsdMultiplier::new("+0-", 8, 2).unwrap();
assert_eq!(multiplier.decimal_value(), 3);
let multiplier = CsdMultiplier::new("-0+", 8, 2).unwrap();
assert_eq!(multiplier.decimal_value(), -3);
}
#[test]
fn test_all_zeros_csd() {
let csd = "0000";
let multiplier = CsdMultiplier::new(csd, 8, 3).unwrap();
let verilog = multiplier.generate_verilog();
assert!(verilog.contains("assign result = 0;"));
}
#[test]
fn test_invalid_csd_chars() {
let csd = "+01-00+0+";
let result = CsdMultiplier::new(csd, 8, 6);
assert!(matches!(result, Err(CsdMultiplierError::InvalidCharacter)));
}
#[test]
fn test_length_mismatch() {
let csd = "+00-00+0+";
let result = CsdMultiplier::new(csd, 8, 5);
assert!(matches!(result, Err(CsdMultiplierError::LengthMismatch)));
}
#[test]
fn test_verilog_generation() {
let csd = "+0-";
let n = 8;
let m = 2;
let multiplier = CsdMultiplier::new(csd, n, m).unwrap();
let expected_verilog = r###"// CSD Multiplier for pattern: +0- (value: 3)
module csd_multiplier (
input signed [7:0] x, // Input value (signed)
output signed [9:0] result // Result (signed)
);
// Signed shifted versions (Verilog handles sign extension)
wire signed [9:0] x_shift2 = $signed({ {0{x[7]}}, x}) << 2;
wire signed [9:0] x_shift0 = $signed({ {2{x[7]}}, x}) << 0;
// CSD implementation with signed arithmetic
assign result = x_shift2 - x_shift0;
endmodule
"###;
assert_eq!(multiplier.generate_verilog(), expected_verilog);
}
#[test]
fn test_fn_basic_valid() {
let v = generate_csd_multiplier("+0-", 8, 2).unwrap();
assert!(v.contains("module csd_multiplier"));
assert!(v.contains("endmodule"));
assert!(v.contains("input signed [7:0] x"));
assert!(v.contains("output signed [9:0] result"));
assert!(v.contains("assign result = x_shift2 - x_shift0"));
}
#[test]
fn test_fn_positive_only() {
let v = generate_csd_multiplier("+0+", 4, 2).unwrap();
assert!(v.contains("assign result = x_shift2 + x_shift0"));
}
#[test]
fn test_fn_negative_only() {
let v = generate_csd_multiplier("-0-", 8, 2).unwrap();
assert!(v.contains("assign result = -x_shift2 - x_shift0"));
}
#[test]
fn test_fn_all_zeros() {
let v = generate_csd_multiplier("000", 8, 2).unwrap();
assert!(v.contains("assign result = 0;"));
assert!(!v.contains("x_shift"));
}
#[test]
fn test_fn_single_nonzero() {
let v = generate_csd_multiplier("+00", 8, 2).unwrap();
assert!(v.contains("assign result"));
assert!(v.contains("x_shift2"));
}
#[test]
fn test_fn_invalid_chars() {
let r = generate_csd_multiplier("123", 8, 2);
assert_eq!(r, Err(CsdMultiplierError::InvalidCharacter));
}
#[test]
fn test_fn_invalid_length() {
let r = generate_csd_multiplier("+0-", 8, 3);
assert_eq!(r, Err(CsdMultiplierError::LengthMismatch));
}
#[test]
fn test_fn_flat_when_pattern_nnz_is_1() {
let v = generate_csd_multiplier("+00-00+0", 8, 7).unwrap();
assert!(!v.contains("_pat"));
assert!(v.contains("x_shift7 - x_shift4 + x_shift1"));
}
#[test]
fn test_fn_double_repeat_optimization() {
let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
assert!(v.contains("_pat"));
assert!(v.contains("_pat = x_shift7 - x_shift5"));
assert!(v.contains("(_pat >>> 4)"));
assert!(v.contains("LCSRe"));
}
#[test]
fn test_fn_triple_repeat_optimization() {
let v = generate_csd_multiplier("+0-0+0-0+0-0", 8, 11).unwrap();
assert!(v.contains("_pat"));
assert!(v.contains("(_pat >>> 4)"));
assert!(v.contains("(_pat >>> 8)"));
}
#[test]
fn test_fn_longer_pattern_repeat() {
let v = generate_csd_multiplier("+00-00+00-00", 8, 11).unwrap();
assert!(v.contains("_pat"));
assert!(v.contains("_pat = x_shift11 - x_shift8"));
assert!(v.contains("(_pat >>> 6)"));
}
#[test]
fn test_fn_leading_minus_no_optimization() {
let v = generate_csd_multiplier("-0-", 8, 2).unwrap();
assert!(!v.contains("_pat"));
assert!(v.contains("-x_shift2 - x_shift0"));
}
#[test]
fn test_fn_pattern_with_leading_minus() {
let v = generate_csd_multiplier("-0+0-0+0", 8, 7).unwrap();
assert!(v.contains("_pat"));
assert!(v.contains("_pat = -x_shift7 + x_shift5"));
assert!(v.contains("(_pat >>> 4)"));
}
#[test]
fn test_fn_no_optimization_for_single_occurrence() {
let v = generate_csd_multiplier("+0-+00-0", 8, 7).unwrap();
assert!(!v.contains("_pat"));
}
#[test]
fn test_fn_pat_wire_width_matches_output() {
let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
assert!(v.contains("[14:0] _pat"));
}
#[test]
fn test_fn_repeat_with_trailing_gap() {
let v = generate_csd_multiplier("+0-0+0-0+0", 8, 9).unwrap();
assert!(v.contains("_pat"));
assert!(v.contains("(_pat >>> 4)"));
}
#[test]
fn test_fn_very_short_csd() {
let v = generate_csd_multiplier("+", 8, 0).unwrap();
assert!(v.contains("assign result = x_shift0"));
}
#[test]
fn test_fn_all_minus_signs() {
let v = generate_csd_multiplier("---", 8, 2).unwrap();
assert!(!v.contains("_pat"));
}
#[test]
fn test_fn_always_has_proper_module_boundaries() {
let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
assert!(v.contains("\nmodule csd_multiplier"));
assert!(v.contains("endmodule\n"));
}
#[test]
fn test_fn_lcsre_comment_present_when_optimized() {
let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
assert!(v.contains("LCSRe"));
}
#[test]
fn test_fn_no_lcsre_comment_when_flat() {
let v = generate_csd_multiplier("+00-00+0", 8, 7).unwrap();
assert!(!v.contains("LCSRe"));
}
#[test]
fn test_multi_empty_coeffs() {
let r = generate_csd_multipliers(&[], "test");
assert_eq!(r, Err(CsdMultiplierError::EmptyCoefficients));
}
#[test]
fn test_multi_single_coeff() {
let coeffs = vec![MultiplierSpec {
name: "y0".to_string(),
csd: "+0-".to_string(),
input_width: 8,
max_power: 2,
}];
let v = generate_csd_multipliers(&coeffs, "test_mod").unwrap();
assert!(v.contains("module test_mod"));
assert!(v.contains("output signed [9:0] y0"));
}
#[test]
fn test_multi_duplicate_coeffs() {
let coeffs = vec![
MultiplierSpec {
name: "y0".to_string(),
csd: "+00-00+0+".to_string(),
input_width: 8,
max_power: 8,
},
MultiplierSpec {
name: "y1".to_string(),
csd: "+00-00+0+".to_string(),
input_width: 8,
max_power: 8,
},
];
let v = generate_csd_multipliers(&coeffs, "csd_filter").unwrap();
assert!(v.contains("Cross-CSE"));
assert!(v.contains("_cse_0"));
}
#[test]
fn test_multi_width_mismatch() {
let coeffs = vec![
MultiplierSpec {
name: "y0".to_string(),
csd: "+0-".to_string(),
input_width: 8,
max_power: 2,
},
MultiplierSpec {
name: "y1".to_string(),
csd: "+0-".to_string(),
input_width: 16,
max_power: 2,
},
];
let r = generate_csd_multipliers(&coeffs, "test");
assert_eq!(r, Err(CsdMultiplierError::WidthMismatch));
}
#[test]
fn test_multi_invalid_chars() {
let coeffs = vec![MultiplierSpec {
name: "y0".to_string(),
csd: "123".to_string(),
input_width: 8,
max_power: 2,
}];
let r = generate_csd_multipliers(&coeffs, "test");
assert_eq!(r, Err(CsdMultiplierError::InvalidCharacter));
}
}