1use std::collections::{BTreeSet, HashMap, HashSet};
24use std::fmt::Write;
25
26use crate::lcsre::longest_repeated_substring;
27
28#[derive(Debug, Clone, PartialEq)]
30pub enum CsdMultiplierError {
31 InvalidCharacter,
33 LengthMismatch,
35 EmptyCoefficients,
37 WidthMismatch,
39}
40
41pub struct CsdMultiplier {
56 csd: String,
57 n: usize,
58 m: usize,
59}
60
61#[derive(Debug, Clone)]
66pub struct MultiplierSpec {
67 pub name: String,
69 pub csd: String,
71 pub input_width: usize,
73 pub max_power: usize,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq)]
82enum TermOp {
83 Add,
84 Sub,
85}
86
87fn parse_terms(
89 csd_str: &str,
90 max_power: usize,
91) -> Result<Vec<(usize, TermOp)>, CsdMultiplierError> {
92 let mut terms = Vec::new();
93 let bytes = csd_str.as_bytes();
94 for (i, &c) in bytes.iter().enumerate() {
95 let power = max_power - i;
96 match c {
97 b'+' => terms.push((power, TermOp::Add)),
98 b'-' => terms.push((power, TermOp::Sub)),
99 b'0' => {}
100 _ => return Err(CsdMultiplierError::InvalidCharacter),
101 }
102 }
103 Ok(terms)
104}
105
106fn build_range_expr(csd_str: &str, start: usize, length: usize, max_power: usize) -> String {
108 let mut expr = String::new();
109 let mut first = true;
110 let bytes = csd_str.as_bytes();
111 let end = start.saturating_add(length).min(bytes.len());
112 for (i, &c) in bytes.iter().enumerate().skip(start).take(end - start) {
113 let power = max_power - i;
114 match c {
115 b'+' => {
116 if first {
117 write!(expr, "x_shift{}", power).unwrap();
118 first = false;
119 } else {
120 write!(expr, " + x_shift{}", power).unwrap();
121 }
122 }
123 b'-' => {
124 if first {
125 write!(expr, "-x_shift{}", power).unwrap();
126 first = false;
127 } else {
128 write!(expr, " - x_shift{}", power).unwrap();
129 }
130 }
131 _ => {}
132 }
133 }
134 expr
135}
136
137fn output_width(input_width: usize, max_power: usize) -> usize {
143 input_width + max_power
144}
145
146impl CsdMultiplier {
151 pub fn new(csd: &str, n: usize, m: usize) -> Result<Self, CsdMultiplierError> {
167 let bytes = csd.as_bytes();
168 if !bytes.iter().all(|&c| matches!(c, b'+' | b'-' | b'0')) {
169 return Err(CsdMultiplierError::InvalidCharacter);
170 }
171 if csd.len() != m + 1 {
172 return Err(CsdMultiplierError::LengthMismatch);
173 }
174 Ok(Self {
175 csd: csd.to_string(),
176 n,
177 m,
178 })
179 }
180
181 fn decimal_value(&self) -> i32 {
186 self.csd.as_bytes().iter().fold(0, |acc, &c| {
187 let acc = acc << 1;
188 match c {
189 b'+' => acc + 1,
190 b'-' => acc - 1,
191 b'0' => acc,
192 _ => unreachable!(),
193 }
194 })
195 }
196
197 pub fn generate_verilog(&self) -> String {
199 let mut output = String::new();
200 self.generate_header(&mut output);
201 self.generate_wires(&mut output);
202 self.generate_result_lcsre(&mut output);
203 writeln!(output, "endmodule").unwrap();
204 output
205 }
206
207 fn generate_header(&self, output: &mut String) {
208 writeln!(
209 output,
210 "// CSD Multiplier for pattern: {} (value: {})",
211 self.csd,
212 self.decimal_value()
213 )
214 .unwrap();
215 writeln!(
216 output,
217 "module csd_multiplier (
218 input signed [{}:0] x, // Input value (signed)
219 output signed [{}:0] result // Result (signed)
220);",
221 self.n - 1,
222 self.n + self.m - 1
223 )
224 .unwrap();
225 }
226
227 fn get_unique_powers(&self) -> Vec<usize> {
229 let mut powers: Vec<usize> = self
230 .csd
231 .char_indices()
232 .filter(|(_, c)| *c != '0')
233 .map(|(i, _)| self.m - i)
234 .collect();
235 powers.sort_unstable_by(|a, b| b.cmp(a));
236 powers.dedup();
237 powers
238 }
239
240 fn generate_wires(&self, output: &mut String) {
241 let shift_powers = self.get_unique_powers();
242 if shift_powers.is_empty() {
243 return;
244 }
245 writeln!(
246 output,
247 "\n // Signed shifted versions (Verilog handles sign extension)"
248 )
249 .unwrap();
250 for &power in &shift_powers {
251 let padding = self.m - power;
252 writeln!(
253 output,
254 " wire signed [{}:0] x_shift{} = $signed({{ {{{}{{x[{}]}}}}, x}}) << {};",
255 self.n + self.m - 1,
256 power,
257 padding,
258 self.n - 1,
259 power
260 )
261 .unwrap();
262 }
263 }
264
265 fn generate_result_lcsre(&self, output: &mut String) {
267 let terms = parse_terms(&self.csd, self.m).unwrap_or_default();
268 if terms.is_empty() {
269 writeln!(output, "\n // CSD implementation").unwrap();
270 writeln!(output, " assign result = 0;").unwrap();
271 return;
272 }
273
274 let repeated = longest_repeated_substring(&self.csd);
276 let pat_positions = if repeated.len() > 1 {
277 let pat_nnz = repeated.chars().filter(|c| *c == '+' || *c == '-').count();
278 if pat_nnz >= 2 {
279 let pos = find_pattern_occurrences(&self.csd, &repeated);
280 if pos.len() >= 2 {
281 Some((repeated, pos))
282 } else {
283 None
284 }
285 } else {
286 None
287 }
288 } else {
289 None
290 };
291
292 if let Some((ref pat, ref positions)) = pat_positions {
293 let base_pos = positions[0];
295 let ow = output_width(self.n, self.m);
296
297 let pat_expr = build_range_expr(&self.csd, base_pos, pat.len(), self.m);
298 writeln!(output, "\n // LCSRe: repeated pattern \"{}\"", pat).unwrap();
299 writeln!(
300 output,
301 " wire signed [{}:0] _pat = {};",
302 ow - 1,
303 pat_expr
304 )
305 .unwrap();
306
307 let mut expr = String::new();
308 let mut cur = 0;
309 for &pos in positions {
310 if pos > cur {
312 let gap = build_range_expr(&self.csd, cur, pos - cur, self.m);
313 if !gap.is_empty() {
314 if expr.is_empty() {
315 expr = gap;
316 } else {
317 write!(expr, " + {}", gap).unwrap();
318 }
319 }
320 }
321 let shift = pos as isize - base_pos as isize;
323 let pat_ref = if shift == 0 {
324 "_pat".to_string()
325 } else {
326 format!("(_pat >>> {})", shift)
327 };
328 if expr.is_empty() {
329 expr = pat_ref;
330 } else {
331 write!(expr, " + {}", pat_ref).unwrap();
332 }
333 cur = pos + pat.len();
334 }
335 if cur < self.csd.len() {
337 let suffix = build_range_expr(&self.csd, cur, self.csd.len() - cur, self.m);
338 if !suffix.is_empty() {
339 write!(expr, " + {}", suffix).unwrap();
340 }
341 }
342
343 writeln!(output, "\n // CSD implementation (LCSRe optimized)").unwrap();
344 writeln!(output, " assign result = {};", expr).unwrap();
345 } else {
346 writeln!(output, "\n // CSD implementation with signed arithmetic").unwrap();
348 let (first_power, first_op) = terms[0];
349 let mut expr = format!(
350 "{}x_shift{}",
351 if first_op == TermOp::Sub { "-" } else { "" },
352 first_power
353 );
354 for (power, op) in &terms[1..] {
355 match op {
356 TermOp::Add => write!(expr, " + x_shift{}", power).unwrap(),
357 TermOp::Sub => write!(expr, " - x_shift{}", power).unwrap(),
358 }
359 }
360 writeln!(output, " assign result = {};", expr).unwrap();
361 }
362 }
363}
364
365fn find_pattern_occurrences(csd_str: &str, pattern: &str) -> Vec<usize> {
371 let mut positions = Vec::new();
372 let mut pos = 0;
373 while let Some(found) = csd_str[pos..].find(pattern) {
374 let absolute = pos + found;
375 positions.push(absolute);
376 pos = absolute + pattern.len();
377 }
378 positions
379}
380
381fn count_nnz(s: &str) -> usize {
383 s.as_bytes()
384 .iter()
385 .filter(|&&c| c == b'+' || c == b'-')
386 .count()
387}
388
389fn build_coeff_expr(
391 csd: &str,
392 max_power: usize,
393 pattern: &str,
394 cse_base_pos: usize,
395 cse_name: &str,
396) -> String {
397 if pattern.is_empty() {
398 return build_range_expr(csd, 0, csd.len(), max_power);
399 }
400
401 let positions = find_pattern_occurrences(csd, pattern);
402 let mut parts: Vec<String> = Vec::new();
403 let mut cur = 0;
404
405 for pos in positions {
406 if pos > cur {
408 let gap = build_range_expr(csd, cur, pos - cur, max_power);
409 if !gap.is_empty() {
410 parts.push(gap);
411 }
412 }
413 let shift = pos as isize - cse_base_pos as isize;
415 if shift == 0 {
416 parts.push(cse_name.to_string());
417 } else {
418 parts.push(format!("({} >>> {})", cse_name, shift));
419 }
420 cur = pos + pattern.len();
421 }
422 if cur < csd.len() {
424 let gap = build_range_expr(csd, cur, csd.len() - cur, max_power);
425 if !gap.is_empty() {
426 parts.push(gap);
427 }
428 }
429
430 if parts.is_empty() {
431 return String::new();
432 }
433 let mut result = parts[0].clone();
434 for p in &parts[1..] {
435 write!(result, " + {}", p).unwrap();
436 }
437 result
438}
439
440fn find_cross_patterns(csd_list: &[String]) -> HashMap<String, Vec<(usize, usize)>> {
443 let mut patterns: HashMap<String, Vec<(usize, usize)>> = HashMap::new();
444 for (ci, csd) in csd_list.iter().enumerate() {
445 let bytes = csd.as_bytes();
446 let n = bytes.len();
447 for i in 0..n {
448 let mut sub = String::with_capacity(n - i);
450 let mut nnz = 0u32;
451 for (j, &c) in bytes.iter().enumerate().skip(i) {
452 sub.push(c as char);
453 if c == b'+' || c == b'-' {
454 nnz += 1;
455 }
456 if j - i + 1 >= 2 && nnz >= 2 {
457 patterns.entry(sub.clone()).or_default().push((ci, i));
458 }
459 }
460 }
461 }
462 patterns.retain(|_, occ| {
464 let unique: HashSet<usize> = occ.iter().map(|(ci, _)| *ci).collect();
465 unique.len() >= 2
466 });
467 patterns
468}
469
470pub fn generate_csd_multiplier(
504 csd_str: &str,
505 input_width: usize,
506 max_power: usize,
507) -> Result<String, CsdMultiplierError> {
508 let len = csd_str.len();
510 if len != max_power + 1 {
511 return Err(CsdMultiplierError::LengthMismatch);
512 }
513 for &c in csd_str.as_bytes() {
514 if c != b'+' && c != b'-' && c != b'0' {
515 return Err(CsdMultiplierError::InvalidCharacter);
516 }
517 }
518
519 let terms = parse_terms(csd_str, max_power)?;
520 let ow = output_width(input_width, max_power);
521
522 let mut verilog = String::new();
523
524 writeln!(verilog).unwrap();
526 writeln!(verilog, "module csd_multiplier (").unwrap();
527 writeln!(
528 verilog,
529 " input signed [{}:0] x, // Input value",
530 input_width - 1
531 )
532 .unwrap();
533 writeln!(
534 verilog,
535 " output signed [{}:0] result // Result of multiplication",
536 ow - 1
537 )
538 .unwrap();
539 writeln!(verilog, ");").unwrap();
540
541 if !terms.is_empty() {
543 writeln!(verilog).unwrap();
544 writeln!(verilog, " // Create shifted versions of input").unwrap();
545 let mut powers_needed: BTreeSet<usize> = BTreeSet::new();
546 for (p, _) in &terms {
548 powers_needed.insert(*p);
549 }
550 for p in powers_needed.into_iter().rev() {
551 writeln!(
552 verilog,
553 " wire signed [{}:0] x_shift{} = x <<< {};",
554 ow - 1,
555 p,
556 p
557 )
558 .unwrap();
559 }
560 }
561
562 let repeated = longest_repeated_substring(csd_str);
564
565 let pat_positions: Vec<usize> = if repeated.len() > 1 {
566 let pat_nnz = count_nnz(&repeated);
567 if pat_nnz >= 2 {
568 let pos = find_pattern_occurrences(csd_str, &repeated);
569 if pos.len() >= 2 {
570 pos
571 } else {
572 Vec::new()
573 }
574 } else {
575 Vec::new()
576 }
577 } else {
578 Vec::new()
579 };
580
581 let use_opt = !pat_positions.is_empty();
582
583 if terms.is_empty() {
585 writeln!(verilog).unwrap();
586 writeln!(verilog, " // CSD implementation").unwrap();
587 writeln!(verilog, " assign result = 0;").unwrap();
588 } else if use_opt {
589 let base_pos = pat_positions[0];
591 let pat_expr = build_range_expr(csd_str, base_pos, repeated.len(), max_power);
592 writeln!(verilog).unwrap();
593 writeln!(verilog, " // LCSRe: repeated pattern \"{}\"", repeated).unwrap();
594 writeln!(
595 verilog,
596 " wire signed [{}:0] _pat = {};",
597 ow - 1,
598 pat_expr
599 )
600 .unwrap();
601
602 let mut expr = String::new();
603 let mut cur = 0;
604 for &pos in &pat_positions {
605 if pos > cur {
607 let gap = build_range_expr(csd_str, cur, pos - cur, max_power);
608 if !gap.is_empty() {
609 if expr.is_empty() {
610 expr = gap;
611 } else {
612 write!(expr, " + {}", gap).unwrap();
613 }
614 }
615 }
616 let shift = pos as isize - base_pos as isize;
618 let pat_ref = if shift == 0 {
619 "_pat".to_string()
620 } else {
621 format!("(_pat >>> {})", shift)
622 };
623 if expr.is_empty() {
624 expr = pat_ref;
625 } else {
626 write!(expr, " + {}", pat_ref).unwrap();
627 }
628 cur = pos + repeated.len();
629 }
630 if cur < csd_str.len() {
632 let suffix = build_range_expr(csd_str, cur, csd_str.len() - cur, max_power);
633 if !suffix.is_empty() {
634 write!(expr, " + {}", suffix).unwrap();
635 }
636 }
637
638 writeln!(verilog).unwrap();
639 writeln!(verilog, " // CSD implementation (LCSRe optimized)").unwrap();
640 writeln!(verilog, " assign result = {};", expr).unwrap();
641 } else {
642 writeln!(verilog).unwrap();
644 writeln!(verilog, " // CSD implementation").unwrap();
645 let mut expr = String::new();
646 for (i, (power, op)) in terms.iter().enumerate() {
647 if i == 0 {
648 if *op == TermOp::Sub {
649 write!(expr, "-").unwrap();
650 }
651 write!(expr, "x_shift{}", power).unwrap();
652 } else {
653 match op {
654 TermOp::Add => write!(expr, " + x_shift{}", power).unwrap(),
655 TermOp::Sub => write!(expr, " - x_shift{}", power).unwrap(),
656 }
657 }
658 }
659 writeln!(verilog, " assign result = {};", expr).unwrap();
660 }
661
662 writeln!(verilog, "endmodule").unwrap();
663 Ok(verilog)
664}
665
666pub fn generate_csd_multipliers(
712 coeffs: &[MultiplierSpec],
713 module_name: &str,
714) -> Result<String, CsdMultiplierError> {
715 if coeffs.is_empty() {
716 return Err(CsdMultiplierError::EmptyCoefficients);
717 }
718
719 let input_width = coeffs[0].input_width;
721 let max_power = coeffs[0].max_power;
722
723 for spec in coeffs {
724 if spec.input_width != input_width || spec.max_power != max_power {
725 return Err(CsdMultiplierError::WidthMismatch);
726 }
727 let len = spec.csd.len();
728 if len != max_power + 1 {
729 return Err(CsdMultiplierError::LengthMismatch);
730 }
731 for c in spec.csd.chars() {
732 if c != '+' && c != '-' && c != '0' {
733 return Err(CsdMultiplierError::InvalidCharacter);
734 }
735 }
736 }
737
738 let ow = output_width(input_width, max_power);
739
740 let mut all_powers: BTreeSet<usize> = BTreeSet::new();
742 for spec in coeffs {
743 for (i, c) in spec.csd.char_indices() {
744 if c != '0' {
745 all_powers.insert(max_power - i);
746 }
747 }
748 }
749
750 let csd_strings: Vec<String> = coeffs.iter().map(|s| s.csd.clone()).collect();
752 let cross = find_cross_patterns(&csd_strings);
753
754 let mut best_pattern = String::new();
755 let mut best_occurrences: Vec<(usize, usize)> = Vec::new();
756 let mut best_score = 0;
757
758 for (pat, occ) in &cross {
759 let nnz = count_nnz(pat);
760 let score = (nnz.saturating_sub(1)) * (occ.len().saturating_sub(1));
761 if score > best_score {
762 best_score = score;
763 best_pattern.clone_from(pat);
764 best_occurrences.clone_from(occ);
765 }
766 }
767
768 let cse_base_pos = if best_pattern.is_empty() {
770 0
771 } else {
772 best_occurrences
773 .iter()
774 .map(|(_, pos)| *pos)
775 .min()
776 .unwrap_or(0)
777 };
778
779 let mut verilog = String::new();
781 writeln!(verilog).unwrap();
782 writeln!(verilog, "module {} (", module_name).unwrap();
783 writeln!(
784 verilog,
785 " input signed [{}:0] x, // Input value",
786 input_width - 1
787 )
788 .unwrap();
789 for spec in coeffs {
790 let ow_spec = output_width(spec.input_width, spec.max_power);
791 writeln!(
792 verilog,
793 " output signed [{}:0] {}",
794 ow_spec - 1,
795 spec.name
796 )
797 .unwrap();
798 }
799 writeln!(verilog, ");").unwrap();
800
801 if !all_powers.is_empty() {
803 writeln!(verilog).unwrap();
804 writeln!(verilog, " // Create shifted versions of input").unwrap();
805 for p in all_powers.iter().rev() {
806 writeln!(
807 verilog,
808 " wire signed [{}:0] x_shift{} = x <<< {};",
809 ow - 1,
810 p,
811 p
812 )
813 .unwrap();
814 }
815 }
816
817 let cse_name = "_cse_0";
819 if !best_pattern.is_empty() {
820 let cse_expr = build_range_expr(
821 &best_pattern,
822 0,
823 best_pattern.len(),
824 max_power.saturating_sub(cse_base_pos),
825 );
826 writeln!(verilog).unwrap();
827 writeln!(
828 verilog,
829 " // Cross-CSE: shared pattern \"{}\"",
830 best_pattern
831 )
832 .unwrap();
833 writeln!(
834 verilog,
835 " wire signed [{}:0] {} = {};",
836 ow - 1,
837 cse_name,
838 cse_expr
839 )
840 .unwrap();
841 }
842
843 let cse_coeffs: HashSet<usize> = best_occurrences.iter().map(|(ci, _)| *ci).collect();
845
846 for (idx, spec) in coeffs.iter().enumerate() {
848 writeln!(verilog).unwrap();
849 writeln!(verilog, " // {}: {}", spec.name, spec.csd).unwrap();
850
851 let has_cse = !best_pattern.is_empty() && cse_coeffs.contains(&idx);
852 let expr = if has_cse {
853 build_coeff_expr(&spec.csd, max_power, &best_pattern, cse_base_pos, cse_name)
854 } else {
855 build_coeff_expr(&spec.csd, max_power, "", 0, "")
856 };
857
858 if expr.is_empty() {
859 writeln!(verilog, " assign {} = 0;", spec.name).unwrap();
860 } else {
861 writeln!(verilog, " assign {} = {};", spec.name, expr).unwrap();
862 }
863 }
864
865 writeln!(verilog, "endmodule").unwrap();
866 Ok(verilog)
867}
868
869#[cfg(test)]
874mod tests {
875 use super::*;
876
877 #[test]
880 fn test_valid_csd() {
881 let csd = "+00-00+0+";
882 let multiplier = CsdMultiplier::new(csd, 8, 8).unwrap();
883 assert_eq!(multiplier.decimal_value(), 229);
884 }
885
886 #[test]
887 fn test_decimal_value() {
888 let multiplier = CsdMultiplier::new("+", 8, 0).unwrap();
889 assert_eq!(multiplier.decimal_value(), 1);
890
891 let multiplier = CsdMultiplier::new("-", 8, 0).unwrap();
892 assert_eq!(multiplier.decimal_value(), -1);
893
894 let multiplier = CsdMultiplier::new("+0-", 8, 2).unwrap();
895 assert_eq!(multiplier.decimal_value(), 3);
896
897 let multiplier = CsdMultiplier::new("-0+", 8, 2).unwrap();
898 assert_eq!(multiplier.decimal_value(), -3);
899 }
900
901 #[test]
902 fn test_all_zeros_csd() {
903 let csd = "0000";
904 let multiplier = CsdMultiplier::new(csd, 8, 3).unwrap();
905 let verilog = multiplier.generate_verilog();
906 assert!(verilog.contains("assign result = 0;"));
907 }
908
909 #[test]
910 fn test_invalid_csd_chars() {
911 let csd = "+01-00+0+";
912 let result = CsdMultiplier::new(csd, 8, 6);
913 assert!(matches!(result, Err(CsdMultiplierError::InvalidCharacter)));
914 }
915
916 #[test]
917 fn test_length_mismatch() {
918 let csd = "+00-00+0+";
919 let result = CsdMultiplier::new(csd, 8, 5);
920 assert!(matches!(result, Err(CsdMultiplierError::LengthMismatch)));
921 }
922
923 #[test]
924 fn test_verilog_generation() {
925 let csd = "+0-";
926 let n = 8;
927 let m = 2;
928 let multiplier = CsdMultiplier::new(csd, n, m).unwrap();
929 let expected_verilog = r###"// CSD Multiplier for pattern: +0- (value: 3)
930module csd_multiplier (
931 input signed [7:0] x, // Input value (signed)
932 output signed [9:0] result // Result (signed)
933);
934
935 // Signed shifted versions (Verilog handles sign extension)
936 wire signed [9:0] x_shift2 = $signed({ {0{x[7]}}, x}) << 2;
937 wire signed [9:0] x_shift0 = $signed({ {2{x[7]}}, x}) << 0;
938
939 // CSD implementation with signed arithmetic
940 assign result = x_shift2 - x_shift0;
941endmodule
942"###;
943 assert_eq!(multiplier.generate_verilog(), expected_verilog);
944 }
945
946 #[test]
950 fn test_fn_basic_valid() {
951 let v = generate_csd_multiplier("+0-", 8, 2).unwrap();
952 assert!(v.contains("module csd_multiplier"));
953 assert!(v.contains("endmodule"));
954 assert!(v.contains("input signed [7:0] x"));
955 assert!(v.contains("output signed [9:0] result"));
956 assert!(v.contains("assign result = x_shift2 - x_shift0"));
957 }
958
959 #[test]
960 fn test_fn_positive_only() {
961 let v = generate_csd_multiplier("+0+", 4, 2).unwrap();
962 assert!(v.contains("assign result = x_shift2 + x_shift0"));
963 }
964
965 #[test]
966 fn test_fn_negative_only() {
967 let v = generate_csd_multiplier("-0-", 8, 2).unwrap();
968 assert!(v.contains("assign result = -x_shift2 - x_shift0"));
969 }
970
971 #[test]
972 fn test_fn_all_zeros() {
973 let v = generate_csd_multiplier("000", 8, 2).unwrap();
974 assert!(v.contains("assign result = 0;"));
975 assert!(!v.contains("x_shift"));
976 }
977
978 #[test]
979 fn test_fn_single_nonzero() {
980 let v = generate_csd_multiplier("+00", 8, 2).unwrap();
981 assert!(v.contains("assign result"));
982 assert!(v.contains("x_shift2"));
983 }
984
985 #[test]
986 fn test_fn_invalid_chars() {
987 let r = generate_csd_multiplier("123", 8, 2);
988 assert_eq!(r, Err(CsdMultiplierError::InvalidCharacter));
989 }
990
991 #[test]
992 fn test_fn_invalid_length() {
993 let r = generate_csd_multiplier("+0-", 8, 3);
994 assert_eq!(r, Err(CsdMultiplierError::LengthMismatch));
995 }
996
997 #[test]
999 fn test_fn_flat_when_pattern_nnz_is_1() {
1000 let v = generate_csd_multiplier("+00-00+0", 8, 7).unwrap();
1002 assert!(!v.contains("_pat"));
1003 assert!(v.contains("x_shift7 - x_shift4 + x_shift1"));
1004 }
1005
1006 #[test]
1007 fn test_fn_double_repeat_optimization() {
1008 let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
1010 assert!(v.contains("_pat"));
1011 assert!(v.contains("_pat = x_shift7 - x_shift5"));
1012 assert!(v.contains("(_pat >>> 4)"));
1013 assert!(v.contains("LCSRe"));
1014 }
1015
1016 #[test]
1017 fn test_fn_triple_repeat_optimization() {
1018 let v = generate_csd_multiplier("+0-0+0-0+0-0", 8, 11).unwrap();
1020 assert!(v.contains("_pat"));
1021 assert!(v.contains("(_pat >>> 4)"));
1022 assert!(v.contains("(_pat >>> 8)"));
1023 }
1024
1025 #[test]
1026 fn test_fn_longer_pattern_repeat() {
1027 let v = generate_csd_multiplier("+00-00+00-00", 8, 11).unwrap();
1029 assert!(v.contains("_pat"));
1030 assert!(v.contains("_pat = x_shift11 - x_shift8"));
1031 assert!(v.contains("(_pat >>> 6)"));
1032 }
1033
1034 #[test]
1035 fn test_fn_leading_minus_no_optimization() {
1036 let v = generate_csd_multiplier("-0-", 8, 2).unwrap();
1038 assert!(!v.contains("_pat"));
1039 assert!(v.contains("-x_shift2 - x_shift0"));
1040 }
1041
1042 #[test]
1043 fn test_fn_pattern_with_leading_minus() {
1044 let v = generate_csd_multiplier("-0+0-0+0", 8, 7).unwrap();
1046 assert!(v.contains("_pat"));
1047 assert!(v.contains("_pat = -x_shift7 + x_shift5"));
1048 assert!(v.contains("(_pat >>> 4)"));
1049 }
1050
1051 #[test]
1052 fn test_fn_no_optimization_for_single_occurrence() {
1053 let v = generate_csd_multiplier("+0-+00-0", 8, 7).unwrap();
1055 assert!(!v.contains("_pat"));
1056 }
1057
1058 #[test]
1059 fn test_fn_pat_wire_width_matches_output() {
1060 let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
1062 assert!(v.contains("[14:0] _pat"));
1063 }
1064
1065 #[test]
1066 fn test_fn_repeat_with_trailing_gap() {
1067 let v = generate_csd_multiplier("+0-0+0-0+0", 8, 9).unwrap();
1069 assert!(v.contains("_pat"));
1070 assert!(v.contains("(_pat >>> 4)"));
1071 }
1072
1073 #[test]
1075 fn test_fn_very_short_csd() {
1076 let v = generate_csd_multiplier("+", 8, 0).unwrap();
1078 assert!(v.contains("assign result = x_shift0"));
1079 }
1080
1081 #[test]
1082 fn test_fn_all_minus_signs() {
1083 let v = generate_csd_multiplier("---", 8, 2).unwrap();
1084 assert!(!v.contains("_pat"));
1085 }
1086
1087 #[test]
1088 fn test_fn_always_has_proper_module_boundaries() {
1089 let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
1090 assert!(v.contains("\nmodule csd_multiplier"));
1091 assert!(v.contains("endmodule\n"));
1092 }
1093
1094 #[test]
1095 fn test_fn_lcsre_comment_present_when_optimized() {
1096 let v = generate_csd_multiplier("+0-0+0-0", 8, 7).unwrap();
1097 assert!(v.contains("LCSRe"));
1098 }
1099
1100 #[test]
1101 fn test_fn_no_lcsre_comment_when_flat() {
1102 let v = generate_csd_multiplier("+00-00+0", 8, 7).unwrap();
1103 assert!(!v.contains("LCSRe"));
1104 }
1105
1106 #[test]
1109 fn test_multi_empty_coeffs() {
1110 let r = generate_csd_multipliers(&[], "test");
1111 assert_eq!(r, Err(CsdMultiplierError::EmptyCoefficients));
1112 }
1113
1114 #[test]
1115 fn test_multi_single_coeff() {
1116 let coeffs = vec![MultiplierSpec {
1117 name: "y0".to_string(),
1118 csd: "+0-".to_string(),
1119 input_width: 8,
1120 max_power: 2,
1121 }];
1122 let v = generate_csd_multipliers(&coeffs, "test_mod").unwrap();
1123 assert!(v.contains("module test_mod"));
1124 assert!(v.contains("output signed [9:0] y0"));
1125 }
1126
1127 #[test]
1128 fn test_multi_duplicate_coeffs() {
1129 let coeffs = vec![
1130 MultiplierSpec {
1131 name: "y0".to_string(),
1132 csd: "+00-00+0+".to_string(),
1133 input_width: 8,
1134 max_power: 8,
1135 },
1136 MultiplierSpec {
1137 name: "y1".to_string(),
1138 csd: "+00-00+0+".to_string(),
1139 input_width: 8,
1140 max_power: 8,
1141 },
1142 ];
1143 let v = generate_csd_multipliers(&coeffs, "csd_filter").unwrap();
1144 assert!(v.contains("Cross-CSE"));
1145 assert!(v.contains("_cse_0"));
1146 }
1147
1148 #[test]
1149 fn test_multi_width_mismatch() {
1150 let coeffs = vec![
1151 MultiplierSpec {
1152 name: "y0".to_string(),
1153 csd: "+0-".to_string(),
1154 input_width: 8,
1155 max_power: 2,
1156 },
1157 MultiplierSpec {
1158 name: "y1".to_string(),
1159 csd: "+0-".to_string(),
1160 input_width: 16,
1161 max_power: 2,
1162 },
1163 ];
1164 let r = generate_csd_multipliers(&coeffs, "test");
1165 assert_eq!(r, Err(CsdMultiplierError::WidthMismatch));
1166 }
1167
1168 #[test]
1169 fn test_multi_invalid_chars() {
1170 let coeffs = vec![MultiplierSpec {
1171 name: "y0".to_string(),
1172 csd: "123".to_string(),
1173 input_width: 8,
1174 max_power: 2,
1175 }];
1176 let r = generate_csd_multipliers(&coeffs, "test");
1177 assert_eq!(r, Err(CsdMultiplierError::InvalidCharacter));
1178 }
1179}