hardware · FPGA observation
Intermittent nibble slip during quad flash reads
On the FPGA, reading helper data back from flash returned a small number of corrupted
words. The count varied across bitstreams (for example 37, 41, then 32 of 88), while the
digital simulation did not reproduce the failure.
Investigation
The errors were consistent with a one-nibble (4-bit) slip, not random bit errors.
The missing nibble frequently had both DQ3=1 and DQ0=1, indicating
a data-dependent effect. Re-reading one address produced different values, slipped words
contained the next word's upper nibble, and an external reader recovered the flash contents
correctly. Together, these checks localized the issue to the quad read path.
Root cause & fix
A quad read includes eight dummy cycles for bus turnaround: the master must release the
data lines before the flash drives them. The dummy state was still using the transmit
branch and held the DQ lines low until the data phase. The fix was to tri-state the lines
during the dummy interval. Single-line reads did not show the issue because they use a
different input/output path.
Lesson: digital simulation does not model all board-level bus contention effects. The full diagnosis is summarized on the
Verification page.
simulation · behavioral model issue
Race condition in the flash model
To test the Quad-SPI master, we wrote a behavioral model of the W25Q64JV flash. Early on,
the master would wait indefinitely in its status-poll loop — waiting for a BUSY bit
that, in the model, never seemed to clear.
Root cause & fix
The first model split its logic across four event-driven blocks triggered by SCLK,
chip-select, and the system clock. Several blocks wrote the same status registers. In some
schedules, the read-status decoder updated one clock after the edge that should have
driven the first output bit, so the master sampled an undefined value instead of
BUSY. The model was rewritten as a single clocked process that decodes
each command on the edge that receives it.
Lesson: verification models also need clear clocking discipline. A simpler event structure made the model easier to reason about and debug.
integration · off by one bit
FIFO entry width mismatch
Every flash job is one FIFO entry: a read/write flag, a 24-bit address and a 32-bit data
word, for a total of 57 bits. During integration, writes were interpreted as reads:
no write command reached the flash, and several assertions failed together.
Root cause & fix
The FIFO had been declared 56 bits wide. The top bit — the read/write flag — was
being truncated, so it always read back as 0 (= read). Widening the FIFO entry to 57 bits
corrected the command interpretation and resolved the related failures.
Lesson: when many tests fail in the same direction, check shared structural assumptions such as packed widths before debugging each symptom independently.
architecture · buffer sizing
FIFO depth and read-path liveness
Reading one record means fetching 62 words from flash. The read path enqueues all
62 read commands first and only then starts popping the results. With 16-entry FIFOs, the
command queue filled before the transaction could complete, causing a reproducible
deadlock.
Root cause & fix
The deadlock threshold is simply TX depth + RX depth < 62. We deepened both the
TX and RX FIFOs from 16 to 64, enough to hold a full packet of read commands and the
58 plaintext words used by the write path.
Lesson: buffer depth can be a correctness constraint when one side of a protocol must enqueue a full transaction before the other side drains it.
architecture · protocol revision
Record format revision
The first design stored 4-word records with an explicit commit marker to mark a page as
valid. After review, the format changed to a full 256-byte page: 2 words of
associated data, 58 words of ciphertext, a 4-word tag — and no marker at all.
What it touched
- The page payload grew from 4 to 58 words; the parameter package, FIFO depths and Main-FSM buffers all had to scale with it.
- The commit marker, related FSM states, and the
ERR_NO_RECORD error were removed. A record is accepted only if its ASCON tag authenticates.
- Sector handling changed to "erase only on the first page of a 4 KB sector," and testbenches built around the old 4-word protocol were replaced.
Lesson: using authentication as the validity criterion removed extra state and reduced the number of cases the FSM had to handle.
methodology · reset review
Reset-style review across the design
A design review question about reset release led to a systematic audit of reset style
across 98 modules covering BCH, ASCON, the fuzzy extractor, FIFOs, the PUF, and SPI.
What we found
- The dominant style is asynchronous assertion with active-low reset, but some boundaries mix synchronous and asynchronous reset behavior.
- The PUF uses an intentionally free-running seed LFSR with no reset, because boot-to-boot variability is part of the model.
- Some documentation described reset behavior differently from the RTL and needed correction.
The conclusion: an external async reset is fine, but it must be deasserted through a
two-flip-flop synchronizer per clock domain to avoid recovery/metastability hazards —
and intentional exceptions such as the PUF seed, behavioral models, and legacy cores
should be documented explicitly.
Lesson: reset behavior should be treated as an interface contract. Documented exceptions are manageable; undocumented ones are integration risk.
integration · reused RTL
BCH codec reset assumptions
Error correction for the fuzzy extractor builds on an existing open BCH codec — a deep
pipeline of syndrome, Berlekamp-Massey and Chien-search stages. Most of its
internal blocks have no global reset: they restart only on operation-local
start/first strobes. Reset the wrapper mid-decode and the
machinery underneath keeps running for a few cycles.
How we handled it
Rather than rewrite the arithmetic core, we treated it as a start/done accelerator
that may only be reset while idle: the FE wrapper controls the external reset, gates
start until after reset release, and the contract is abort-and-restart, never
pause-and-resume. Deep reset is added only where it is genuinely needed.
Lesson: reused RTL should be integrated with its assumptions made explicit at the wrapper boundary.
tooling · second synthesis toolchain
RTL that Vivado accepted, Design Compiler did not
The design simulates under XSim and maps cleanly to the FPGA through Vivado. The
first time the SEC subsystem (tum_ss) was pushed through standard-cell
ASIC synthesis with Synopsys Design Compiler, several constructs that both
simulation and FPGA synthesis had tolerated were rejected at elaboration.
Two representative cases
- An accidental half-megabit register. A BCH syndrome helper built its
lookup table in a local variable before returning it. The elaborator
materialized that intermediate as a single
~524 288-bit
register and stopped. Rewriting the function to build the table directly in
its return value, with explicit widths, removed the oversized implicit
storage.
- A cross-generate reference. One LFSR term reached into a sibling
generate block's scope — a hierarchical reference that simulators
resolve but the ASIC elaborator does not accept. The term was restructured to be
computed locally, without reaching across generate instances.
Lesson: simulation and FPGA synthesis are more permissive than a sign-off ASIC elaborator. Portable RTL avoids huge implicit intermediates and cross-
generate hierarchical references — the kind of thing only a second toolchain surfaces. The synthesis bring-up status and the silicon budget are tracked on the
Verification page.
tooling · integration namespace
Module-name collision during SoC synthesis
Unit and subsystem simulations passed. The first time the design went through full SoC
synthesis, it failed: a module of ours was simply named counter — and so was a
cell in the SoC's shared component library. The names collided only once both were pulled
into the same elaboration.
Fix
We renamed the local module to bch_counter and re-ran the regression. The
failure was not visible until subsystem sources were elaborated together with the full SoC.
Lesson: a flat Verilog/SystemVerilog namespace is shared across the integrated design. Subsystem-specific prefixes reduce this risk.
field notes · smaller issues
Additional implementation notes
- Subsystem base address. One SoC document listed a base address that differed from the RTL interconnect decode (
0x01051000). Firmware and documentation were aligned with the RTL-visible address.
- FPGA area constraints. Debug-oriented hardware, including the QSPI command trace and performance-counter unit, was made optional behind a synthesis switch.
- Simulator scheduling in tests. Some testbench loops sampled a multi-bit guard before a non-blocking update became visible. Rewriting the checks with explicit loop control removed the false failures.
- Enrollment ordering. Helper data, key hash, counter, and ENROLLED flag are written in an order that lets an interrupted enrollment restart cleanly on the next boot.