UART · Module 15
Baud Mismatch, Reset-During-Traffic and Boundary Stress
The three stresses that find real UART bugs, measured: the recovery window against its arithmetic prediction, a mid-frame reset that delivers a wrong byte with no error anywhere, and the FIFO boundary that hid a defect through an entire suite.
The faults of Chapter 15.4 are single-frame events. These three are not: they are conditions that persist, or that straddle a boundary, and each of them found something.
One confirms a prediction to within a fraction of a percent. One produces a silently wrong byte with no error reported anywhere in the system. One hid a genuine defect through sixty-two passing checks.
1. Baud Mismatch: a Prediction and a Measurement
The two ends of a UART link never agree exactly. Each has its own oscillator, and the question is how far apart they may drift before the link stops working.
The arithmetic is short. A receiver samples the last data bit 8.5 bit periods after the start edge, so accumulated error must stay under half a bit:
0.5 / 8.5 = 5.88%
And the measurement, sweeping the driver's rate from −10% to +10% while the receiver's stays nominal:
[info] recovery window: -6% to 6% (13 of 21 offsets OK)
PASS the sweep found BOTH working and failing rates -- a real edge exists
PASS recovery survives at least +/-4% of baud error
PASS and fails beyond +/-7%, as 0.5/8.5 = 5.9% predicts±6% against a prediction of ±5.88%, and the same window falls out of the Verilog, SystemVerilog and VHDL implementations independently.
2. Reset During Traffic: the One That Reports Nothing
Assert reset on the transmitter part-way through a frame. Three observers, three answers.
The property checker of Chapter 15.2 reports nothing:
after one clean frame : fail=0 mask=00000000
mid-frame : busy=1 line=1
during reset : busy=0 line=1
after release : fail=0 mask=00000000 line=1
clean frame after : fail=0 mask=00000000 frames=1T8 — busy does not drop mid-frame — is exactly the property this should violate, and it does not fire. The checker shares the DUT's reset. When reset asserts, the checker is reset too: in_frame goes false, the tick counter clears, and the fact that a frame was in progress is erased from the only thing that was watching.
And the far end sees a perfectly well-formed frame:
far end saw 1 frame(s)
frame 0 : data=0x0fd parity_err=0 framing_err=0
(the byte actually requested was 0xA5)No parity error. No framing error. One frame, correctly structured, carrying 0xFD where 0xA5 was requested.
The arithmetic is exact. 0xA5 is 1010_0101, sent least significant first: 1, 0, 1, 0, 0, 1, 0, 1. The reset landed after three data bits had gone out, and reset forces the line to MARK — so the remaining five bits are read as ones:
1, 0, 1then1, 1, 1, 1, 1=1011_1111=0xFD
The stop interval is MARK, which is legal. The frame is well-formed. Nothing in the protocol distinguishes it from a deliberate transmission of 0xFD.
This is Chapter 12.4 §4's finding — a well-formed frame with the wrong byte — reproduced from the verification side, with the byte value derived rather than observed.
The only thing that detects it is a scoreboard, comparing the byte requested against the byte received. No status bit, no assertion, and no amount of error injection.
3. FIFO Boundaries: the One That Hid a Defect
Chapter 15.3 §6 records this in full; it belongs here as a boundary.
The FIFO boundaries worth targeting are not "empty" and "full" — those are reached constantly. They are the simultaneous cases:
| Boundary | Why it is a corner |
|---|---|
| push into an empty FIFO with a pop in the same cycle | the pop must not consume the byte being pushed |
| push into a full FIFO with a pop in the same cycle | the two candidate acceptance rules disagree here and nowhere else |
| pop from a FIFO at level 1 with a push in the same cycle | the level passes through zero and back |
| the level at DEPTH−1 | the last cycle before full asserts |
The second row is the one that mattered. A mutant carrying the wrong acceptance rule survived sixty-two passing checks across three languages and 4,079 clocks of random push/pop traffic, because:
clocks=4079 push+pop while FULL occurred 0 timesNot rarely. Zero. push and pop were independent random bits, the FIFO spends very little of a random walk at exactly full, and the joint condition never arose.
Five lines of directed stimulus killed it:
for (i = 0; i < DEPTH; i = i + 1) @(negedge clk) push = 1'b1;
@(negedge clk) push = 1'b0;
repeat (2) @(negedge clk);
@(negedge clk) push = 1'b1; pop = 1'b1; // both, while full
@(negedge clk) push = 1'b0; pop = 1'b0;Randomisation has no reason to prefer a corner. It explores the space in proportion to how large each region is, and a corner is by definition the smallest region there is. What tells you a corner was missed is a coverage bin; what reaches it is a directed test.
4. Module 15 Verification Evidence
Every listing published in this module was extracted from the page, compiled with a real tool, and simulated.
Three components, three languages, nine implementations:
| Component | SystemVerilog | Verilog-2001 | VHDL-2008 + PSL | Chapter |
|---|---|---|---|---|
uart_fifo_assert | ✅ | ✅ | ✅ | 15.2 |
uart_tx_assert | ✅ | ✅ | ✅ | 15.2 |
uart_cov | ✅ | ✅ | ✅ (package) | 15.3 |
The checkers are bound to designs this curriculum published earlier and did not modify: the transmitter of Chapter 7.1 and the FIFO of Chapter 10.2.
Nine testbenches, identical check counts across all three languages:
| Suite | Checks | Verilog-2001 | SystemVerilog | VHDL-2008 |
|---|---|---|---|---|
uart_fifo_assert | 20 | 20 / 0 | 20 / 0 | 20 / 0 |
uart_tx_assert | 18 | 18 / 0 | 18 / 0 | 18 / 0 |
uart_cov | 24 | 24 / 0 | 24 / 0 | 24 / 0 |
| Total per language | 62 | 62 / 0 | 62 / 0 | 62 / 0 |
186 checks across the three languages, 0 failures. Tooling: Icarus Verilog 13.0 (-g2001, -g2012) and NVC 1.23.0 with --psl.
Mutation campaign — twelve defects, twelve killed:
| # | Component | Defect installed | Result |
|---|---|---|---|
| M1 | fifo | P4 compares against the current cycle's push/pop | killed, 7 |
| M2 | fifo | P2 made one-directional | killed, 1 |
| M3 | fifo | P7 deleted | killed, 1 |
| M4 | fifo | acceptance rule simplified to push && !full | survived, then killed |
| M5 | fifo | P8 deleted | killed, 1 |
| M6 | tx | T1 (idle line is MARK) deleted | killed, 1 |
| M7 | tx | T6 checks the start bit one tick early | killed, 3 |
| M8 | tx | T2 reverted to ready-and-busy-exclusive | killed, 3 |
| M9 | tx | T5 frame-length bound made inclusive | killed, 2 |
| M10 | cov | the cross bin never incremented | killed, 3 |
| M11 | cov | hole counting skips the cross | killed, 2 |
| M12 | cov | the DEPTH−1 level bin folded into the middle | killed, 2 |
M4 is the module's result. It survived the entire suite, the coverage instrumentation explained why in one number, five lines of directed stimulus killed it, and the campaign closed at twelve of twelve.
5. Verification
Assert on both sides of a boundary. The baud sweep requires a working region and a failing one; without the second, the test passes on a receiver that measures nothing.
Compare a measurement against its arithmetic. ±6% against 5.88% is evidence that the design and the theory describe the same thing. A measurement with no prediction to check it against is a number.
Put at least one observer outside the reset domain. A checker reset with the design cannot see anything about a reset event, by construction.
Target simultaneous boundaries, not single ones. Empty and full are reached constantly; push-and-pop-at-full was reached zero times in 4,079 clocks.
Use coverage to find the corner and a directed test to reach it. Randomisation explores in proportion to region size, and a corner is the smallest region there is.
And do not expect a stress test to raise an error flag. Two of this chapter's three stresses produce no error status anywhere — one because the checker is blind to it, one because the resulting frame is genuinely well-formed.
6. Debugging
7. Understanding Check
8. Summary
Baud tolerance measured at ±6% against an arithmetic prediction of 5.88%, consistently across three independent implementations — and asserted on both sides of the edge, because a one-sided check passes on a receiver that measures nothing.
A mid-frame reset produces no error anywhere in the system. The property checker is blind to it because it shares the DUT's reset; the far end receives one well-formed frame carrying 0xFD instead of 0xA5, and nothing in the protocol distinguishes that from a deliberate transmission.
Only a scoreboard detects it — the same conclusion Chapter 15.4 reached about a truncated stop bit.
The FIFO boundary worth targeting is the simultaneous one, and it hid a real defect through 62 passing checks and 4,079 clocks of random traffic by occurring exactly zero times.
Twelve mutants, twelve killed, with the twelfth requiring a coverage observation rather than a better checker.
And six property bugs, none of them in a design: four about timing, one about the specification, one about folklore. A property file is a program, and the most expensive way it fails is by being confidently wrong about a correct design.
9. What Comes Next
Module 15 is complete, and with it the evidence layer. The UART now has properties that hold continuously, coverage that says what was exercised, and injected failures that prove detection works.
Module 16 rebuilds the whole environment in UVM: the agent, sequencer, driver and monitor as classes; sequences as objects that can be composed and randomised; the factory and configuration database that let one environment serve many tests; and the analysis ports that connect a scoreboard without wiring. The roles do not change — Modules 14 and 15 defined them — and what changes is that the properties and coverage written here become components that travel with the agent rather than files bound to one instance.
Browse the full path on the UART tutorials index. For the coverage observation that closed this module's last mutant, read back to Chapter 15.3.
Continue learning
Related tutorials
- Related topic
TX and RX FIFO Architecture
A UART FIFO is an asynchronous FIFO only when its two sides genuinely sit in different clock domains — which, for a receiver whose input was already synchronised, is usually not the case.
- Related topic
Sampling Centres and the Timing Margin Budget
Half a bit period separates an interval's centre from its boundary. That half-bit is a budget spent by origin uncertainty, interval construction, accumulated drift and the decision mechanism — and the last interval of a frame is where it runs out first.
- Related topic
Parity Generation, Checking and Error Detection
One interval, one XOR reduction, and a detection guarantee with a sharp edge: parity catches every corruption that flips an odd number of protected bits and provably misses every even-numbered one — demonstrated, not asserted.
- Related topic
Stop Bits, Frame Boundaries and Back-to-Back Frames
The stop interval is a required mark condition the receiver checks at a known position — not a pause, not recovery time, and not a resynchronisation. What a framing error reports, what it cannot tell you, and why two frames may follow with no idle between them.
Where this fits
Part of the UART curriculum.
