class Sphere {
auto pop() {
auto cosphi = 2 * this->vdcgen.pop() - 1; // [-1, 1]
auto sinphi = sqrt(1 - cosphi * cosphi);
auto [cx, cy] = this->cirgen.pop(); // Circle<3>
return {sinphi * cx, sinphi * cy, cosphi};
}
};
```
.mermaid[
graph LR
en["en ๐"] --> V2["vdc_16"]
en --> C3["circle_3_16"]
V2 --> CP["cosphi = angle - 0x8000"]
CP --> Z["z = cosphi"]
CP --> SQIN["1 - cosphi^2"]
SQIN --> SQ["cordic_sqrt_16\nโ"]
SQ -->|"sinphi"| MX["ร"]
SQ -->|"sinphi"| MY["ร"]
C3 -->|"cos(theta)"| MX
C3 -->|"sin(theta)"| MY
MX --> X["x"]
MY --> Y["y"]
Z --> ZO["z"]
style SQ fill:#2e3440,stroke:#bf616a,color:#eceff4
style CP fill:#2e3440,stroke:#ebcb8b,color:#eceff4
]
---
### ๐บ๏ธ Module Reuse Map
.mermaid[
graph TD
V16["๐ท vdc_16\nVdC<2> bit-rev"] --> C16["๐ท cordic_16\nCORDIC sin/cos"]
V16 -.-> Z["z = cosphi"]
V16 -.-> SPH["sphere_23"]
V3I["๐ท vdc_3_ilds\nVdC<3> 2^2-1"] -.-> C16
V3I --> SQRT["๐ท cordic_sqrt_16\nhyperbolic CORDIC sqrt"]
C16 --> C216["circle_2_16"]
C16 --> C316["circle_3_16"]
C216 --> DISK["disk_23"]
C316 --> SPH
SQRT --> DISK
SQRT --> SPH
style V16 fill:#5e81ac,stroke:#88c0d0,color:#eceff4
style V3I fill:#5e81ac,stroke:#88c0d0,color:#eceff4
style C16 fill:#5e81ac,stroke:#a3be8c,color:#eceff4
style SQRT fill:#5e81ac,stroke:#bf616a,color:#eceff4
style C216 fill:#4c566a,stroke:#ebcb8b,color:#eceff4
style C316 fill:#4c566a,stroke:#ebcb8b,color:#eceff4
]
**5 base modules -> 5 composite modules** ๐ง
**All share the same CORDIC core** ๐ช
---
## ๐ฌ Verification with iverilog
Each module has a testbench checking against C++ reference values ๐
.mermaid[
graph TD
subgraph "Testbench"
TB["๐ Assert en"] --> DUT["DUT"]
DUT -->|"valid"| CAP["Capture outputs"]
REF["Expected values\n(from C++)"] --> CMP["Compare"]
CAP --> CMP
CMP -->|"error < 10"| PASS["โ
PASS"]
CMP -->|"error > 10"| FAIL["โ FAIL"]
end
style PASS fill:#2e3440,stroke:#a3be8c,color:#eceff4
style FAIL fill:#2e3440,stroke:#bf616a,color:#eceff4
]
Testbench pattern:
```verilog
en = 1; @(posedge clk); #1; en = 0;
@(posedge valid); #1;
err = $signed(got) - $signed(exp);
if (err < 0) err = -err;
if (err < 10) $display("PASS");
```
---
## ๐ ๏ธ Yosys Logic Synthesis
RTL to gate-level netlist in one script ๐๏ธ
```tcl
read_liberty -lib ../tutorial_tech.lib # read std-cell library
read_verilog ../rtl/circle_2_16.v # read RTL
hierarchy -top circle_2_16 # elaborate
synth -top circle_2_16 # coarse synthesis
dfflibmap -liberty ../tutorial_tech.lib # map DFFs
abc -liberty ../tutorial_tech.lib # map combinational
stat # report area
write_verilog circle_2_16_synth.v # gate-level netlist
write_json circle_2_16_synth.json # JSON netlist
```
Synthesized netlist verified with iverilog + Yosys simlib.v:
```
iverilog -o test tb.v synth.v /path/to/simlib.v
vvp test --> same 12/12 PASS โ
```
---
### ๐ Synthesis Results
| Module | Cells | DFFs | Key Logic |
|:-------|:-----:|:----:|:----------|
| vdc_16 | **72** | 17 | bit-reversal = **0 gates!** ๐ฅ |
| vdc_3_ilds | 1443 | 70 | 2^2-1 trick + 10-factor MAC |
| vdc_7_ilds | 1025 | 69 | 2^3-1 trick + 5-factor MAC |
| cordic_16 | ~1000 | ~70 | 16 shift-add iterations |
| cordic_sqrt_16 | ~1000 | ~67 | 20 hyperbolic iterations |
**Key insight:** Base-2 VdC costs only a counter (72 cells).
Base-3 needs 20x more logic (1443 cells) for div/mod/MAC.
CORDIC dominates area for Circle/Disk/Sphere (~70% of total).
---
### ๐ JSON Netlist Validation
```json
{
"creator": "Yosys 0.9+2406",
"modules": {
"vdc_16": {
"ports": { "clk": "input", "rst_n": "input", ... },
"cells": { ... }, // 72 cell instances
"netnames": { ... }
}
}
}
```
Schema validation:
```
$ python validate_yosys_json.py
Validating against yosys_schema.json...
*** SCHEMA VALIDATION PASSED ***
```
Both schema check and netlistx loading pass โ
Netlist simulation with simlib.v: **all tests pass** โ
---
### ๐ Verification Results: 88 Tests All Pass
| Module | Tests | RTL | Synth | Max Error |
|:-------|:-----:|:---:|:-----:|:---------:|
| vdc_16 (bit-reversal) | 20 | โ
| โ
| 0 (exact) |
| vdc_3_ilds (2^2-1 trick) | 12 | โ
| โ
| 0 (exact) |
| vdc_7_ilds (2^3-1 trick) | 12 | โ
| โ
| 0 (exact) |
| circle_2_16 | 12 | โ
| โ
| 5 LSB |
| circle_3_16 | 12 | โ
| โ
| 6 LSB |
| disk_23 | 10 | โ
| -- | 11 LSB |
| sphere_23 | 10 | โ
| -- | 11 LSB |
**Flow:** C++ -> Python -> Verilog (iverilog) -> Yosys -> JSON -> netlist sim ๐
---
## ๐ Bug Stories: 6 Bugs Found & Fixed
### ๐ Bug #1: Missing reg signed
```verilog
// WRONG -- unsigned! >>> does LOGICAL shift
reg [17:0] x, y;
// CORRECT -- signed! >>> does ARITHMETIC shift
reg signed [17:0] x, y;
```
x goes negative during CORDIC iterations.
With unsigned reg, >>> zero-fills instead of sign-extending.
All subsequent iterations compute with wrong values! ๐ฅ
---
### ๐ Bug #2: Quadrant Truncation
```verilog
// WRONG -- 14-bit truncation
z <= {2'b00, 16'h4000 - angle[13:0]};
// CORRECT -- full 16-bit
z <= 16'h4000 - {2'b00, angle[13:0]};
```
For angle = 0xC000 (3*pi/2): 0x4000 - 0 = 0x4000 = 16384.
BUT 0x4000 is 15 bits! {2'b00, 14-bit} drops bit 14.
z becomes 0 instead of pi/2. ๐ฑ
---
### ๐ Bug #3: Python Serial vs Verilog Parallel
```python
# WRONG -- sequential!
x -= (y >> i)
y += (x >> i) # x already changed!
```
CORDIC requires BOTH x and y to use ORIGINAL values (parallel).
Verilog NBA (<=) does this correctly. Python = does NOT! ๐ฅ
```python
# CORRECT -- use temporaries
xn = x - (y >> i)
yn = y + (x >> i) # uses original x!
x, y = xn, yn
```
---
### ๐ Bug #4: 16-bit Multiply Truncation
```verilog
// WRONG -- 16-bit x 16-bit = 16-bit result!
wire signed [31:0] sq = a * b; // truncated!
```
(-16384)^2 = 268435456 = 0x10000000. But 16-bit result = 0x0000! ๐ฅ
```verilog
// CORRECT -- 32-bit operands
wire signed [31:0] a_ext = {{16{a[15]}}, a};
wire signed [31:0] sq = a_ext * a_ext;
```
**Lesson:** Verilog multiply width = max operand width,
NOT twice the width. Always widen! ๐ง
---
### ๐ Bug #5: Saturation Threshold Typo
```verilog
// WRONG -- 28-bit threshold (missing two Fs!)
(tmp > 32'h3FFFFFF) ? 65535 : (tmp >> 14);
// CORRECT -- 32-bit threshold
(tmp > 32'h3FFFFFFF) ? 65535 : (tmp >> 14);
```
0x3FFFFFF = 67 million. Typical tmp = 805 million.
805M > 67M --> always saturates to 65535! ๐ฅ
Count your hex digits! ๐ง
---
### ๐ Bug #6: State Machine Losing Handshake
.mermaid[
graph TD
subgraph "Bug: en arrives in S_DONE"
IDLE1["S_IDLE"] -->|"en"| WAIT1["S_WAIT"]
WAIT1 -->|"both done"| DONE1["S_DONE"]
DONE1 -->|"valid=0, go IDLE"| IDLE1
EN["๐ en (2nd pulse)"] -.-> DONE1
DONE1 -.->|"โ S_IDLE never runs"| NO["Flags never reset!"]
end
style NO fill:#3b4252,stroke:#bf616a,color:#eceff4
]
**Fix:**
1. Capture valid signals UNCONDITIONALLY (outside case statement)
2. Transition from S_DONE to S_WAIT directly when en is high
```verilog
always @(posedge clk) begin
if (circle_valid) circle_done <= 1; // unconditional!
if (sqrt_valid) sqrt_done <= 1;
case (state)
S_DONE: if (en) state <= S_WAIT; // skip S_IDLE!
```
---
## ๐ Summary
### ๐ Key Techniques
| Technique | Application | Area Impact |
|:-----------|:------------|:-----------:|
| ๐ช Bit reversal | Base-2 VdC | **0 logic cells** ๐ฅ |
| โจ 2^n-1 trick | Base-3/7 div/mod | ~1400 cells |
| ๐ฆ Integer factors | ROM replacement | **160 bits** vs 128 KB |
| ๐ Circular CORDIC | sin / cos | ~1000 cells |
| ๐ Hyperbolic CORDIC | sqrt(x) | ~1000 cells |
| ๐งฉ Module reuse | Circle/Disk/Sphere | cascaded |
### ๐ฏ Accuracy
All errors < 0.04% (CORDIC approximation + fixed-point truncation).
Z-axis (Sphere): **exact** โ
### ๐พ Zero ROM
No lookup tables anywhere. Pure shift-add computation! ๐ช
---
count: false
class: nord-dark, middle, center
# ๐ Thank You!
### github.com/luk036/lds-hdl
**Verification:** 88 tests via iverilog โ
**Synthesis:** Yosys -> gate-level netlist -> JSON โ
**Validation:** Schema match + netlist loading โ
### Questions? ๐ค