`:
```verilog
// CSD "+0-0+0" at powers 7,5,3,1
wire signed [41:0] x_shift7 = x <<< 7;
wire signed [41:0] x_shift5 = x <<< 5;
wire signed [41:0] x_shift3 = x <<< 3;
wire signed [41:0] x_shift1 = x <<< 1;
assign result = x_shift7 - x_shift5 + x_shift3 - x_shift1;
```
---
class: nord-light, middle, center
## 2๏ธโฃ Direct-Form Architecture
---
### ๐๏ธ Direct-Form FIR โ Multiplier Bank
.pull-left[
**Structure**: each coefficient is a **separate output wire**.
```verilog
module fir_filter (
input signed [15:0] x,
output signed [41:0] h0,
output signed [41:0] h1,
// ...
output signed [41:0] h47
);
```
All $h_i = \text{coeff}_i \times x$ computed in **parallel** from same $x$.
**User must** add shift register + accumulator externally. โ ๏ธ
]
.pull-right[
.center[.mermaid[
graph LR
subgraph "Direct-Form"
X["x[n]"] --> H0["hโ = cโยทx"]
X --> H1["hโ = cโยทx"]
X --> H2["hโ = cโยทx"]
X --> H3["โฏ"]
X --> HN["hโโโ = cโโโยทx"]
end
style X fill:#e3f2fd,stroke:#1565c0
style H0 fill:#c8e6c9,stroke:#2e7d32
style H1 fill:#c8e6c9,stroke:#2e7d32
style H2 fill:#c8e6c9,stroke:#2e7d32
style HN fill:#c8e6c9,stroke:#2e7d32
]]
**Outputs**: N parallel `h` wires โ all combinational, no clock.
]
---
### ๐ Direct-Form Verilog Example
```verilog
// x_shift wires (one per unique power)
wire signed [41:0] x_shift17 = x <<< 17;
wire signed [41:0] x_shift14 = x <<< 14;
wire signed [41:0] x_shift12 = x <<< 12;
// ...
// Cross-CSE: shared sub-expression (pattern "+0-")
wire signed [41:0] _cse_0 = x_shift14 - x_shift12;
// Per-coefficient assignments
wire signed [41:0] h0 = (_cse_0 >>> 6) + x_shift4 + x_shift0;
wire signed [41:0] h5 = (_cse_0 >>> 5) + x_shift3 + x_shift0;
// ...
```
**Key observations** ๐:
- All `wire` โ no registers, no clock
- Cross-CSE shares repeated patterns across coefficients
- User must sum externally: $y[n] = \sum h_i \cdot x[n-i]$
---
class: nord-light, middle, center
## 3๏ธโฃ Transpose-Form Architecture
---
### ๐ Transpose-Form FIR โ Pipelined Accumulator
**Structure**: single output `y` with **pipeline registers**.
```verilog
module fir_filter (
input clk,
input rst_n,
input signed [15:0] x,
output signed [41:0] y
);
```
Coefficients applied in **reverse order** (canonical transpose form).
**Complete filter** โ no external summing needed! โ
.center[.mermaid[
graph TD
subgraph "Transpose-Form (Pipeline)"
X["x[n]"] --> M0["ร hโโโโโ"]
M0 --> R0["sumโ"]
R0 --> M1["+ ร hโโโโโ"]
M1 --> R1["sumโ"]
R1 --> M2["+ โฏ"]
M2 --> RN["sumโโโโโ = y[n]"]
end
style X fill:#e3f2fd,stroke:#1565c0
style M0 fill:#fff9c4,stroke:#f9a825
style M1 fill:#fff9c4,stroke:#f9a825
style RN fill:#c8e6c9,stroke:#2e7d32
]]
**Output**: single `y` โ registered, pipelined, complete.
---
### ๐ Transpose-Form Verilog Example
```verilog
// Same x_shift wires and CSE as direct-form!
wire signed [41:0] x_shift17 = x <<< 17;
wire signed [41:0] x_shift14 = x <<< 14;
wire signed [41:0] _cse_0 = x_shift14 - x_shift12;
// Pipeline registers (not wires!)
reg signed [41:0] sum0;
reg signed [41:0] sum1;
// ...
reg signed [41:0] sum47;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sum0 <= 0; sum1 <= 0; // ... sum47 <= 0;
end else begin
sum0 <= /* h[47]*x */; // last coefficient first
sum1 <= sum0 + /* h[46]*x */;
// ...
sum47 <= sum46 + /* h[0]*x */; // first coefficient last
end
end
assign y = sum47;
```
---
### โฑ๏ธ Pipeline Latency
The transpose-form has **1-cycle pipeline latency** at the output.
.center[.mermaid[
graph TD
subgraph "Impulse Response (x=1 at cycle 0)"
C0["Cycle 0: y = 0"] --> C1["Cycle 1: y = hโ"]
C1 --> C2["Cycle 2: y = hโ"]
C2 --> C3["Cycle 3: y = hโ"]
C3 --> CN["โฏ"]
CN --> C32["Cycle 32: y = hโโ"]
end
style C0 fill:#ffcdd2,stroke:#c62828
style C1 fill:#c8e6c9,stroke:#2e7d32
style C2 fill:#c8e6c9,stroke:#2e7d32
style C32 fill:#c8e6c9,stroke:#2e7d32
]]
At cycle $i$, $y = h[i-1]$ (with $h[-1] = 0$).
The output completely represents the **impulse response** โ just shifted by 1 cycle.
---
class: nord-light, middle, center
## 4๏ธโฃ Side-by-Side Comparison
---
### โ๏ธ Direct vs Transpose โ Structural Differences
.pull-left[
#### Direct-Form
.font-sm.mb-xs[
| Aspect | Detail |
|--------|--------|
| Ports | 1 input, N outputs |
| Clock | โ None |
| Reset | โ None |
| Registers | โ None (wires only) |
| Output | $h[0] \dots h[N-1]$ |
| Integration | External accumulator needed |
]
**Best for**: Custom accumulator trees,
multi-rate filters, partial product reuse.
]
.pull-right[
#### Transpose-Form (Default)
.font-sm.mb-xs[
| Aspect | Detail |
|--------|--------|
| Ports | clk, rst_n, 1 input, 1 output |
| Clock | โ
`posedge clk` |
| Reset | โ
Active-low `rst_n` |
| Registers | N pipeline `sum` regs |
| Output | Single $y = \sum h[i] \cdot x$ |
| Integration | Instantiate and connect |
]
**Best for**: Drop-in filter module,
minimum external wiring.
]
---
### ๐ก Same CSD, Same CSE โ Different Structure
Both forms share the **same building blocks**:
```text
โโโโโโโโโโโโโโโโโโโโ
โ CSD Strings โ
โ (same coeffs) โ
โโโโโโโโโโฌโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ x_shift wires โ โ Deduplicated powers
โ _cse_0 pattern โ โ Cross-CSE
โโโโโโโโโโฌโโโโโโโโโโ
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
โ Direct-Form โ โ Transpose-Form โ
โ N output wires โ โ N pipeline regs โ
โ No clock โ โ Single output y โ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
```
**Same coefficients** โ same CSD quantization โ same hardware cost per tap.
---
### ๐ง When to Use Each
.pull-left[
**Choose Direct-Form when** โ
- You need raw tap products for custom accumulation
- Building an adder tree with your own pipeline
- Multi-rate or polyphase filter structures
- Coefficient visibility for debugging
**Example**: Polyphase decimation filter where
each phase needs separate tap access.
]
.pull-right[
**Choose Transpose-Form when** โ
- You want a complete drop-in filter module
- Minimizing external wiring
- Standard FIR filtering applications
- Quick prototyping and integration
**Example**: Single-rate FIR as part of
a larger DSP chain โ connect and go.
]
---
class: nord-light, middle, center
## 5๏ธโฃ Cross-CSE Optimization
---
### ๐ Cross-CSE Explained
When the same CSD pattern appears in **multiple coefficients**, compute it once:
.center[.mermaid[
graph TD
subgraph "Without CSE (2 adders)"
H0["hโ: x_shift14 - x_shift12"] --> A1["+ x_shift4 + x_shift0"]
H5["hโ
: x_shift14 - x_shift12"] --> A2["+ x_shift3 + x_shift0"]
end
subgraph "With CSE (1 adder shared)"
CSE["_cse_0 = x_shift14 - x_shift12"] --> H0C["hโ: (_cse_0 >>> 6) + ..."]
CSE --> H5C["hโ
: (_cse_0 >>> 5) + ..."]
end
style CSE fill:#fff9c4,stroke:#f9a825
style H0C fill:#c8e6c9,stroke:#2e7d32
style H5C fill:#c8e6c9,stroke:#2e7d32
]]
```verilog
wire signed [41:0] _cse_0 = x_shift14 - x_shift12;
// Pattern "+0-" found in h0, h5, h13, h27 โ factored out!
assign h0 = (_cse_0 >>> 6) + x_shift4 + x_shift0;
assign h5 = (_cse_0 >>> 5) + x_shift3 + x_shift0;
```
Results from 32-tap filter: **82 cells** with CSE vs ~110 without (25% reduction) ๐
---
### ๐งฎ How CSE Pattern Selection Works
The algorithm scores each candidate pattern:
$$ \text{score} = (\text{nnz} - 1) \times (\text{occurrences} - 1) $$
Where nnz = number of non-zero CSD digits in the pattern.
```python
def _score(pattern, occurrences):
nnz = pattern.count("+") + pattern.count("-")
return (nnz - 1) * (len(occurrences) - 1)
```
The pattern with the **highest score** saves the most adders.
**Cross-platform**: Identical algorithm in C++ (`csd-cpp`) and Python (`csdigit`) โ
both libraries produce the same `_cse_0` wire for the same coefficients. โ
---
class: nord-light, middle, center
## 6๏ธโฃ Cross-Platform Implementation
---
### ๐ C++ vs Python Codegen
Both projects implement the **same two-form architecture**:
.pull-left[
#### C++ (multiplierless-cpp)
```
โโโโโโโโโโโโโโโโโโโโโโโ
โ csd-cpp library โ โ External dep (CPM)
โ generate_csd_ โ
โ multipliers() โ โ Direct-form
โโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโ
โ fir_design.cpp โ
โ generate_transpose_โ
โ form_verilog() โ โ Transpose-form
โโโโโโโโโโโโโโโโโโโโโโโ
```
]
.pull-right[
#### Python (multiplierless)
```
โโโโโโโโโโโโโโโโโโโโโโโ
โ csdigit library โ โ External dep (pip)
โ generate_csd_ โ
โ multipliers() โ โ Direct-form
โโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโ
โ fir_design.py โ
โ _generate_transposeโ
โ _verilog() โ โ Transpose-form
โโโโโโโโโโโโโโโโโโโโโโโ
```
]
Both share the same `form: "transpose"` (default) or `form: "direct"` JSON option.
---
class: nord-light, middle, center
## 7๏ธโฃ Verification
---
### โ
Verification with iverilog
Self-checking test benches verify both forms:
**Transpose-form test** โ impulse response:
```text
PASS: cycle 0: y=0
PASS: cycle 1: y=2287
PASS: cycle 2: y=275
...
PASS: cycle 31: y=83
PASS: cycle 32: y=105
ALL TESTS PASSED (33 checks)
```
**Direct-form test** โ 4 test values ร 32 taps:
```text
PASS: x=1, h0=2287 PASS: x=1, h1=275 ...
PASS: x=-1, h0=-2287 PASS: x=-1, h1=-275 ...
PASS: x=127, h0=290449 PASS: x=127, h1=34925 ...
PASS: x=-128,h0=-292736 ...
ALL TESTS PASSED
```
---
### ๐ Verification with cocotb (Python-driven)
cocotb enables **Python-driven Verilog simulation** โ test logic in Python, run in iverilog:
.center[.mermaid[
graph TD
subgraph "cocotb Test Flow"
PY["Python Test\n(test_transpose.py)"] --> VPI["VPI Bridge\n(cocotbvpi_icarus)"]
VPI --> DUT["Verilog DUT\n(fir_filter.v)"]
DUT -->|"signals"| PY
PY -->|"assert"| RESULT["PASS/FAIL"]
end
style PY fill:#e3f2fd,stroke:#1565c0
style DUT fill:#c8e6c9,stroke:#2e7d32
style RESULT fill:#fff9c4,stroke:#f9a825
]]
**Results**:
- Transpose: **33/33 checks PASS** โ
- Direct: **All 128 checks PASS** โ
---
### ๐ช Windows-Specific cocotb Setup
cocotb on Windows needs **3 workarounds** that Linux/macOS tutorials don't cover:
.font-sm.mb-xs[
| # | Issue | Fix |
|---|-------|-----|
| 1 | Missing Python DLL | `PYGPI_PYTHON_BIN=python.exe` |
| 2 | No "`timescale" in generated Verilog | Prepend "`timescale 1ns/1ps" at compile time |
| 3 | `COCOTB_TEST_MODULES` env var | Set (not `MODULE`) for cocotb 2.0 |
]
**Runner skeleton**:
```python
env["PYGPI_PYTHON_BIN"] = sys.executable
env["TOPLEVEL"] = "fir_filter"
env["COCOTB_TEST_MODULES"] = "test_transpose"
subprocess.run([
"vvp", "-M", cocotb_libs, "-m", "cocotbvpi_icarus",
"sim.vvp",
], env=env)
```
Documented as the **cocotb-for-windows** agent skill! ๐
---
class: nord-light, middle, center
## 8๏ธโฃ Results & Takeaways
---
### ๐ Verification Results Summary
| Form | Test Method | Checks | Result |
|------|------------|--------|--------|
| **Transpose** | iverilog TB | 33 impulse response | โ
ALL PASS |
| **Transpose** | cocotb | 33 impulse response | โ
ALL PASS |
| **Direct** | iverilog TB | 128 (4 values ร 32 taps) | โ
ALL PASS |
| **Direct** | cocotb | 128 (4 values ร 32 taps) | โ
ALL PASS |
**Both forms produce identical CSD-quantized values** for the same coefficients.
Cross-CSE optimizations apply equally to both architectures. ๐ฌ
---
### ๐ฏ Key Takeaways
.pull-left[
**For Developers** ๐จโ๐ป
- **Transpose-form** is now the **default** โ drop-in FIR module
- **Direct-form** available via `form: "direct"` โ custom integration
- Same CSD + CSE optimization for both
- Both C++ and Python projects updated
]
.pull-right[
**For Verification** ๐งช
- iverilog self-checking test benches
- cocotb Python-driven simulation
- Windows setup documented
- Pipeline latency: 1 cycle
- Signed value conversion handled
- Both forms produce identical results
]
---
### ๐ Quick Start
```bash
# C++ โ generate transpose-form Verilog (default)
echo '{"filter_order":32,"csd_nnz":4,"verilog":{"input_width":16}}' \
> spec.json
FirDesign spec.json | python tools/extract_verilog.py
# Python โ same, with form option
echo '{"filter_order":32,"csd_nnz":4,"verilog":{"form":"direct"}}' \
> spec.json
fir-design spec.json > output.json
# Run cocotb tests
python sim/run.py
```
**JSON option**:
```json
"verilog": { "form": "transpose" } // default
"verilog": { "form": "direct" } // opt-in
```
---
count: false
class: nord-dark, middle, center
## ๐ Thank You
### Direct-Form vs Transpose-Form FIR
**Slides**: [`luk036.github.io/AxC/direct-transpose-remark`](https://luk036.github.io/AxC/direct-transpose-remark.html)
**Repos**:
- [luk036/multiplierless-cpp](https://github.com/luk036/multiplierless-cpp)
- [luk036/multiplierless](https://github.com/luk036/multiplierless)
**Key takeaway**: *Same coefficients, same CSD, same CSE โ different structure, different tradeoffs.*
@luk036 ๐จโ๐ป ยท 2026 ๐