UART · Module 11
Loopback, Test Modes and Built-In Self-Check
Internal and line loopback, what each one proves, and — measured — the faults they provably cannot see. The cheapest RTL in the IP and the most decisive during bring-up.
A link that does not work presents as one symptom: nothing arrives. Behind that symptom are at least six candidates — the transmitter, the receiver, the baud generator, the pins, the wiring, and the far end — and a UART with no test modes gives you no way to eliminate any of them.
This chapter builds the features that partition that space. They are the cheapest RTL in the entire IP: two muxes, one extra port. They are also the only thing that turns a bring-up from guessing into bisection.
The discipline that matters most here is knowing what a passing test mode does not prove — so §3 breaks the receive pin and shows internal loopback passing anyway.
1. Two Loopbacks, Facing Opposite Directions
They are routinely confused because both are called "loopback". They test disjoint things.
Internal loopback connects this IP's transmitter to its own receiver, before the pins. It asks: do my engines work?
Line loopback connects the receive pin straight back to the transmit pin. It asks nothing about this IP at all — it lets the far end ask: does the path to that board and back work?
// Both loopbacks, complete. They face opposite directions and are
// independent; setting both is legal and is not a combinational loop,
// because rx_i is an input.
logic tx_line, rx_line;
assign tx_o = cfg_line_loop_i ? rx_i : tx_line;
assign rx_line = cfg_loopback_i ? tx_line : rx_i;That is the entire feature. Two ternaries. Everything else in this chapter is about what they let you conclude.
The transmit pin keeps driving during internal loopback — the mux feeds the receiver a copy of the line, it does not steal it. That is deliberate: the frames stay visible on an oscilloscope, so a self-test can be watched from outside. Measured: internal loopback: tx_o STILL drives the pin (scope-visible) — pass.
2. What Internal Loopback Proves
It exercises, in one measurement, nearly everything Modules 6 through 10 built:
| Covered | Because |
|---|---|
| transmit shift, framing, parity generation | the frame has to be well-formed to decode |
| receive sampling, start detection, parity check | it has to decode that frame |
| the shared timing generator | both halves use the same ticks — Chapter 11.2 §2 |
| both FIFOs and their handshakes | the bytes travel through them |
| the configured frame format end to end | wrong parity shows as a parity error |
The timing generator is the valuable one. A baud rate that is wrong by 10% still round-trips perfectly in internal loopback, because both halves are wrong identically — so loopback does not validate the baud rate. What it validates is that the tick distribution is coherent, which is a different and still useful claim.
3. The Fault Internal Loopback Cannot See
Reasoning about coverage is weaker than measuring it. So: break the receive pin — stuck at 0, a dead pad or a broken trace, entirely outside anything the IP can reach — and run internal loopback anyway.
pass internal loopback: byte returns
pass internal loopback: no errors
pass RX PIN STUCK AT 0 : internal loopback STILL PASSES
-> a dead receive pin is invisible to internal loopbackThe byte round-trips perfectly over a receive pin that is physically dead, because rx_line is sourced from tx_line and never touches the pin. This is the correct behaviour and it is exactly the trap: a passing self-test on a board whose receive path is broken.
Which is precisely why line loopback exists, and why the pair is worth more than either alone:
| internal loopback | line loopback | |
|---|---|---|
| TX engine | ✅ | ❌ |
| RX engine | ✅ | ❌ |
| timing generator | ✅ (coherence only) | ❌ |
| FIFOs | ✅ | ❌ |
| TX pin / pad | ❌ | ✅ |
| RX pin / pad | ❌ | ✅ |
| board wiring, connector, cable | ❌ | ✅ |
| absolute baud rate | ❌ | ❌ |
| far-end configuration | ❌ | ❌ |
The two columns are almost perfectly complementary, which is the reason to build both. Line loopback was measured driving an external frame onto the receive pin with both engines uninvolved: line loopback: tx pin echoes the rx pin — pass.
Neither column covers baud rate or the far end. No test mode can: both are statements about a second device.
4. Reading a Dead Link
With the two modes, bring-up becomes a decision procedure rather than a search:
| Internal | Line | Normal | Conclusion |
|---|---|---|---|
| ✅ | ✅ | ❌ | IP and pins are fine — baud rate or far-end configuration |
| ✅ | ❌ | ❌ | engines fine — pins, pads, or board wiring |
| ❌ | — | ❌ | the IP itself: clock, reset, timing generator, engines |
| ✅ | ✅ | ✅ | working; if data is still wrong, it is format, not plumbing |
Three tests, four conclusions, and each eliminates a region rather than guessing at a cause. The first row is the common one and the reason to check baud first when loopback passes.
5. Test Modes Worth Their Gates, and Ones That Are Not
The two loopbacks are unambiguously worth building. The rest of the usual list is more mixed:
Worth it — a forced break. One bit that holds tx_o low lets you generate a break on demand, which is the only way to test the far end's break handling without unplugging a wire. It costs one OR gate on the transmit output, and Chapter 9.4 §6 specified it.
Worth it — exposing cfg_pending_q. One status bit, and it converts "my configuration write did nothing" into "the change is queued and the link has not gone quiet" — Chapter 11.3 §3.
Worth it — a counter on received bytes and on errors. Cheap, and it turns "it is dropping data sometimes" into a rate.
Usually not worth it — an RTL pattern generator. A walking-ones self-test in hardware duplicates what software can do with a loop over the existing write port, and it must be maintained in RTL where changing it costs a re-synthesis. Build the loopback; let software supply the patterns.
Not worth it — a test mode that changes the datapath. Any mode that alters how data is processed rather than how it is routed creates a configuration in which the IP has never shipped. The two loopbacks here only re-route: every engine, FIFO and status path behaves identically in loopback and in normal operation, which is what makes a loopback result meaningful about normal operation.
6. Verification
Loopback is so useful as a test tool that it is easy to forget it is also logic that must be tested. Four properties matter:
// Assertion — internal loopback must not disturb the transmit pin. The
// receiver takes a COPY; stealing the line would make self-test invisible
// to a scope and would silence the link the moment the mode is entered.
property p_loopback_keeps_tx_pin;
@(posedge clk) disable iff (!rst_n)
(cfg_loopback_i && !cfg_line_loop_i) |-> (tx_o == tx_line);
endproperty
assert property (p_loopback_keeps_tx_pin);
// Assertion — line loopback echoes the pin combinationally, whatever the
// engines are doing.
property p_line_loop_echoes;
@(posedge clk) disable iff (!rst_n)
cfg_line_loop_i |-> (tx_o == rx_i);
endproperty
assert property (p_line_loop_echoes);
// Assertion — with both modes off, neither path is engaged.
property p_no_mode_is_transparent;
@(posedge clk) disable iff (!rst_n)
(!cfg_loopback_i && !cfg_line_loop_i) |-> (tx_o == tx_line && rx_line == rx_i);
endproperty
assert property (p_no_mode_is_transparent);The third is the one that protects production. It states that the test features are absent when not selected, which is the property every test mode must have and the one that a mux with a wrong polarity would break — in a way that only shows up on hardware, never in the loopback tests that were used to develop the feature.
Test that leaving a mode restores normal operation, not only that entering it works. Measured: line loop off: tx_o driven by the transmitter again — pass. A one-directional test passes on a mux that latches.
All three held for the entire run. And they were mutation-checked, because an assertion that has never failed is an assertion that might not be connected to anything: rewiring internal loopback to steal the transmit pin rather than copy it produced 8,680 assertion failures against zero on the real RTL.
The full result for this chapter's suite, including the broken-pin injection:
== 13 checks, 0 failures ==Routing, and what each mode can see
6 cycles7. What This Means on an FPGA
Two 2:1 muxes on pin paths. One extra level of logic between the transmit register and the pad. On any FPGA this is noise against the pad delay itself, and it is worth knowing the level is there when reading a timing report for a fast link.
Bring up with internal loopback before the cable exists. It needs no peer, no external equipment and no correct baud rate — which means it can run on the first day the board powers on, and a pass eliminates the entire IP from the search.
Then check the bit period on a scope. Loopback cannot do it (§2), and it is the most common remaining fault. One measurement on tx_o, compared against Chapter 4.3's table.
Put line loopback behind a documented register bit and tell the far-end team it exists. Its whole value is to somebody debugging from the other end of the cable, and an undocumented test feature helps nobody.
Print the mode bits in the driver's diagnostic output. The failure mode of loopback is being left on, and it is indistinguishable from a broken link until somebody reads the register.
8. Understanding Check
9. Summary
Two ternaries and one extra port buy the ability to partition a dead link instead of guessing at it.
Internal loopback and line loopback face opposite directions and cover almost disjoint regions — engines and timing versus pins and board — which is why the pair is worth far more than either alone.
Neither can check the baud rate, because both halves share one generator and a wrong divisor cancels out. Loopback passing while the link is dead is the most common bring-up result, and it points at baud rate precisely because everything else has been eliminated.
A dead receive pin is invisible to internal loopback — measured, with the pin forced stuck at zero and the byte still returning intact. That is correct behaviour and exactly why the second mode exists.
A stuck-low pin is loud and a stuck-high pin is silent: one garbage byte, a framing error and a standing break, versus a trace indistinguishable from a healthy idle link.
Test modes must only re-route, never re-process, or a loopback result says nothing about normal operation. And the assertion that matters most is that with every mode off the datapath is transparent — the one property nothing else exercises, and the one whose failure ships.
10. What Comes Next
The IP is complete. Eleven blocks assembled, configuration plumbed and deferred, status aggregated, parameters checked at elaboration, and test modes to bring it up — verified by 53 integration checks, three parameter sets and a broken pin.
Everything remaining is about the world it has to live in. Module 12 takes the clock-domain question seriously: this IP assumes one clock, and a real SoC will not offer that — which brings genuine asynchronous FIFOs, reset methodology, timing constraints and synthesis signoff. Module 13 attaches it to a bus, and the register map, interrupts and DMA hooks this module deliberately kept out become the subject.
Browse the full path on the UART tutorials index. For the break behaviour a stuck-low pin triggers, read back to Chapter 9.4.
Continue learning
Related tutorials
- Related topic
The UART Link: TX, RX, Idle and Full Duplex
A UART link is two independent one-way conductors, not one bidirectional bus — which removes arbitration, turnaround and direction control from the design, makes the naming endpoint-relative, and means full duplex guarantees simultaneity and nothing else.
- Related topic
Top-Level UART IP Architecture
Ten independently verified blocks and the interfaces that join them. The top level adds no new function; what it adds is fan-out, configuration distribution, status aggregation and the decisions that only exist once blocks meet.
- Related topic
Integrating TX, RX, Baud Generation and FIFOs
The details that only appear at the top level: one generator feeding both halves, a flush that must not corrupt a frame in flight, and several distinct loss points that must feed one honest overrun flag.
- Related topic
Configuration, Control and Status Plumbing
Routing configuration to both halves and status back, with defined behaviour when a change arrives mid-traffic — because a format applied to one half and not the other is worse than not applying it at all.
Where this fits
Part of the UART curriculum.
