Clock-Enable Refactor: eliminating the derived-clock nets¶
Full path: Verilog/docs/clock-enable-refactor.md
Last updated: 2026-09-28
Status: DONE 2026-07-05 (CYC_TERM_D.v, CYC_CC_D.v, the second
PAL_44307C instance in CYC_36.v); the level fix for MCLK/MACLK/UCLK
followed 2026-07-07. This file keeps how it works and the rules learned.
The planning sections and the failed experiments are in git history.
Why¶
The CPU makes its clocks (ALUCLK, MCLK, MACLK, UCLK, CLK) with
combinational gates in CYC_36 and fans them out as clock nets. On the
FPGA, Vivado force-inserted global buffers on exactly these nets and STA
could not constrain the dozens of gated clock domains (Basys3 routed WNS
was about -38.7 ns on 2026-07-04). The fix below turns them into clean
sysclk-generated flip-flop clocks in FPGA_FF_MODE.
IMPLEMENTED (2026-07-05): phase-accurate clocks via validated mirrors¶
The gated-combinational clocks minted in CYC_36 are now clean sysclk-generated FF
clocks in FPGA_FF_MODE, boot byte-identical to golden (seqcheck IDENTICAL). The
key that made it work (after edge/level/uniform all failed by +1 sysclk): fire each
clock's enable on the FSM's next-state, which leads by one cycle, so registering
it lands the rising edge on the exact edge the old gated clock fired (net-zero
latency).
- Validated next-state mirrors of
PAL_44601B(PALs stay untouched & golden):CYC_TERM_D.v- TERM next-state. Exhaustively validated == PAL (all 16 states x 2 TERM x 128 inputs = 4096 checks, 0 err; mutation-tested).make test-cyctermd.CYC_CC_D.v- CC3..CC0 next-state. Exhaustively validated == PAL (512 checks, 0 err).make test-ccd. (Both inCPU-BOARD-3202/circuit/sim/.)
- MCLK/MACLK/UCLK next values: a SECOND (combinational)
PAL_44307Cinstance is fed the next-state (TERM_D + CC_D) - the real PAL reused, no mirror of 44307. - Per clock: register the predicted next LEVEL:
q <= <clock>_NEXT. UPDATE 2026-07-07: originally this registered the rise pulse<clock>_NEXT & ~<clock>_NOW. That kept every RISING edge phase-accurate (which is all boot seqcheck exercises) but collapsed the HIGH phase to 1 sysclk. Level consumers then broke:CPU_CS_ACAL_17's address latches are transparent while MACLK is HIGH, so with a 1-sysclk MACLK the WCS address (LUA) froze mid-microcycle and the DGA instruction-dispatch address (WCA, e.g. o7250) never reached the WCS -> FF-mode program execution hung (the 07-JUL "20!hangs in FF mode" bug). MCLK/MACLK/UCLK are now level-registered: identical rising edge, full high phase reproduced. RULE: a generated replacement for a gated clock must reproduce the WAVEFORM, not just the rising edge - transparent-latch consumers use it as a level.- ALUCLK:
aluclk_en = TERM_D & ~LCS. - CLK (
~TERM_n):clk_en = TERM_D; the cleanclk_paalso feeds the FSM's ownPAL_44403/44404.CLK(their clock source changes, the PALs do not). - MCLK/MACLK:
~(TERM_n_next & MCLK_n_next/MACLK_n_next)from the 2nd 44307. - UCLK:
TERM_n_next & UCLK_44307_next.
- ALUCLK:
- Not converted: WRFSTB (no posedge/clock use - the WRF clocks on ALUCLK).
- Consumers unchanged - they still
posedge <clock>, but on a clean FF clock instead of a gated LUT net. (True single-sysclk-domain, i.e. converting consumers toif(<clk>_en), is a later optional step - mainly for Gowin.) - OSC (the FSM clock):
= sysclkin sim; on FPGAoc_select=2'b11constant- folds it toclk1, so it is a clean clock net (not the gated-clock problem).
Gate for all of the above: sim/seqcheck.py after make compare -> address
sequence IDENTICAL, FF reaches o002001@69778 / o002047@72362.
The two idioms, and WHEN to use each (this is the load-bearing decision)¶
Shared/ndlib/LATCH.v is already converted and its header records the trap:
Edge-detect was tried first and broke LCS loading, because during LCS load
s_aluclk_nis held high constantly - no rising edge ever appears, so an edge-detect FF never fires and CSEL_Q stayed at its init value.
So there are two patterns, and picking wrong silently breaks boot:
A. Level-capture (the LATCH pattern) - the SAFE DEFAULT.
Use when the "clock"/enable can be held static across a whole phase (the LCS load holds ALUCLK's source static). MatchesL4.v/L8.v/LATCH.v already in the
tree. Transparent on a 1-sysclk grain - the closest synchronous analog of the
original 74xx transparent latch.
B. Edge-enable (one-shot) - only where a true single pulse per cycle is needed.
reg d1; always @(posedge sysclk) d1 <= raw;
wire en = raw & ~d1; // 1-sysclk pulse on rising edge
always @(posedge sysclk) if (en) q <= d;
RESOLVED RULE (2026-07-04 investigation). The level-vs-edge choice is decided by the primitive's type, not per-instance guesswork:
- Transparent latches (
LATCH,L4,L8) are level-sensitive - they must track D across the whole enable-high window -> pattern A (level-capture). Already converted. The LCS trap only ever applied to these. - Edge flip-flops (
D_FLIPFLOP,J_K_FLIPFLOP,T_FLIPFLOP,R81,R41P,R81P,SCAN_FF) trigger onposedge <clock>-> pattern B (edge-detect). There is NO LCS trap for them: a real edge FF clocked on a static-held signal also never fires, so edge-detect (raw & ~raw_d) is the exact equivalent.
So the FF-primitive conversion is uniform - edge-detect for all of them - and
make compare (only-BDRY gate) confirms each one. This removes the biggest
correctness risk the earlier draft flagged.
Lesson from the failed attempts (2026-07-04/05)¶
Edge-detect, level-capture and a uniform sysclk migration of the consumers all failed at the same point: each captured ONE sysclk late, because an enable derived from an already-risen clock is late by construction. The working fix fires each enable from the cycle FSM's NEXT state (available combinationally before the edge), so the registered clock rises on the exact sysclk edge the old gated clock did.
References¶
fpga-debug-methodology.md3.2 - root cause + the derived-clock work list.plan-fix-unconstrained-clocks.md- the later net-by-net conversion of the remaining derived clocks (finished 10-JUL-2026).sim/FPGA_REFACTORING_GUIDE.md- the async-clock -> synchronous pattern.Shared/ndlib/LATCH.v- the already-converted reference (pattern A + the LCS trap).boot-golden-spec.md- the boot phasesmake comparemust still hit.