Skip to content
VLSI Mentor

UART · Module 11

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.

Chapter 11.2 treated configuration as a signal that arrives and is used. This chapter asks what happens when it changes, which is where an IP differs from a block in a way that has caught many designs.

Chapter 7.5 §3 already solved a version of this: the transmitter captures the parity mode at acceptance so a mid-frame reconfiguration cannot reformat the frame it is emitting. That was correct and it is not sufficient at the top level, for a reason that only exists once both halves share one configuration:

A parity change that reaches the transmitter and the receiver at different moments makes one IP disagree with itself — a frame is sent under one format and decoded under another, on the same device.

That failure is not detectable by either half. The transmitter emitted a well-formed frame; the receiver decoded a well-formed frame; they simply were not the same frame. §3 builds the deferral that prevents it.

1. What Is Configuration and What Is Control

Two kinds of input arrive from above, and conflating them produces an IP that is hard to drive:

ConfigurationControl
Shapea level, helda one-cycle pulse
Examplescfg_parity_i, cfg_hw_flow_en_i, cfg_loopback_icfg_tx_flush_i, cfg_rx_flush_i, cfg_err_clear_i
Questionhow should the IP behave?do this now
Persistsyes — it is the current settingno — it is an action
Safe to re-applyyes, idempotentno — each pulse is another action

Control inputs must be pulses, not levels. A flush held high keeps its FIFO in reset, which presents as a queue that is permanently empty while the write port stays ready — an easy defect to create in a register decode and a confusing one to read. Module 13 will generate these from register writes, and the one-cycle shape is part of the contract this IP publishes.

2. Distribution: Both Halves, One Value

The parity mode reaches the transmitter and the receiver. The naive distribution wires the input straight to both:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG at the top level — each half sees the change at its own moment.
uart_tx u_tx (... .parity_mode_i(cfg_parity_i) ...);
uart_rx u_rx (... .parity_mode_i(cfg_parity_i) ...);

This looks safe because Chapter 7.5 established that the transmitter captures at acceptance and Chapter 6.4 that the receiver's frame shape is fixed at its start. Both are true, and they fix different frames at different moments.

A block diagram of the configuration and status paths inside the UART IP. On the left, configuration levels and control pulses enter the top level. Configuration levels feed a shadow and commit stage, which also receives a link-quiet condition derived from the transmit engine, the transmit queue and the receive state machine. The commit stage drives one active configuration value to both the transmitter half and the receiver half simultaneously. Control pulses go directly to the queues and the error aggregator without passing through the commit stage. On the right, status returns in three groups: per-byte status travelling with each received byte, live conditions from the queues and engines, and sticky error flags from the aggregator.cfg_* levelsparity, flow enables,loopbackcfg_*_i pulsesflush, err clear — 1 cycleshadow + commithold until link_quietlink_quiet!tx_busy & tx_empty & rxidleTX halfengine + queue + gateRX halfengine + queue + qualifiersticky aggregatorframe, parity, overrun,breakstatus outper-byte · live · stickyrequestedactivesame edgecommit enablebusystateclearflushliveper-bytesticky12
Figure 1 — the configuration and status plumbing. Configuration enters on the left, is held in a shadow register and commits to BOTH halves on one edge when the link is quiet. Control pulses bypass the deferral entirely, because an action is not a format. Status returns on the right in three groups with three different lifetimes.

3. Commit on Quiet

The fix is to hold the change at the top level until neither half is mid-anything, then apply it to both on one edge:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Synthesizable SystemVerilog — configuration shadow and commit.
// A change is recorded immediately and applied only when the link is quiet,
// so both halves switch format on the SAME edge.
parity_mode_t cfg_parity_q;          // what both halves actually see
parity_mode_t cfg_parity_shadow_q;   // what software asked for
logic         cfg_pending_q;

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cfg_parity_q        <= PARITY_NONE;
        cfg_parity_shadow_q <= PARITY_NONE;
        cfg_pending_q       <= 1'b0;
    end else begin
        if (cfg_parity_i != cfg_parity_shadow_q) begin
            cfg_parity_shadow_q <= cfg_parity_i;
            cfg_pending_q       <= 1'b1;
        end
        if (cfg_pending_q && link_quiet) begin
            cfg_parity_q  <= cfg_parity_shadow_q;
            cfg_pending_q <= 1'b0;
        end
    end
end

link_quiet is stricter than busy_o, and the difference matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Nothing transmitting, nothing queued to transmit, no frame being received.
assign link_quiet = !tx_busy && tx_fifo_empty && (rx_state == S_IDLE);

It excludes receive FIFO occupancy, for the same reason Chapter 11.1 §6 excluded it from busy_o: unread bytes already have their format — they were decoded under the old configuration and their status travels with them — so waiting for software to read them would mean a configuration change could be blocked indefinitely by a consumer that is simply slow.

It includes the TX FIFO, because a queued byte has not yet been launched and would be sent under whichever format is active when it is. Letting a change commit with bytes still queued would send them under the new format when the producer wrote them expecting the old one.

Measured behaviour

From the integration suite, with a change requested while three frames were in flight:

Momentcfg_parity_qcfg_pending_qObservation
quiet, change requestedapplied immediately0no traffic to disturb
3 bytes queued, change requestedPARITY_NONE1deferred
in-flight bytes completingPARITY_NONE1all 3 decoded under the old format
link quiet againPARITY_EVEN0committed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mid-traffic: link not quiet                        pass
config change DEFERRED while busy                  pass
in-flight bytes complete under OLD config (3/3)    pass
config commits once quiet                          pass

Change requested during traffic, committed when quiet

8 cycles
A trace of eight frame-level events showing a configuration change being deferred. Initially the active parity configuration is none and no change is pending. Three frames are queued and begin transmitting. Software then requests a change to even parity: the pending flag asserts immediately but the active configuration does not move, because the link is not quiet. The three queued frames are transmitted and received under the original no-parity format. Once the transmit queue is empty, nothing is being transmitted and no frame is being received, the link-quiet condition asserts and the change commits: the active configuration becomes even parity and the pending flag clears, with both the transmitter and receiver switching on the same clock edge.deferred — active value frozendeferred — active value frozenchange requested — heldchange requested — heldin-flight frames use OLD formatin-flight frames use OLDformatquiet — both halves switch togetherquiet — both halves switchtogethereventidle3 queuedframe 1REQ EVENframe 2frame 3quietnextcfg_parity_qNONENONENONENONENONENONEEVENEVENcfg_pending_qlink_quiett0t1t2t3t4t5t6t7
Figure 2 — a configuration change arriving mid-traffic. Columns are FRAME EVENTS, not clock cycles. The request is recorded the moment it arrives and the active value does not move; the three queued frames are sent and received under the original format; the commit happens on the first event where the link is quiet, and both halves switch together.

The policy is a choice, and alternatives are defensible. Apply immediately is correct for an IP whose software always quiesces the link first — and it makes that quiescing the software's responsibility, unenforced. Apply on the next frame boundary is finer-grained and lets the two halves disagree for one frame, which is the failure §2 describes. Committing on quiet is the conservative choice, and its cost is that a change on a permanently busy link never commits — which a status bit exposing cfg_pending_q makes visible rather than mysterious.

4. What a Disabled Feature Drives

An integration-only question with a wrong answer that looks tidy.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Synthesizable SystemVerilog — a disabled feature drives the PERMISSIVE
// value, not its inactive electrical level.
assign rts_n_o = cfg_hw_flow_en_i ? rts_n_int : 1'b0;   // 0 = "may send"

uart_tx_gate u_gate (
    .cts_n_i(cfg_hw_flow_en_i ? cts_n_i : 1'b0),        // 0 = permitted
    ...);

Disabled hardware flow control must not look like backpressure. Driving rts_n_o to its inactive level when the feature is off would tell the far end to stop on a link that never intended to use flow control at all — a link that would then appear dead for no reason anyone could see in either device's configuration.

Equally, a disabled cts_n_i must read as permitted. Chapter 10.5 §7 made the gate's synchroniser reset to not permitted, which is right for a link that uses flow control and wrong for one that does not. Forcing the permissive value when the feature is disabled resolves it, and the two lines above are the whole mechanism.

The general rule: a disabled feature drives the value that makes the rest of the design behave as though the feature did not exist. That is frequently not the same as the signal's idle or reset level, and the difference is where integration defects live.

The same reasoning applies to cfg_sw_flow_en_i: with software flow control disabled the detector is inert, 0x13 is payload, and tx_paused_o stays low — which Chapter 10.6 §2 built as the transparency escape and which the top level simply must not defeat.

5. Status, From the Consumer's Side

The IP exposes eleven status outputs, and they fall into three groups with different lifetimes — the distinction Chapter 9.6 §1 established, now visible at the boundary:

GroupSignalsLifetimeCleared by
per-byterd_parity_err_o, rd_frame_err_ovalid with rd_valid_opopping the byte
live conditiontx_empty_o, rx_empty_o, busy_o, tx_trigger_o, rx_trigger_o, break_active_ocontinuousthe condition ending
sticky historyerr_frame_o, err_parity_o, err_overrun_o, err_break_o, any_error_ountil clearedcfg_err_clear_i

All three are needed and none substitutes for another. Per-byte status answers was this byte good; live conditions answer what is happening now; sticky flags answer has anything gone wrong since I looked. A design exposing only the third cannot attribute an error to a byte; one exposing only the first forces software to catch every byte.

break_active_o is deliberately in the middle group and has a sticky counterpart in the third. Chapter 9.4 §3 argued both are needed: is the link broken right now and was it ever are different questions, and a UART IP that merges them cannot answer the first.

Module 13 maps these to registers, and the mapping is not one-to-one — a status register typically packs the sticky group into one word with write-one-to-clear, exposes the live group as read-only, and delivers per-byte status alongside the data register. None of that is this IP's concern, and keeping it out is what lets the same IP sit behind different bus protocols.

6. Verification

The mid-traffic test is the one that matters, and it must check three things rather than one: that the change is deferred, that in-flight bytes complete under the old format, and that it commits afterwards. Checking only the last would pass on an implementation that applied the change immediately and corrupted the frames.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — the active configuration never changes while the link is busy.
// This is §3's policy stated directly; it fails on a straight-through wiring.
property p_config_stable_while_busy;
    @(posedge clk) disable iff (!rst_n)
        !link_quiet |=> $stable(cfg_parity_q);
endproperty
assert property (p_config_stable_while_busy);

// Assertion — a pending change eventually commits, provided the link goes
// quiet. States the liveness half, which the stability property does not.
property p_pending_commits_when_quiet;
    @(posedge clk) disable iff (!rst_n)
        (cfg_pending_q && link_quiet) |=> !cfg_pending_q;
endproperty
assert property (p_pending_commits_when_quiet);

// Assertion — both halves always see the SAME value. Trivially true by
// construction here, and worth stating because the defect §2 describes is
// exactly a violation of it.
property p_halves_agree;
    @(posedge clk) disable iff (!rst_n)
        u_tx.parity_mode_i == u_rx.parity_mode_i;
endproperty
assert property (p_halves_agree);

// Assertion — disabled hardware flow control never requests a stop.
property p_disabled_fc_is_permissive;
    @(posedge clk) disable iff (!rst_n)
        !cfg_hw_flow_en_i |-> !rts_n_o;
endproperty
assert property (p_disabled_fc_is_permissive);

The third is the one to keep. It is true by construction in this implementation and it is precisely what a "simplification" back to straight-through wiring would break — and it would break silently, since both halves would still be individually well-behaved.

Test each disabled feature's drive value. Three features, three checks, each asserting the permissive value rather than the inactive one. From the suite: hw flow disabled -> rts permitted passes at reset, before anything else runs.

7. Debugging

8. What This Means on an FPGA

The plumbing is a shadow register, a pending flag and a comparator. Three flip-flops plus the mux terms for the disabled features. It is the cheapest thing in the IP and it prevents a class of defect that is expensive to find.

Expose cfg_pending_q. One bit, and it turns "my configuration write did nothing" into "the change is queued and the link has not been quiet". Module 13 should carry it into the status register.

Keep the control inputs one cycle. A register write naturally produces a pulse; a register bit that software sets and clears does not. The IP's contract says pulse, and an adapter that gets it wrong produces a permanently-held flush that looks like a dead queue.

The disabled-feature mux is on a pin path. rts_n_o passes through one mux before the pad — negligible, but worth knowing it is there when reading a timing report.

9. Understanding Check

10. Summary

Configuration is a held level; control is a one-cycle pulse. Conflating them produces a flush that holds its FIFO in reset and reads as a dead queue.

Per-block capture is not enough once blocks share a configuration. The transmitter freezes format at acceptance and the receiver at the start edge; a change between those instants makes one IP disagree with itself, sending under one format and decoding under another with neither half able to detect it.

The fix is shadow-and-commit on quiet. A change is recorded immediately and applied only when nothing is transmitting, nothing is queued to transmit and no frame is being received — so both halves switch on the same edge. Verified with three frames in flight: deferred, all three completed under the old format, committed afterwards.

link_quiet excludes receive occupancy and includes transmit occupancy, because the first is finished business and the second has not yet chosen its format.

A disabled feature drives its permissive value, not its inactive level. Disabled hardware flow control that drives stop produces a dead link with nothing in either configuration to explain it.

Status falls into three groups with three lifetimes — per-byte, live condition and sticky history — and none substitutes for another. Mapping them to registers is Module 13's, and keeping that out is what lets one IP sit behind different buses.

And the assertion worth keeping is that both halves always see the same value — true by construction, and precisely what a plausible simplification would break silently.

11. What Comes Next

The IP now works and is correctly plumbed. Chapter 11.4 asks a different question: will it survive being used again?

That means deciding what belongs to a parameter and what belongs to a register — a distinction this module has applied without stating — and organising the RTL so a second project can adopt it without editing it. It is the least glamorous chapter in the module and the one that determines whether the previous ten were worth the effort.

Browse the full path on the UART tutorials index. For the per-frame capture this chapter builds on, read back to Chapter 7.5.

Continue learning

Where this fits

Part of the UART curriculum.