Skip to main content

csd/
csd_multiplier.rs

1//! CSD Multiplier Module
2//!
3//! This module provides functionality to generate Verilog code for efficient constant multiplication
4//! using Canonical Signed Digit (CSD) representation. CSD representation minimizes the number of
5//! non-zero digits, which reduces the number of adders/subtractors needed in hardware implementation.
6//!
7//! # Overview
8//!
9//! In digital signal processing and hardware design, multiplying a variable by a constant is a common
10//! operation. Using CSD representation, we can implement these multiplications efficiently using only
11//! shifts, additions, and subtractions instead of full multipliers.
12//!
13//! # Single Multiplier (with LCSRe optimization)
14//!
15//! When the CSD string contains a repeated non-overlapping pattern with ≥2 non-zero digits,
16//! the generated Verilog shares hardware via a sub-expression wire `_pat`, reducing adder count.
17//!
18//! # Multi-Coefficient Cross-CSE
19//!
20//! `generate_csd_multipliers()` finds repeated substrings across **different** coefficients and
21//! creates a shared common sub-expression (CSE) wire, reducing total hardware across the filter.
22
23use std::collections::{BTreeSet, HashMap, HashSet};
24use std::fmt::Write;
25
26use crate::lcsre::longest_repeated_substring;
27
28/// Error type for CSD multiplier operations.
29#[derive(Debug, Clone, PartialEq)]
30pub enum CsdMultiplierError {
31    /// Invalid character found in CSD string (only '+', '-', '0' allowed)
32    InvalidCharacter,
33    /// Length of CSD string doesn't match expected length (max_power + 1)
34    LengthMismatch,
35    /// At least one coefficient is required
36    EmptyCoefficients,
37    /// All coefficients must share the same input_width and max_power
38    WidthMismatch,
39}
40
41/// A CSD-based constant multiplier that generates Verilog code
42///
43/// # Example
44///
45/// ```rust
46/// use csd::csd_multiplier::{CsdMultiplier, CsdMultiplierError};
47///
48/// // Create a multiplier for the CSD pattern "+00-00+" (value: 57)
49/// let multiplier = CsdMultiplier::new("+00-00+", 8, 6).unwrap();
50///
51/// // Generate Verilog code
52/// let verilog = multiplier.generate_verilog();
53/// assert!(verilog.contains("module csd_multiplier"));
54/// ```
55pub struct CsdMultiplier {
56    csd: String,
57    n: usize,
58    m: usize,
59}
60
61/// Specification for a single CSD multiplier coefficient
62///
63/// Used with [`generate_csd_multipliers()`] for multi-coefficient
64/// cross-common-subexpression elimination.
65#[derive(Debug, Clone)]
66pub struct MultiplierSpec {
67    /// Output port name (e.g. "y0", "y1")
68    pub name: String,
69    /// CSD string ('+', '-', '0')
70    pub csd: String,
71    /// Bit width of input x
72    pub input_width: usize,
73    /// Highest power (len(csd) - 1)
74    pub max_power: usize,
75}
76
77// ---------------------------------------------------------------------------
78// Internal helpers
79// ---------------------------------------------------------------------------
80
81#[derive(Debug, Clone, Copy, PartialEq)]
82enum TermOp {
83    Add,
84    Sub,
85}
86
87/// Parse a CSD string into (power, operation) pairs.
88fn 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
106/// Build a flat Verilog expression for a range [start, start+length) of the CSD string.
107fn 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
137/// Compute output width from input_width and max_power.
138///
139/// $$ W_{\text{out}} = W_{\text{in}} + m $$
140///
141/// where $W_{\text{in}}$ is the input bit width and $m$ is the maximum power of two.
142fn output_width(input_width: usize, max_power: usize) -> usize {
143    input_width + max_power
144}
145
146// ---------------------------------------------------------------------------
147// CsdMultiplier (struct-based, backward compatible)
148// ---------------------------------------------------------------------------
149
150impl CsdMultiplier {
151    /// Create a new CSD multiplier.
152    ///
153    /// # Arguments
154    ///
155    /// * `csd` - The CSD pattern string (e.g., "+0-")
156    /// * `n` - Input bit width
157    /// * `m` - Highest power index (length of CSD minus 1)
158    ///
159    /// # Errors
160    ///
161    /// Returns `CsdMultiplierError::InvalidCharacter` if the CSD string contains
162    /// characters other than '+', '-', or '0'.
163    ///
164    /// Returns `CsdMultiplierError::LengthMismatch` if the CSD string length
165    /// doesn't equal `m + 1`.
166    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    /// Calculate the decimal value represented by the CSD string.
182    ///
183    /// $$ v = \sum_{i=0}^{m} d_i \cdot 2^{m-i}, \quad d_i \in \{-1,0,+1\} $$
184    ///
185    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    /// Generate the Verilog module code (with LCSRe optimization).
198    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    /// Return sorted unique powers of non-zero digits, descending.
228    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    /// Generate assign statement with LCSRe optimization.
266    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        // Detect LCSRe optimization opportunity
275        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            // LCSRe-optimized path
294            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                // gap before this occurrence
311                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                // pattern occurrence
322                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            // suffix
336            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            // flat path (no repeated pattern)
347            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
365// ---------------------------------------------------------------------------
366// Free-function API (matching C++ style)
367// ---------------------------------------------------------------------------
368
369/// Find all non-overlapping occurrences of `pattern` in `csd_str`.
370fn 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
381/// Count non-zero digits ('+' or '-') in a CSD substring.
382fn count_nnz(s: &str) -> usize {
383    s.as_bytes()
384        .iter()
385        .filter(|&&c| c == b'+' || c == b'-')
386        .count()
387}
388
389/// Build a coefficient expression using CSE wire + flat gap terms.
390fn 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        // gap before this occurrence
407        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        // CSE reference
414        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    // suffix
423    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
440/// Find substrings (NNZ >= 2) that appear in >= 2 different CSD strings.
441/// Returns a map: pattern -> [(coeff_index, position), ...].
442fn 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            // Reusable buffer per start position, extended incrementally
449            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    // Keep only patterns crossing >= 2 different CSD strings
463    patterns.retain(|_, occ| {
464        let unique: HashSet<usize> = occ.iter().map(|(ci, _)| *ci).collect();
465        unique.len() >= 2
466    });
467    patterns
468}
469
470/// Generate Verilog code for a single CSD multiplier module (no cross-CSE).
471///
472/// Converts a Canonical Signed Digit (CSD) string into a synthesizable
473/// Verilog module that performs constant multiplication using shifts and
474/// additions/subtractions:
475///
476/// $$ y = \sum_{i=0}^{m} d_i \cdot (x \ll i), \quad d_i \in \{-1,0,+1\} $$
477///
478/// where $d_i$ is the CSD digit at position $i$, $x$ is the input, and
479/// $m$ is the highest power. When the CSD string contains a repeated
480/// non-overlapping pattern, LCSRe optimization shares hardware via a
481/// `_pat` wire.
482///
483/// # Arguments
484///
485/// * `csd_str` - CSD string using '+', '-', '0' (e.g. "+00-00+0+")
486/// * `input_width` - Bit width of the input signal x
487/// * `max_power` - Highest power of two in the CSD (must be csd_str.len() - 1)
488///
489/// # Errors
490///
491/// Returns `CsdMultiplierError` if csd_str length doesn't match max_power+1
492/// or if the string contains characters other than '+', '-', '0'.
493///
494/// # Examples
495///
496/// ```
497/// use csd::csd_multiplier::generate_csd_multiplier;
498///
499/// let v = generate_csd_multiplier("+0-", 8, 2).unwrap();
500/// assert!(v.contains("module csd_multiplier"));
501/// assert!(v.contains("assign result = x_shift2 - x_shift0"));
502/// ```
503pub fn generate_csd_multiplier(
504    csd_str: &str,
505    input_width: usize,
506    max_power: usize,
507) -> Result<String, CsdMultiplierError> {
508    // --- validation ---
509    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    // --- module header ---
525    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    // --- wire declarations (deduplicated powers) ---
542    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        // Reverse order: highest power first
547        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    // --- detect LCSRe optimization opportunity ---
563    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    // --- combinational logic ---
584    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        // LCSRe-optimized path
590        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            // prefix/gap before this occurrence
606            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            // pattern occurrence
617            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        // suffix
631        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        // flat path (no repeated pattern)
643        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
666/// Generate Verilog for multiple CSD multipliers with cross-CSE.
667///
668/// When the same CSD substring appears in multiple coefficients, a shared
669/// sub-expression wire is created — reducing total adder count across the
670/// entire filter.
671///
672/// For each coefficient $k$:
673///
674/// $$ y_k = \sum_{i=0}^{m} d_{k,i} \cdot (x \ll i), \quad d_{k,i} \in \{-1,0,+1\} $$
675///
676/// All coefficients **must** share the same `input_width` and `max_power`
677/// so that the same bit position encodes the same power of two.
678///
679/// # Arguments
680///
681/// * `coeffs` - List of coefficient specifications
682/// * `module_name` - Name for the generated Verilog module
683///
684/// # Errors
685///
686/// Returns `CsdMultiplierError::EmptyCoefficients` if the list is empty.
687/// Returns `CsdMultiplierError::WidthMismatch` if coefficient widths differ.
688///
689/// # Examples
690///
691/// ```
692/// use csd::csd_multiplier::{generate_csd_multipliers, MultiplierSpec};
693///
694/// let coeffs = vec![
695///     MultiplierSpec {
696///         name: "y0".to_string(),
697///         csd: "+00-00+0+".to_string(),
698///         input_width: 8,
699///         max_power: 8,
700///     },
701///     MultiplierSpec {
702///         name: "y1".to_string(),
703///         csd: "+00-00+0+".to_string(),
704///         input_width: 8,
705///         max_power: 8,
706///     },
707/// ];
708/// let v = generate_csd_multipliers(&coeffs, "csd_filter").unwrap();
709/// assert!(v.contains("module csd_filter"));
710/// ```
711pub 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    // Validation and uniform-width enforcement
720    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    // Collect all x_shift powers
741    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    // Find best cross-CSD pattern
751    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    // Base position for the CSE wire
769    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    // Build the Verilog module
780    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    // x_shift wires
802    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    // Shared CSE wire
818    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    // Set of coeff indices that have the pattern
844    let cse_coeffs: HashSet<usize> = best_occurrences.iter().map(|(ci, _)| *ci).collect();
845
846    // Per-coefficient assignments
847    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// ---------------------------------------------------------------------------
870// Tests
871// ---------------------------------------------------------------------------
872
873#[cfg(test)]
874mod tests {
875    use super::*;
876
877    // ---- Existing struct-based tests ----
878
879    #[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    // ---- Free-function tests (matching C++ test_csd_multiplier.cpp) ----
947
948    // Basic structural tests
949    #[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    // LCSRe optimization tests
998    #[test]
999    fn test_fn_flat_when_pattern_nnz_is_1() {
1000        // "+00-00+0" has no repeated pattern with ≥2 nnz
1001        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        // +0-0+0-0: repeated "+0-0" (2 nnz) at positions 0 and 4
1009        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        // +0-0+0-0+0-0: repeated "+0-0" at positions 0, 4, 8
1019        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        // +00-00+00-00: repeated "+00-00" (2 nnz, 5 chars) at positions 0 and 6
1028        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        // CSD starting with '-' and no repeated pattern
1037        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        // Repeated pattern starting with '-': -0+0-0+0
1045        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        // CSD with unique pattern throughout — no repeat = flat
1054        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        // output_width = 8 + 7 = 15, so wire signed [14:0]
1061        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        // Repeated pattern followed by non-repeating suffix
1068        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    // Edge cases
1074    #[test]
1075    fn test_fn_very_short_csd() {
1076        // Length-1 CSD
1077        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    // ---- Multi-coefficient tests ----
1107
1108    #[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}