Long session straight after the P01 harden landed. Project 02 is the first sequential design on the ladder — 8-bit counter, 8-bit Fibonacci LFSR (max-length, taps 8/6/5/4), 1-bit PWM, 2-bit mode mux. ~50 lines of RTL.
Three things landed: the project itself, a switch to SystemVerilog, and a full rebuild of the layout/3D viewer pipeline.
RTL pass on the first compile
Wrote the testbench in six phases (reset, 1024-cycle exhaustive run, counter wrap check, exact PWM duty, 255-state LFSR coverage, mode mux). All passed first compile.
$ make test PROJECT=02_counter_pwm_lfsr
PASS: counter (4 wraps), LFSR (255 states), PWM (256/1024 high), mode mux all OK.
02_counter_pwm_lfsr PASS
Two things made first-compile pass not a fluke:
- The reference inline LFSR I used to predict states matched the
Galois taps (8/6/5/4 numbered from 1, i.e.
lfsr[7] ^ lfsr[5] ^ lfsr[4] ^ lfsr[3]in 0-indexed land). Easy to get wrong. - PWM duty check is exact, not statistical. With
threshold=0x40over 4 wraps, expect exactly 256 high cycles. Anything else would fail loudly.
Switched the project ladder to SystemVerilog
Project 01 was plain Verilog 2001 to keep the toolchain happy. P02
introduces flops, and the SV always_ff / always_comb discipline
catches sequential-vs-combinational mistakes at compile time —
worth the upgrade for everything from here forward.
- Yosys 0.16+ handles the synth subset natively (
logic,always_ff,always_comb,unique case,typedef enum). - Iverilog needs
-g2012. Updated the Makefile.
The visible code reads the same. The compiler just gets stricter.
Hardened, with a real-flow lesson
LibreLane ran end-to-end at 100 MHz on sky130A. Result: 1946 std cells, +1.24 ns setup ws, +0.12 ns hold ws, 0 DRC, 0 LVS, 0 antenna — and 11 max-slew violations in the slow PVT corner.
That’s the first non-zero-violation harden in this project. I didn’t fix it. It’s a good artifact: a real-flow violation in a real run on a small design, useful to point at in the lesson page rather than chase. Possible mitigations are listed there for the inevitable “would tightening MAX_TRANSITION fix this?” question later.
Cell count exploded vs P01 (81 → 1946). Most of the difference is not the new flops (16 of them); it’s the clock tree. CTS inserted a balanced buffer fan-out so every flop’s clock arrives within tens of picoseconds of every other. Real flops + real CTS is where digital design actually starts costing silicon.
Viewer pipeline: full rebuild
The naïve approach from P01 (inline 9 MB SVG into the page, plus a
15 MB GLB on <model-viewer> which I tried first) does not scale.
Three rounds of pivots:
- SVG inline → SVG with viewBox transform — pan/zoom worked but the browser’s cached bitmap looked fuzzy at any zoom that wasn’t 1×.
- SVG → canvas 2D with parsed polygon JSON — re-rasterizes every frame at the current viewport, sharp at every zoom. Pan went from ~5 fps (SVG, ~187 ms / frame) to 60 fps (canvas, ~16 ms / frame). ~11× speedup.
- inline GLB → meshopt-compressed GLB.
gltfpack -cc -kncompresses geometry losslessly;-knkeeps named nodes so the Three.js layer panel still groups polygons by sky130 layer name. P02 went 15 MB → 1.9 MB. Three.js needsMeshoptDecoderwired up; one extra import.
Side effects of this work:
- A reusable
<Source>component that fetches a project file at build time and pipes it through Astro’s<Code>for syntax highlighting, with line numbers via CSS counter. Pine64-style. - A
mise run viz:assets <project>task wrappingscripts/build_layout_assets.sh, which readsDIE_AREAfrom the project’s librelane config and produces all three artifacts (PNG, shapes.json, GLB) in one shot. So future projects don’t redo this.
Centering / layer-panel fights
Both viewers had their own version of the same bug: a layer panel overlapping the canvas, hijacking pan-drag, and the camera fitting to a pre-load placeholder rather than the actual model.
- Layout viewer: layer panel was an absolutely-positioned overlay;
pointerdown inside it would start a pan. Fix: short-circuit
pointerdown if the event target is inside
.lv-layers. - Chip viewer: layer panel moved out of the canvas entirely, into
a flex sidebar. Fit logic now runs
controls.saveState()after the fit, soR/ double-click resets back to the centered post-load position rather than the placeholder camera.
(The 2D viewer also had a y-axis bug from the SVG-flip handling that hid most of the chip; I didn’t catch it until much later when the full-width layout exposed it. Fixed in a later commit.)
Now what
- 02 →
hardened. Site shows real metrics. - Asset pipeline is reusable. P03 onward will not redo any of this.
mise run sim:allexists and runs every project’s testbench.- Next: project 03, UART transmitter. First protocol, first explicit FSM.