Skip to content
VLSI Mentor

UART · Module 10

XON/XOFF Software Flow Control and Its Limits

Sending the stop request inside the data stream removes the pin requirement and creates a problem hardware flow control does not have: the control values are ones arbitrary binary payload may legitimately contain.

Chapter 10.5 reached the far end with a dedicated wire. Plenty of links do not have one — a three-wire connection, a bridge exposing only data lines, a connector chosen before flow control was considered.

The alternative is to send the request inside the byte stream that already exists. Two reserved values, one meaning stop and one meaning resume, travelling as ordinary characters.

That removes the pin requirement entirely, and it introduces a problem hardware flow control does not have:

The control values are byte values, and arbitrary binary payload may legitimately contain them.

A mechanism that steals two values out of 256 is transparent only if something above the UART guarantees those values will never appear as data. §4 quantifies how often that guarantee is violated in practice, and it is more often than the framing suggests.

1. The Mechanism

Two ASCII device-control characters, conventionally:

ValueNameMeaning
0x13DC3, XOFFstop sending
0x11DC1, XONresume sending

Neither is defined by UART. Chapter 3.5 established that a UART frame carries an opaque payload; nothing in the framing distinguishes a control character from data. The assignment above is an agreement between the two endpoints, and a link where only one end has been told will behave in a way that looks like corruption.

The exchange itself is simple:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
receiver's buffer fills past its threshold
    -> it TRANSMITS 0x13 on its own transmit line
    -> the far end's software or driver recognises it
    -> the far end stops sending payload

receiver's buffer drains past its resume threshold
    -> it TRANSMITS 0x11
    -> the far end resumes

Note where the request travels: on the receiver's transmit line. Software flow control therefore requires the link to be bidirectional and requires the local transmitter to be available — a point §5 returns to, because it interacts badly with a transmitter that is itself paused.

2. The Detector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Synthesizable SystemVerilog — the receive-side detector. It watches
// completed characters and pauses the local transmitter.

module uart_xon_xoff #(
    parameter logic [7:0] XON  = 8'h11,   // DC1
    parameter logic [7:0] XOFF = 8'h13    // DC3
) (
    input  logic       clk,
    input  logic       rst_n,
    input  logic       rx_char_valid_i,
    input  logic [7:0] rx_char_data_i,
    input  logic       sw_flow_en_i,      // only interpret when ENABLED
    output logic       tx_paused_o,
    output logic       consume_char_o     // this byte was control, not payload
);
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx_paused_o    <= 1'b0;
            consume_char_o <= 1'b0;
        end else begin
            consume_char_o <= 1'b0;
            if (sw_flow_en_i && rx_char_valid_i) begin
                if (rx_char_data_i == XOFF) begin
                    tx_paused_o    <= 1'b1;
                    consume_char_o <= 1'b1;
                end else if (rx_char_data_i == XON) begin
                    tx_paused_o    <= 1'b0;
                    consume_char_o <= 1'b1;
                end
            end
        end
    end
endmodule

Three details carry the chapter's argument.

sw_flow_en_i gates everything. With software flow control disabled the detector is inert and 0x13 is an ordinary byte. That input is not a convenience — it is the mechanism by which a system carrying binary data avoids §4's problem entirely, and it is why the enable belongs in hardware rather than being assumed always-on.

consume_char_o is separate from tx_paused_o. The first says this byte was control, do not deliver it as payload; the second says stop transmitting. They are different facts and a design that merges them cannot express the case where a control character is recognised but the consumer still wants to see it — which is exactly what a protocol analyser or a transparent bridge needs.

The pause output gates the transmitter's launch, and it composes with Chapter 10.5's gate rather than replacing it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Conceptual SystemVerilog — the two mechanisms combine as an AND of
// permissions. Either can withhold; neither overrides the other.
assign launch_allowed = !cts_sync_q          // hardware, Chapter 10.5
                     && !tx_paused_q         // software, this chapter
                     && !tx_fifo_empty;

And the same rule as Chapter 10.5 §7 applies: this gates the launch, not a frame in progress. Receiving XOFF halfway through transmitting a character does not truncate it — freezing the line mid-frame corrupts the byte rather than pausing it, for the reason that chapter derived.

3. The Latency Is Worse, and for an Extra Reason

Chapter 10.5 §3 built the in-flight budget. Every term in it still applies, and in-band control adds one more before any of them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
HARDWARE:  decide -> drive pin -> far end recognises -> in-flight bytes arrive
SOFTWARE:  decide -> QUEUE the control byte -> TRANSMIT it (a full frame time)
                  -> far end receives and recognises -> in-flight bytes arrive
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                     this stage does not exist for a pin

The control byte must itself be serialised, which costs a full frame time at minimum — and more if the local transmit FIFO already holds payload:

baudT_frameXOFF sent immediatelyqueued behind 1 bytebehind 4behind 16
9,6001041.67 µs1041.7 µs2083.3 µs5208.3 µs17708.3 µs
115,20086.81 µs86.8 µs173.6 µs434.0 µs1475.7 µs
1,000,00010.00 µs10.0 µs20.0 µs50.0 µs170.0 µs

4. The Transparency Problem, Quantified

0x11 and 0x13 are two of 256 possible byte values, and payload that is genuinely binary will contain them.

The probability is not small. For uniformly distributed bytes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
P(a given byte is a control value)  =  2/256  =  0.781 %
bytes transmittedP(at least one collision)
10054.36 %
1,00099.96 %
10,000100.00 %

A hundred bytes of binary data is more likely than not to contain a control value. A firmware image, a sensor record, a compressed frame, a struct with a length field — any of these will trip it within the first packet.

What happens when it does depends on which value collides:

A payload 0x13 is consumed as XOFF: the byte disappears from the data stream and the far end's transmitter stops. The receiver sees corrupted data and the link appears to hang.

A payload 0x11 is consumed as XON: the byte disappears and a pause that was genuinely requested is lifted, so the buffer overflows.

Both are silent. Nothing in the UART layer reports a control character being consumed, because from its point of view nothing went wrong.

A sequence showing in-band software flow control. The local receive FIFO's occupancy rises past its stop threshold and the flow control logic decides to request a pause. Because the request travels inside the data stream, the control character must first be queued on the local transmit path and then serialised, which takes at least one full frame time and longer if payload is already queued ahead of it. The character then propagates to the far end, where the far end's receiver assembles it and its software or driver recognises it as a stop request and ceases transmitting payload. Meanwhile the characters the far end had already committed continue to arrive, exactly as they do with hardware flow control. When the local buffer drains, a resume character is queued and serialised in the same way and the far end restarts.RX FIFOLocal TXFar endFar SWlevel >= STOP: queueXOFF (0x13)SERIALISE — >= 1frame time, more ifqueued0x13 on the wireassembled,recognised ascontrolstop launchingpayloadin-flight bytesstill arrive — Ch10.5 §3level <= RESUME:queue XON (0x11)
Figure 1 — the in-band round trip, and where it differs. Time runs downward. Every stage the hardware form has is present, plus one that has no counterpart: the control character must be serialised on the local transmit line before the far end can begin to react, and if the transmit queue is not empty it waits behind whatever is already there.

5. Two Failure Modes With No Hardware Counterpart

A lost control character deadlocks the link. XOFF is an ordinary frame and can be destroyed by a framing error, a parity error or an overrun on the far end's receiver. If XOFF is lost, the far end never stops and the buffer overflows. If XON is lost, the far end never resumes — and the link is silent indefinitely, with no error reported anywhere, because from the UART's point of view nothing failed.

A pin has no equivalent failure. rts_n_o is a level, continuously asserted; there is no event to miss. That difference is structural: in-band control is edge-like and stateful, out-of-band control is level-like and stateless, and the recovery properties follow directly. A robust software-flow-control implementation usually needs a timeout that re-sends XON, which is a policy decision belonging to the driver.

Deadlock through mutual pause is possible. If both ends pause each other, neither can transmit the XON that would release the other — because the resume character travels on a transmit line that is itself paused. Implementations normally resolve this by exempting control characters from the pause, which is a sensible rule and another thing the hardware form does not need.

6. The Comparison, Without a Winner

RTS/CTSXON/XOFF
Extra pins2none
Works on a 3-wire linknoyes
Payload transparencycompleterequires a higher-layer guarantee
Consumes link bandwidthnoyes — one frame per transition
Reaction pathpin, one clockqueue + serialise + recognise
Latency depends on TX trafficnoyes, unless priority-injected
Control can be lostno — it is a levelyes — deadlock on a lost XON
Needs a timeout for robustnessnousually
Implementationcomparator + flip-flopdetector + injection path + policy

Neither dominates. Hardware flow control is the better mechanism where the pins exist, and the reason is narrower than "it is better engineered": it requires no promise from any layer above it. In-band control requires a guarantee about payload content that the UART cannot verify and did not make.

In-band control is the right choice where pins are genuinely unavailable and the payload is genuinely textual — which describes a large number of real links, including most of the terminal and AT-command traffic the mechanism was designed for. Dismissing it as obsolete misreads why it exists.

What is not defensible is enabling it on a binary link because it was the default, which §4 shows fails within the first hundred bytes.

7. The Same Detector in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  uart_xon_xoff_v — Synthesizable Verilog-2001
//
//  The receive-side detector. It watches COMPLETED characters, which is why
//  it sits after the receiver rather than on the wire: XOFF is a byte in the
//  data stream, and the only way to recognise it is to have decoded it.
//
//  consume_char_o says "this byte was control, not payload" -- the flag that
//  keeps a control character out of the application's data.
//===========================================================================
module uart_xon_xoff_v #(
    parameter [7:0] XON  = 8'h11,    // DC1
    parameter [7:0] XOFF = 8'h13     // DC3
) (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       rx_char_valid_i,
    input  wire [7:0] rx_char_data_i,
    input  wire       sw_flow_en_i,   // only interpret when ENABLED
    output reg        tx_paused_o,
    output reg        consume_char_o
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx_paused_o    <= 1'b0;
            consume_char_o <= 1'b0;
        end else begin
            consume_char_o <= 1'b0;                 // default: one-cycle pulse
            if (sw_flow_en_i && rx_char_valid_i) begin
                if (rx_char_data_i == XOFF) begin
                    tx_paused_o    <= 1'b1;
                    consume_char_o <= 1'b1;
                end else if (rx_char_data_i == XON) begin
                    tx_paused_o    <= 1'b0;
                    consume_char_o <= 1'b1;
                end
            end
        end
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  uart_xon_xoff -- Synthesizable VHDL-2008
--
--  The receive-side detector. It watches COMPLETED characters, which is why
--  it sits after the receiver rather than on the wire: XOFF is a byte in the
--  data stream and can only be recognised once decoded.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;

entity uart_xon_xoff is
    generic (
        XON  : std_logic_vector(7 downto 0) := x"11";   -- DC1
        XOFF : std_logic_vector(7 downto 0) := x"13"    -- DC3
    );
    port (
        clk             : in  std_logic;
        rst_n           : in  std_logic;
        rx_char_valid_i : in  std_logic;
        rx_char_data_i  : in  std_logic_vector(7 downto 0);
        sw_flow_en_i    : in  std_logic;    -- only interpret when ENABLED
        tx_paused_o     : out std_logic;
        consume_char_o  : out std_logic     -- control byte, not payload
    );
end entity uart_xon_xoff;

architecture rtl of uart_xon_xoff is
begin
    process (clk, rst_n)
    begin
        if rst_n = '0' then
            tx_paused_o    <= '0';
            consume_char_o <= '0';
        elsif rising_edge(clk) then
            consume_char_o <= '0';                  -- default: one-cycle pulse
            if sw_flow_en_i = '1' and rx_char_valid_i = '1' then
                if rx_char_data_i = XOFF then
                    tx_paused_o    <= '1';
                    consume_char_o <= '1';
                elsif rx_char_data_i = XON then
                    tx_paused_o    <= '0';
                    consume_char_o <= '1';
                end if;
            end if;
        end if;
    end process;
end architecture rtl;
PropertyVerilog-2001SystemVerilogVHDL-2008Agree?
control codesparameter [7:0]parameter logic [7:0]generic std_logic_vectoryes
watchescompleted characterssamesameyes
tx_paused_oLEVEL, held until XON or resetsamesameyes
consume_char_oONE cycleone cycleone cycleyes
gated by sw_flow_en_iyesyesyesyes
disabling while pauseddoes not resumesamesameyes

The last row is a contract, not an accident. The pause is state, not a live decode of the current byte. Turning the feature off while the transmitter is paused leaves it paused, because the far end asked for a stop and has not withdrawn it. Only an XON or a reset resumes. A design that resumed on disable would restart a stream the far end is still not ready for.

8. The Self-Checking Testbenches

The block's whole difficulty is that a control character is indistinguishable from payload that happens to have the same value, so the testbenches cover both readings of the identical byte:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// enabled: 0x13 is a command
rx_char(XOFF);  check(paused === 1'b1, "XOFF pauses the local transmitter");

// disabled: the SAME byte is data and must pass through untouched
sw_en = 1'b0;
rx_char(XOFF);  check(n_consume == base, "disabled: XOFF/XON are payload");

That pair is §4's transparency problem stated as a test. The feature does not merely cost two values from the data space — it makes the meaning of a byte depend on a configuration bit the receiver had better agree about.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  tb_uart_xon_xoff_v — self-checking Verilog-2001 testbench
//
//  The block's whole difficulty is that a control character is INDISTIN-
//  GUISHABLE from payload that happens to have the same value. The tests
//  therefore cover both readings: with software flow control enabled the
//  byte is a command and is consumed; with it disabled the identical byte is
//  data and must pass through untouched.
//===========================================================================
`timescale 1ns/1ps

module tb_uart_xon_xoff_v;

    localparam [7:0] XON = 8'h11, XOFF = 8'h13;

    reg clk = 1'b0, rst_n = 1'b0;
    always #5 clk = ~clk;

    reg        rx_valid = 1'b0, sw_en = 1'b1;
    reg  [7:0] rx_data  = 8'h00;
    wire       paused, consume;

    uart_xon_xoff_v #(.XON(XON), .XOFF(XOFF)) dut (
        .clk(clk), .rst_n(rst_n),
        .rx_char_valid_i(rx_valid), .rx_char_data_i(rx_data),
        .sw_flow_en_i(sw_en),
        .tx_paused_o(paused), .consume_char_o(consume));

    integer checks = 0, failures = 0;

    task check;
        input cond;
        input [8*58-1:0] name;
        begin
            checks = checks + 1;
            if (cond) $display("  PASS %0s", name);
            else begin failures = failures + 1; $display("  FAIL %0s", name); end
        end
    endtask

    integer n_consume = 0, consume_wide = 0;
    reg     consume_prev = 1'b0;
    always @(posedge clk) if (rst_n) begin
        if (consume) begin
            n_consume = n_consume + 1;
            if (consume_prev) consume_wide = consume_wide + 1;
        end
        consume_prev = consume;
    end

    task rx_char;
        input [7:0] d;
        begin
            @(negedge clk) rx_data = d; rx_valid = 1'b1;
            @(negedge clk) rx_valid = 1'b0;
            @(negedge clk);
        end
    endtask

    task settle; begin repeat (2) @(negedge clk); end endtask

    integer base, i;

    initial begin
        #300_000;
        $display("  FAIL watchdog: simulation did not finish");
        $display("== %0d checks, %0d failures ==", checks+1, failures+1);
        $display("   RESULT: VERILOG XON/XOFF TESTS FAILED (timeout)");
        $finish;
    end

    initial begin
        $display("== uart_xon_xoff_v : self-checking Verilog testbench ==");

        rst_n = 1'b0; sw_en = 1'b1;
        repeat (4) @(negedge clk);
        check(paused === 1'b0 && consume === 1'b0, "reset: not paused, no consume");
        @(negedge clk) rst_n = 1'b1;
        @(negedge clk);

        // ---- ordinary payload passes through ------------------------------
        base = n_consume;
        rx_char(8'h41); rx_char(8'hA5); rx_char(8'h00); rx_char(8'hFF);
        settle;
        check(n_consume == base, "four ordinary bytes: none consumed");
        check(paused === 1'b0,   "four ordinary bytes: still not paused");

        // ---- XOFF pauses ---------------------------------------------------
        base = n_consume;
        rx_char(XOFF);
        settle;
        check(paused === 1'b1,       "XOFF pauses the local transmitter");
        check(n_consume == base + 1, "XOFF is consumed, exactly once");

        // ---- the pause persists across unrelated traffic -------------------
        rx_char(8'h42); rx_char(8'h43);
        check(paused === 1'b1, "pause survives intervening payload");

        // ---- a second XOFF is idempotent ------------------------------------
        base = n_consume;
        rx_char(XOFF);
        settle;
        check(paused === 1'b1,       "second XOFF: still paused");
        check(n_consume == base + 1, "second XOFF is still consumed");

        // ---- XON resumes ----------------------------------------------------
        base = n_consume;
        rx_char(XON);
        settle;
        check(paused === 1'b0,       "XON resumes the local transmitter");
        check(n_consume == base + 1, "XON is consumed too");

        // ---- XON when not paused --------------------------------------------
        base = n_consume;
        rx_char(XON);
        settle;
        check(paused === 1'b0,       "XON while running: stays running");
        check(n_consume == base + 1, "and is still consumed as a control byte");

        // ---- THE ambiguity: the same byte with the feature DISABLED ---------
        // 0x13 is a legitimate data value. With software flow control off it
        // is payload and must not be touched. This is the cost the chapter
        // names: the feature steals two values from the data space.
        @(negedge clk) sw_en = 1'b0;
        base = n_consume;
        rx_char(XOFF); rx_char(XON);
        settle;
        check(n_consume == base, "disabled: XOFF/XON are payload, not consumed");
        check(paused === 1'b0,   "disabled: transmitter never pauses");

        // ---- re-enabling mid-stream ------------------------------------------
        @(negedge clk) sw_en = 1'b1;
        base = n_consume;
        rx_char(XOFF);
        settle;
        check(paused === 1'b1,       "re-enabled: XOFF is a command again");
        check(n_consume == base + 1, "re-enabled: and is consumed");

        // ---- disabling WHILE paused -------------------------------------------
        // The pause is state, not a live decode. Disabling the feature does
        // not resume the transmitter -- only an XON or a reset does.
        @(negedge clk) sw_en = 1'b0;
        repeat (4) @(negedge clk);
        check(paused === 1'b1, "disabling while paused does NOT resume");
        @(negedge clk) sw_en = 1'b1;
        rx_char(XON);
        check(paused === 1'b0, "XON still resumes after re-enabling");

        // ---- a byte with no valid strobe is not seen --------------------------
        base = n_consume;
        @(negedge clk) rx_data = XOFF; rx_valid = 1'b0;
        repeat (4) @(negedge clk);
        check(n_consume == base, "data without rx_char_valid is ignored");
        check(paused === 1'b0,   "and does not pause");

        // ---- reset while paused ------------------------------------------------
        rx_char(XOFF);
        check(paused === 1'b1, "paused before reset");
        @(negedge clk) rst_n = 1'b0;
        repeat (2) @(negedge clk);
        check(paused === 1'b0, "reset resumes the transmitter");
        @(negedge clk) rst_n = 1'b1;
        @(negedge clk);

        check(consume_wide == 0, "consume pulse never wider than one clock");

        $display("== %0d checks, %0d failures ==", checks, failures);
        if (failures == 0) $display("   RESULT: ALL VERILOG XON/XOFF TESTS PASSED");
        else               $display("   RESULT: VERILOG XON/XOFF TESTS FAILED");
        $finish;
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  tb_uart_xon_xoff_sv — self-checking Verilog-2001 testbench
//
//  The block's whole difficulty is that a control character is INDISTIN-
//  GUISHABLE from payload that happens to have the same value. The tests
//  therefore cover both readings: with software flow control enabled the
//  byte is a command and is consumed; with it disabled the identical byte is
//  data and must pass through untouched.
//===========================================================================
`timescale 1ns/1ps

module tb_uart_xon_xoff_sv;

    localparam [7:0] XON = 8'h11, XOFF = 8'h13;

    logic clk = 1'b0, rst_n = 1'b0;
    always #5ns clk = ~clk;

    logic rx_valid = 1'b0, sw_en = 1'b1;
    logic [7:0] rx_data  = 8'h00;
    logic paused, consume;

    uart_xon_xoff #(.XON(XON), .XOFF(XOFF)) dut (
        .clk(clk), .rst_n(rst_n),
        .rx_char_valid_i(rx_valid), .rx_char_data_i(rx_data),
        .sw_flow_en_i(sw_en),
        .tx_paused_o(paused), .consume_char_o(consume));

    int unsigned checks = 0, failures = 0;

    task automatic check(input bit cond, input string name);
        checks++;
        if (cond) $display("  PASS %0s", name);
        else begin failures++; $display("  FAIL %0s", name); end
    endtask

    int n_consume = 0, consume_wide = 0;
    logic consume_prev = 1'b0;
    always @(posedge clk) if (rst_n) begin
        if (consume) begin
            n_consume = n_consume + 1;
            if (consume_prev) consume_wide = consume_wide + 1;
        end
        consume_prev = consume;
    end

    task automatic rx_char(input logic [7:0] d);
        @(negedge clk) rx_data = d; rx_valid = 1'b1;
        @(negedge clk) rx_valid = 1'b0;
        @(negedge clk);
    endtask

    task automatic settle(); repeat (2) @(negedge clk); endtask

    int base, i;

    initial begin
        #300us;
        $display("  FAIL watchdog: simulation did not finish");
        $display("== %0d checks, %0d failures ==", checks+1, failures+1);
        $display("   RESULT: SYSTEMVERILOG XON/XOFF TESTS FAILED (timeout)");
        $finish;
    end

    initial begin
        $display("== uart_xon_xoff : self-checking Verilog testbench ==");

        rst_n = 1'b0; sw_en = 1'b1;
        repeat (4) @(negedge clk);
        check(paused === 1'b0 && consume === 1'b0, "reset: not paused, no consume");
        @(negedge clk) rst_n = 1'b1;
        @(negedge clk);

        // ---- ordinary payload passes through ------------------------------
        base = n_consume;
        rx_char(8'h41); rx_char(8'hA5); rx_char(8'h00); rx_char(8'hFF);
        settle();
        check(n_consume == base, "four ordinary bytes: none consumed");
        check(paused === 1'b0,   "four ordinary bytes: still not paused");

        // ---- XOFF pauses ---------------------------------------------------
        base = n_consume;
        rx_char(XOFF);
        settle();
        check(paused === 1'b1,       "XOFF pauses the local transmitter");
        check(n_consume == base + 1, "XOFF is consumed, exactly once");

        // ---- the pause persists across unrelated traffic -------------------
        rx_char(8'h42); rx_char(8'h43);
        check(paused === 1'b1, "pause survives intervening payload");

        // ---- a second XOFF is idempotent ------------------------------------
        base = n_consume;
        rx_char(XOFF);
        settle();
        check(paused === 1'b1,       "second XOFF: still paused");
        check(n_consume == base + 1, "second XOFF is still consumed");

        // ---- XON resumes ----------------------------------------------------
        base = n_consume;
        rx_char(XON);
        settle();
        check(paused === 1'b0,       "XON resumes the local transmitter");
        check(n_consume == base + 1, "XON is consumed too");

        // ---- XON when not paused --------------------------------------------
        base = n_consume;
        rx_char(XON);
        settle();
        check(paused === 1'b0,       "XON while running: stays running");
        check(n_consume == base + 1, "and is still consumed as a control byte");

        // ---- THE ambiguity: the same byte with the feature DISABLED ---------
        // 0x13 is a legitimate data value. With software flow control off it
        // is payload and must not be touched. This is the cost the chapter
        // names: the feature steals two values from the data space.
        @(negedge clk) sw_en = 1'b0;
        base = n_consume;
        rx_char(XOFF); rx_char(XON);
        settle();
        check(n_consume == base, "disabled: XOFF/XON are payload, not consumed");
        check(paused === 1'b0,   "disabled: transmitter never pauses");

        // ---- re-enabling mid-stream ------------------------------------------
        @(negedge clk) sw_en = 1'b1;
        base = n_consume;
        rx_char(XOFF);
        settle();
        check(paused === 1'b1,       "re-enabled: XOFF is a command again");
        check(n_consume == base + 1, "re-enabled: and is consumed");

        // ---- disabling WHILE paused -------------------------------------------
        // The pause is state, not a live decode. Disabling the feature does
        // not resume the transmitter -- only an XON or a reset does.
        @(negedge clk) sw_en = 1'b0;
        repeat (4) @(negedge clk);
        check(paused === 1'b1, "disabling while paused does NOT resume");
        @(negedge clk) sw_en = 1'b1;
        rx_char(XON);
        check(paused === 1'b0, "XON still resumes after re-enabling");

        // ---- a byte with no valid strobe is not seen --------------------------
        base = n_consume;
        @(negedge clk) rx_data = XOFF; rx_valid = 1'b0;
        repeat (4) @(negedge clk);
        check(n_consume == base, "data without rx_char_valid is ignored");
        check(paused === 1'b0,   "and does not pause");

        // ---- reset while paused ------------------------------------------------
        rx_char(XOFF);
        check(paused === 1'b1, "paused before reset");
        @(negedge clk) rst_n = 1'b0;
        repeat (2) @(negedge clk);
        check(paused === 1'b0, "reset resumes the transmitter");
        @(negedge clk) rst_n = 1'b1;
        @(negedge clk);

        check(consume_wide == 0, "consume pulse never wider than one clock");

        $display("== %0d checks, %0d failures ==", checks, failures);
        if (failures == 0) $display("   RESULT: ALL SYSTEMVERILOG XON/XOFF TESTS PASSED");
        else               $display("   RESULT: SYSTEMVERILOG XON/XOFF TESTS FAILED");
        $finish;
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  tb_uart_xon_xoff -- self-checking VHDL-2008 testbench
--  Same 23 checks. Both readings of the ambiguous byte are covered: enabled
--  it is a command, disabled it is payload.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use std.env.finish;

entity tb_uart_xon_xoff is
end entity tb_uart_xon_xoff;

architecture sim of tb_uart_xon_xoff is
    constant XONC  : std_logic_vector(7 downto 0) := x"11";
    constant XOFFC : std_logic_vector(7 downto 0) := x"13";
    constant CLK_PERIOD : time := 10 ns;

    signal clk, rst_n, done : std_logic := '0';
    signal rx_valid : std_logic := '0';
    signal sw_en    : std_logic := '1';
    signal rx_data  : std_logic_vector(7 downto 0) := (others => '0');
    signal paused, consume : std_logic;
    signal n_consume, consume_wide : natural := 0;
begin
    clk <= not clk after CLK_PERIOD/2 when done = '0' else '0';

    dut : entity work.uart_xon_xoff
        generic map (XON => XONC, XOFF => XOFFC)
        port map (clk => clk, rst_n => rst_n,
                  rx_char_valid_i => rx_valid, rx_char_data_i => rx_data,
                  sw_flow_en_i => sw_en,
                  tx_paused_o => paused, consume_char_o => consume);

    obs : process (clk)
        variable n, w : natural := 0;
        variable prev : std_logic := '0';
    begin
        if rising_edge(clk) and rst_n = '1' then
            if consume = '1' then
                n := n + 1;
                if prev = '1' then w := w + 1; end if;
            end if;
            prev := consume;
            n_consume <= n; consume_wide <= w;
        end if;
    end process obs;

    watchdog : process
    begin
        wait for 300 us;
        report "   RESULT: VHDL XON/XOFF TESTS FAILED (timeout)" severity failure;
        wait;
    end process watchdog;

    stim : process
        variable checks, failures, base : natural := 0;
        procedure check(cond : boolean; name : string) is
        begin
            checks := checks + 1;
            if cond then report "  PASS " & name;
            else failures := failures + 1; report "  FAIL " & name severity error; end if;
        end procedure;
        procedure rx_char(d : std_logic_vector) is
        begin
            wait until falling_edge(clk); rx_data <= d; rx_valid <= '1';
            wait until falling_edge(clk); rx_valid <= '0';
            wait until falling_edge(clk);
        end procedure;
        procedure settle is
        begin
            for i in 0 to 1 loop wait until falling_edge(clk); end loop;
        end procedure;
    begin
        report "== uart_xon_xoff (VHDL) : self-checking testbench ==";
        rst_n <= '0'; sw_en <= '1';
        for i in 0 to 3 loop wait until falling_edge(clk); end loop;
        check(paused = '0' and consume = '0', "reset: not paused, no consume");
        rst_n <= '1'; wait until falling_edge(clk);

        base := n_consume;
        rx_char(x"41"); rx_char(x"A5"); rx_char(x"00"); rx_char(x"FF");
        settle;
        check(n_consume = base, "four ordinary bytes: none consumed");
        check(paused = '0',     "four ordinary bytes: still not paused");

        base := n_consume; rx_char(XOFFC); settle;
        check(paused = '1',       "XOFF pauses the local transmitter");
        check(n_consume = base+1, "XOFF is consumed, exactly once");

        rx_char(x"42"); rx_char(x"43");
        check(paused = '1', "pause survives intervening payload");

        base := n_consume; rx_char(XOFFC); settle;
        check(paused = '1',       "second XOFF: still paused");
        check(n_consume = base+1, "second XOFF is still consumed");

        base := n_consume; rx_char(XONC); settle;
        check(paused = '0',       "XON resumes the local transmitter");
        check(n_consume = base+1, "XON is consumed too");

        base := n_consume; rx_char(XONC); settle;
        check(paused = '0',       "XON while running: stays running");
        check(n_consume = base+1, "and is still consumed as a control byte");

        wait until falling_edge(clk); sw_en <= '0';
        base := n_consume; rx_char(XOFFC); rx_char(XONC); settle;
        check(n_consume = base, "disabled: XOFF/XON are payload, not consumed");
        check(paused = '0',     "disabled: transmitter never pauses");

        wait until falling_edge(clk); sw_en <= '1';
        base := n_consume; rx_char(XOFFC); settle;
        check(paused = '1',       "re-enabled: XOFF is a command again");
        check(n_consume = base+1, "re-enabled: and is consumed");

        wait until falling_edge(clk); sw_en <= '0';
        for i in 0 to 3 loop wait until falling_edge(clk); end loop;
        check(paused = '1', "disabling while paused does NOT resume");
        wait until falling_edge(clk); sw_en <= '1';
        rx_char(XONC);
        check(paused = '0', "XON still resumes after re-enabling");

        base := n_consume;
        wait until falling_edge(clk); rx_data <= XOFFC; rx_valid <= '0';
        for i in 0 to 3 loop wait until falling_edge(clk); end loop;
        check(n_consume = base, "data without rx_char_valid is ignored");
        check(paused = '0',     "and does not pause");

        rx_char(XOFFC);
        check(paused = '1', "paused before reset");
        wait until falling_edge(clk); rst_n <= '0';
        for i in 0 to 1 loop wait until falling_edge(clk); end loop;
        check(paused = '0', "reset resumes the transmitter");
        wait until falling_edge(clk); rst_n <= '1';
        wait until falling_edge(clk);

        check(consume_wide = 0, "consume pulse never wider than one clock");

        report "== " & integer'image(checks) & " checks, " &
               integer'image(failures) & " failures ==";
        if failures = 0 then report "   RESULT: ALL VHDL XON/XOFF TESTS PASSED";
        else report "   RESULT: VHDL XON/XOFF TESTS FAILED" severity error; end if;
        done <= '1'; finish;
    end process stim;
end architecture sim;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
== uart_xon_xoff_v : self-checking Verilog testbench ==
  PASS four ordinary bytes: none consumed
  PASS XOFF pauses the local transmitter
  PASS XOFF is consumed, exactly once
  PASS pause survives intervening payload
  PASS second XOFF: still paused
  PASS XON resumes the local transmitter
  PASS XON while running: stays running
  PASS disabled: XOFF/XON are payload, not consumed
  PASS re-enabled: XOFF is a command again
  PASS disabling while paused does NOT resume
  PASS data without rx_char_valid is ignored
  PASS reset resumes the transmitter
  PASS consume pulse never wider than one clock
== 23 checks, 0 failures ==
   RESULT: ALL VERILOG XON/XOFF TESTS PASSED

23 checks in each language, 0 failures.

Proving these checks can fail

MutationWhat it breaksFailures
consume_char_o is a level, not a pulsethe one-cycle contract8 / 23
ignores sw_flow_en_i, always interprets§4's transparency rule1 / 23

One check out of twenty-three catches the second one, and it is the check that drives 0x13 with the feature disabled. A detector that always interprets is correct for every enabled test and silently eats a data byte the moment a user turns the feature off — which is the shape of a bug that reaches a customer rather than a regression.

9. Verification

StimulusExpectedResult
resetnot pausedpass
ordinary byte 0x41no pause, not consumedpass
0x13 with flow control enabledpaused, consumedpass
ordinary byte while pausedstays pausedpass
0x11 with flow control enabledresumed, consumedpass
0x13 with flow control DISABLEDno pause, not consumed — it is payloadpass

The last row is the transparency contract expressed as a test, and it is the one that matters. A detector that acts on 0x13 regardless of the enable has made the mechanism unconditional, and every binary link using that UART will corrupt data — silently, within the first hundred bytes per §4.

The positive control is the ordinary byte. A detector that pauses on everything passes every negative test.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — a pause change requires an enabled control character.
// Nothing else may alter the pause state.
property p_pause_needs_control;
    @(posedge clk) disable iff (!rst_n)
        $changed(tx_paused_o) |-> $past(sw_flow_en_i && rx_char_valid_i);
endproperty
assert property (p_pause_needs_control);

// Assertion — with software flow control disabled, control VALUES are
// ordinary payload. This is the transparency contract.
property p_disabled_is_transparent;
    @(posedge clk) disable iff (!rst_n)
        !sw_flow_en_i |=> (!consume_char_o && $stable(tx_paused_o));
endproperty
assert property (p_disabled_is_transparent);

// Assertion — a consumed character is always one of the two control values.
property p_consume_only_control;
    @(posedge clk) disable iff (!rst_n)
        consume_char_o |-> ($past(rx_char_data_i) inside {XON, XOFF});
endproperty
assert property (p_consume_only_control);

What is not asserted: that the far end stops within any time bound. That is a property of the far end's software, which this design does not control — the same discipline Chapter 10.5 §9 applied.

The system test that matters is a binary payload sweep with software flow control enabled, asserting that data is corrupted. Writing a test whose pass condition is the mechanism breaks the link feels wrong and pins the boundary exactly — and it documents in executable form why the enable exists.

10. Debugging

11. What This Means on an FPGA

The detector is two comparators and two flip-flops. It is the cheapest block in Module 10, and its cost is entirely in the surrounding policy rather than the logic.

The priority-injection path is where the real work is. Inserting a control character ahead of queued payload means either a separate high-priority register the transmit engine checks before popping the FIFO, or a small bypass — and either way it must not disturb a frame in flight. That is more logic than the detector and it is what makes the mechanism's latency deterministic.

Bring the enable out as a register bit, not a parameter. Whether a link carries text or binary is a runtime property of the system, and a design that fixes it at synthesis will be wrong for one of its use cases. Module 13 owns the register; the decision to make it runtime belongs to whoever instantiates this block.

Probe consume_char_o during bring-up on a binary link. Any assertion at all is §4 occurring, and it is the direct evidence that the enable is set wrongly — far more useful than inferring it from missing bytes.

12. Assertions Across the Module

Five blocks, and the properties worth asserting are the ones whose violation leaves the design working — where only the meaning is wrong.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Published as SystemVerilog Assertions. Icarus Verilog 13.0 supports
// neither named properties nor $past, so what was EXECUTED is the
// procedural equivalent inside each testbench.

// p_level_bounded -- catches a width or update error in the occupancy
// counter. A level that can exceed DEPTH makes `full` meaningless and a
// level that cannot reach DEPTH makes it unreachable.
property p_level_bounded;
    @(posedge clk) disable iff (!rst_n) level_o <= CNT_W'(DEPTH);
endproperty

// p_empty_full_exclusive -- catches the two flags being derived from
// different quantities. They are both functions of one counter and can
// never both be true unless DEPTH is zero.
property p_empty_full_exclusive;
    @(posedge clk) disable iff (!rst_n) !(empty_o && full_o);
endproperty

// p_no_overflow_on_handoff -- catches the `push && !full` formulation.
// A push that coincides with a pop must be accepted, and reporting an
// overflow there is a lost byte at exactly the moment the consumer was
// keeping up.
property p_no_overflow_on_handoff;
    @(posedge clk) disable iff (!rst_n)
        (push_i && pop_i && full_o) |=> !overflow_evt_o;
endproperty

// p_hysteresis_holds -- catches a single-threshold flow controller. Between
// the two levels the decision must not change, whatever occupancy does.
property p_hysteresis_holds;
    @(posedge clk) disable iff (!rst_n)
        (level_i > RESUME_LEVEL && level_i < STOP_LEVEL) |=> $stable(allow_remote_o);
endproperty

// p_cts_two_flops -- catches a synchroniser shortened to one stage. The
// output must not respond to cts_n_i in fewer than two clocks.
property p_cts_two_flops;
    @(posedge clk) disable iff (!rst_n)
        $changed(cts_n_i) |=> $stable(launch_allowed_o);
endproperty

// p_consume_is_a_pulse -- catches a control-byte strobe held as a level,
// which makes a downstream consumer discard more than one byte.
property p_consume_is_a_pulse;
    @(posedge clk) disable iff (!rst_n) consume_char_o |=> !consume_char_o;
endproperty
PropertyDefect it catchesWhy it would otherwise be missed
p_level_boundedoccupancy width wrongfull unreachable; the FIFO silently never asserts backpressure
p_empty_full_exclusiveflags from different quantitiesboth false simultaneously reads as "partly filled" forever
p_no_overflow_on_handoffpush && !fullone byte lost per full-FIFO hand-off, under load only
p_hysteresis_holdssingle thresholdRTS chatters; the far end sees a flapping permission
p_cts_two_flopsone-stage synchroniseridentical behaviour except under metastability
p_consume_is_a_pulsestrobe held as a levelpayload bytes silently eaten after a control byte

13. UVM Mapping

The full environment is Module 16's subject. What is worth fixing here is which part of the buffering contract each component owns, because those boundaries follow from this module rather than from UVM.

Contract elementUVM componentWhat it carries
a byte to send, and whensequence itemdata, gap_bits (inter-character idle)
producing it on the wiredriverserialisation at the configured baud
how fast software drainsa second sequenceservice_latency, burst_size
observing rts_n / cts_nmonitorpin transitions, timestamped
observing delivered bytesmonitorwhat software actually read
what should have survivedpredictorthe queue model, from the stimulus alone
comparisonscoreboarddelivered sequence, and which bytes were lost

Three things in that table are decided by this module, not by UVM.

The interesting stimulus is the CONSUMER, not the producer. Every other UART agent drives the line and watches the DUT. Here the line is the easy half: overflow happens because software was slow, so the sequence that matters models service latency. An environment that only varies the traffic will never fill a FIFO that is being drained promptly, and will report full coverage of a block it never stressed.

The scoreboard must check WHICH bytes were lost, not how many. §3 established the policy — the new byte is dropped, the held one survives — and a scoreboard counting losses would pass the opposite policy. This is not theoretical: the mutation that inverts the policy is caught by three checks in the unit testbench, and all three inspect payload identity.

Flow control needs a two-sided monitor and a latency budget, not a pass/fail. §3 of 10.5 derived the in-flight budget: bytes already committed when RTS deasserts are expected to arrive. A scoreboard that flagged them as overflow would fail a correct design. The check is that arrivals after the deassertion do not exceed the budget.

Coverage worth collecting, from the axes this module established:

AxisBinsWhy
FIFO occupancy at pushempty, 1, mid, DEPTH−1, DEPTHthe boundary is the only interesting point
simultaneous push/popat empty, mid, at full§7's enumerated case
DEPTHpower of two, non-power-of-twoa truncating pointer is correct at one and not the other
service latencyprompt, marginal, absentoverflow only exists in the third
flow controlnone, RTS/CTS, XON/XOFFthree different failure surfaces
occupancy relative to thresholdsbelow RESUME, in band, above STOPhysteresis is only testable in the band

The cross that earns its keep is occupancy × simultaneous-push-pop, because the acceptance rule of §7 differs from the naive one at exactly one point in that cross and nowhere else.

What is deliberately not here: no uvm_component boilerplate, no factory registration, no config-db plumbing. None of it teaches anything about buffering.

14. The Module 10 Backpressure Matrix

Every way this module's hardware can refuse, lose or delay a byte, end to end. Read the "Where it stops" column first — it is what distinguishes these mechanisms from one another.

MechanismTriggerWhere it stopsLatency to effectWhat is lostRecovery
FIFO fulloccupancy = DEPTHnothing — the FIFO just refusesimmediatethe new byte (10.4 §3)automatic once software reads
FIFO emptyoccupancy = 0nothing — the pop just failsimmediatenothing; underflow is a statement, not a lossautomatic on the next push
RX watermarklevel ≥ triggernothing — it only asksimmediatenothingsoftware services the FIFO
TX watermarklevel ≤ triggernothing — it only asksimmediatenothingsoftware refills
RTS deassertlevel ≥ STOP_LEVELthe far end's transmitterits detection + in-flight bytesnothing, if the budget was rightlevel falls to RESUME_LEVEL
CTS deassertfar end's decisionour launch, at a frame boundary2 clocks (synchroniser) + current framenothingCTS reasserts
XOFF receiveda byte equal to 0x13our transmitterone whole character time, minimumnothing — but 0x13 is no longer usable dataXON received

Three rows stop nothing at all, and that is the distinction §1 of 10.3 is built on: a watermark is a warning, a full flag is a boundary. Confusing them produces a design that politely announces a condition it has already failed to prevent.

One row has a latency measured in character times rather than clocks. An XOFF cannot be acted on until the whole character carrying it has been received, which is why §3 of this chapter calls its latency worse and for an extra reason.

15. The Module 10 Verification Matrix

RequirementDirected testCorner testAssertionCoverageWaveform
occupancy 0..DEPTH, never beyond1500 random cycles ×3fill to DEPTH exactlyp_level_boundedoccupancy bins10.2 Fig 1
full reachable at allfill to DEPTHcounter widthp_level_bounded——
push+pop at full is acceptedexplicit case ×3at empty toop_no_overflow_on_handoffoccupancy × simultaneity10.2 §7
overflow drops the new bytepayload identity ×35 offered, 1 kept——10.4 Fig 1
pointers wrap explicitlyDEPTH = 5 twin200 wrap cycles—DEPTH bins—
byte order preservedqueue model, 1500 cyclesacross wrap———
watermark senses are oppositeempty and full ×317×17 exhaustive——10.3 Fig 1
trigger levels are runtimereprogram mid-test ×3degenerate 0 / DEPTH———
hysteresis holds in the bandrise-then-fall ×370 boundary crossingsp_hysteresis_holdsthreshold bins10.5 Fig 1
rts_n is the inverse of allowevery cycle ×3————
CTS takes two clocksmeasured, not assumedone-clock glitchp_cts_two_flops——
a frame in flight is never truncatedtx_busy held ×3———10.5 §7
XOFF/XON only when enabledboth readings ×3disable while paused—flow-control bins—
consume_char_o is one cyclewhole-run observer ×3—p_consume_is_a_pulse——
reset clears every blockall five ×3reset while full/paused———

Two rows have no assertion and that is deliberate. "Overflow drops the new byte" and "byte order preserved" are statements about a sequence of payloads, which an immediate assertion cannot express; they are checked by the queue model instead, which is the stronger instrument.

16. Module Evidence

Five RTL blocks, each in three languages, each with a self-checking testbench in its own language. Every number quoted anywhere in Module 10 comes from these runs.

BlockChapterVerilogSystemVerilogVHDL
uart_sync_fifo10.2 §11303030
uart_fifo_watermark10.3 §8161616
uart_flow_ctrl10.5 §9212121
uart_tx_gate10.5 §9141414
uart_xon_xoff§8 above232323
total104104104

312 checks across three languages, 0 failures.

LanguageToolCompileSimulate
Verilog-2001Icarus Verilog 13.0 (-g2001)cleanyes
SystemVerilogIcarus Verilog 13.0 (-g2012)cleanyes
VHDL-2008NVC 1.23.0 (LLVM 23.1.1)cleanyes

The per-block counts are identical in all three languages. That is the acceptance criterion rather than a coincidence: a module where SystemVerilog runs two hundred checks and VHDL runs three has verified one design and compiled two others.

The whole mutation campaign

Ten deliberate defects, each run against the unmodified testbench for its block:

MutationBlockFailures
occupancy counter sized like a pointerFIFO19 / 30
push rejected when it coincides with a popFIFO9 / 30
consume_char_o held as a levelXON/XOFF8 / 23
two conditional increments instead of one caseFIFO7 / 30
both watermark senses the same directionwatermark6 / 16
one threshold instead of twoflow control4 / 21
pointers truncate instead of wrappingFIFO3 / 30
one synchroniser flop instead of twoTX gate1 / 14
tx_busy_i added to the launch termTX gate1 / 14
detector ignores sw_flow_en_iXON/XOFF1 / 23

17. Understanding Check

18. Summary

In-band flow control sends the request inside the byte stream: 0x13 for XOFF and 0x11 for XON, by convention rather than by anything UART defines. It removes the two-pin requirement and works on a three-wire link.

The detector is trivial; the surrounding policy is not. sw_flow_en_i gates the interpretation, and consume_char_o is separate from tx_paused_o because they state different facts. The pause gates the launch, never a frame in progress.

The latency has a stage the pin form does not: the control character must be serialised, costing at least one frame time — and 1.48 ms at 115,200 baud if it waits behind sixteen queued payload bytes. The remedy is priority injection, which is more logic than the detector.

The transparency problem is quantitative: two values in 256 means 54% of 100 random bytes contain one, and 99.96% of 1,000. A payload 0x13 stops the far end and vanishes; a payload 0x11 lifts a genuine pause and vanishes. Both silently.

The UART cannot solve it, because the framing carries no type information. The four responses — disable, reserve, escape, or use a framing protocol that already escapes — all live above the UART.

A lost XON deadlocks the link silently, because in-band control is stateful and edge-like where a pin is stateless and level-like. Mutual pause needs control characters exempted.

Neither mechanism dominates. Hardware flow control is preferred where pins allow — not because it is better engineered, but because it requires no promise from any layer above it. In-band control remains right for text-oriented links without spare pins, which is a large fraction of real traffic.

19. Where Module 10 Leaves You

Chapter 10.1 established that depth buys time, not bandwidth — a consumer 0.17% too slow overflows 64 entries in 3.2 seconds. Chapter 10.2 built the FIFO and settled that it is synchronous unless the two sides genuinely sit in different clock domains, fixing a contract that every later chapter used unchanged. Chapter 10.3 derived trigger levels from a latency budget and showed the interrupt-load cost of every entry of headroom. Chapter 10.4 located the boundary precisely and established that ready has no path off the device. Chapter 10.5 reached the far end with a pin and proved the reach is not instantaneous — 17 bytes in flight at 3 Mbaud with a 50 µs far end, which no 16-entry FIFO can absorb. This chapter reached it without a pin, at the cost of a promise nobody may have made.

The thread through all six: buffering converts a hard real-time requirement into an average-rate requirement, and flow control converts an average-rate requirement into a negotiated one. Neither creates capacity. A link whose consumer is genuinely slower than its line fails under all three, and the only remaining levers are a lower rate or a faster consumer.

An engineer holding this module can size a FIFO from a measured latency, derive two different thresholds for two different questions, say which byte is lost and why, compute how many characters arrive after backpressure is asserted, and tell when flow control cannot work at all.

20. What Comes Next

Every block in this module was built and verified independently. That was deliberate — a FIFO whose correctness depends on the transmitter it feeds is a FIFO nobody can reason about.

Module 11 assembles them. One UART IP with a receiver, a transmitter, a baud generator, both FIFOs, the thresholds and the flow control — plus the configuration and status plumbing that lets one coherent interface present all of it. That is where the independent pieces acquire a top level, a configuration story, and the integration decisions that only appear when blocks meet.

After that: Module 12 returns to the clock-domain and reset questions this module deliberately scoped out — including the genuine asynchronous FIFO for the case where the two sides really do sit in different domains; Module 13 builds the register interface, interrupts and DMA hooks that the triggers and status of this module have been produced for.

Browse the full path on the UART tutorials index. For the hardware mechanism this chapter is the alternative to, read back to Chapter 10.5.

Continue learning

Where this fits

Part of the UART curriculum.