Skip to content
VLSI Mentor

UART · Module 13

Bus Attachment and Integration Concerns

What attaching the register block to an APB- or AXI-class bus requires of the UART, what the UART must require of the integration, and the clock question — answered by measurement.

This is not a bus tutorial. The interesting question is narrower and more useful: what does attaching to a bus require of the UART, and what must the UART require of the integration?

The answer is short in one direction and longer in the other, and the asymmetry is the point. A well-built peripheral asks for very little. What it demands — and Chapter 13.1 established that a read side effect demands quite a lot — has to be written down, because nothing enforces it automatically.

1. The Division of Responsibility

The UART providesThe interconnect provides
a register block with a defined access contractaddress decode down to this peripheral
completion of every transfer it acceptsordering between masters
status that is true when readthe memory type of the region
side effects tied to a committed transfernot issuing transfers that were not asked for

The bottom row is the whole relationship. The UART promises that a side effect fires exactly once per completed transfer; the system promises that a completed transfer means somebody actually wanted the data. Neither promise is useful without the other.

2. APB Is the Easy Case

Chapter 13.1 used APB deliberately. It has properties that make a side-effecting register straightforward:

PropertyWhy it helps
one transfer at a timeno outstanding reads to reason about
explicit setup and access phasesthe committed cycle is unambiguous
no burstsa transfer is one access to one address
no speculationa transfer happened because a master issued it
no reorderingreads and writes arrive in program order
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign access   = psel_i && penable_i && pready_o;
assign do_read  = access && !pwrite_i;
assign rd_ready = do_read && (paddr_i == A_DATA);

One expression, exactly correct, because the bus defines "committed" precisely. Chapter 13.1 §3 measured what happens when it is written slightly wrong — 4 bytes delivered out of 8, one read consuming exactly 2.0 bytes.

3. What an AXI-Class Attachment Changes

Three things, and only one of them is really about the UART.

Separate address and data channels with outstanding transactions. A master may issue several reads before any completes. For the UART this is mostly a bridging problem — the adapter must return responses in the required order and track IDs — and it does not change when the side effect fires: still exactly once, when the read data for that transaction is accepted. What it does change is that "the moment of commitment" is further from "the moment the address arrived", so the pop must be tied to the data handshake rather than to the address one.

Bursts. A burst read to a fixed address is legal and pops once per beat. That is correct behaviour and sometimes exactly what is wanted — it is how a DMA engine efficiently drains the queue (Chapter 13.4). It is also how a processor doing something innocuous can consume four bytes when it meant to read one, so the register map's documentation has to say which reads are bursts-safe. Here, DATA is the only address where a burst means anything, and a burst to it means give me N bytes.

Byte strobes. APB3 has none; APB4 and AXI do. A register block that ignores them mishandles sub-word writes — a byte write to one field of CTRL would update the whole register from whatever the master placed on the unused lanes. This block ignores strobes, which is safe only because its contract says accesses are word-aligned and word-sized. That constraint is now part of the map and belongs in its documentation rather than in an assumption.

4. Bridge, Don't Rewrite

The practical answer for almost every UART is to keep the register block APB and put a bridge in front of it.

The bridge is generic and already exists. An AXI-to-APB bridge is a standard interconnect component, verified, and shared by every simple peripheral in the SoC. Rewriting the UART's register block in AXI duplicates that work per peripheral and makes the UART bus-specific — losing exactly the reusability Chapter 11.1 §5 and Chapter 13.1 §1 were protecting.

A UART does not need AXI's bandwidth. At 115,200 baud a byte arrives every 86.8 µs. Even at 3 Mbaud it is one byte every 3.3 µs. No plausible UART saturates an APB link, so the only reason to attach natively would be latency, and a bridge costs a handful of cycles against a bit period measured in microseconds.

When a native attachment is warranted it is usually because the peripheral is behind a bus that has no APB segment at all, or because a very wide FIFO access is wanted. Both are integration decisions, not UART decisions.

A block diagram of the UART's bus attachment. On the left a processor or DMA engine issues transactions onto an AXI-class interconnect. The interconnect decodes the address and routes peripheral accesses to a generic AXI-to-APB bridge, which is a standard shared component rather than part of the UART. The bridge presents APB to the UART's register block, which qualifies each committed transfer and converts it into the handshakes the unmodified UART core already speaks. A separate arrow runs in the opposite direction, from the register block back to the system, labelled as a requirement rather than a signal: because the data register has a read side effect, the region must be mapped as device memory, non-cacheable, non-prefetchable and not speculatively accessible, and no hardware in the path can enforce that.CPU / DMAmastersinterconnectdecode, orderingAXI-to-APBbridgegeneric, shareduart_regsthe access contractuart_ipunmodifiedREQUIREMENTdevice,non-cacheablebecause DATApopsunenforceable inhardwaretxnsregionAPBvld/rdymapped as12
Figure 1 — the attachment. The UART's register block speaks one simple protocol; a generic bridge adapts whatever the system uses. The requirements flow the other way: the peripheral's read side effect constrains how the region must be mapped, and nothing in the hardware can enforce that.

5. The Clock Question, Answered by Measurement

Chapter 12.3 §10 advised asking whether two clocks are needed at all before reaching for a crossing. This is where that question is actually posed, and the answer for a UART is usually no.

The register block and the core share one clock. Run that clock at whatever the bus runs at, and set CLK_HZ to match — the baud divisor follows from the parameter (Chapter 8.3 makes the divisor a value, not a structure).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- bus clock 100 MHz, 115200 baud : 868 clocks per bit
  pass 100 MHz bus: all 8 bytes loop back correctly
-- bus clock 50 MHz, 115200 baud : 434 clocks per bit
  pass 50 MHz bus: all 8 bytes loop back correctly
-- bus clock 24 MHz, 115200 baud : 208 clocks per bit
  pass 24 MHz bus: all 8 bytes loop back correctly

   RESULT: one clock, three bus frequencies, no CDC needed

Three bus frequencies, the same RTL, one parameter changed, no clock-domain crossing anywhere. That is the whole answer for the overwhelming majority of UART integrations, and it eliminates the asynchronous FIFO, the pointer synchronisers, the gray coding and the constraints that go with them.

6. The Integration Contract

What the UART's documentation must state, because none of it is discoverable from the RTL:

RequirementWhy
region is Device / strongly-ordered, non-cacheable, non-prefetchableDATA has a read side effect — §3
accesses are word-aligned and word-sizedbyte strobes are ignored — §3
a burst read of DATA returns N different bytesit is N pops, by design
reading any other register is free of side effectsdebuggers and pollers depend on it
CLK_HZ must match the actual clock frequencythe baud divisor is derived from it
the interrupt output is a levelChapter 13.3
the DMA requests are levelsChapter 13.4 §11
trigger level should match the DMA burst sizeChapter 13.4 §2

Every row is a promise the system must keep, and not one of them can be checked by the UART. That is what an integration guide is for, and a peripheral whose guide omits the first row will eventually lose data on a system that did nothing wrong by its own rules.

7. A UVM Environment for a Register Block

Chapter 13.2 built the register model. This is what sits underneath it — and the shape is worth learning because it is the same for every memory-mapped peripheral anyone will ever verify.

The register layer does not know what a bus is. It produces uvm_reg_bus_op items and hands them to an adapter, which converts them into whatever the agent drives. Change the bus and the adapter changes; the model, the sequences and the tests do not. That indirection is the entire reason to use the register layer on a design whose map is only eleven entries long.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
//  uart_apb_adapter — the ONE class that knows this is APB
//
//  Swap this for an AXI-Lite adapter and every register sequence written
//  against the model keeps working unchanged. That is the whole contract.
// ---------------------------------------------------------------------------
class uart_apb_adapter extends uvm_reg_adapter;
    `uvm_object_utils(uart_apb_adapter)

    function new(string name = "uart_apb_adapter");
        super.new(name);
        supports_byte_enable = 0;    // APB3 here has no PSTRB
        provides_responses   = 0;    // and no separate response channel
    endfunction

    virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
        apb_item it = apb_item::type_id::create("it");
        it.write = (rw.kind == UVM_WRITE);
        it.addr  = rw.addr;
        it.data  = rw.data;
        return it;
    endfunction

    virtual function void bus2reg(uvm_sequence_item bus_item,
                                  ref uvm_reg_bus_op rw);
        apb_item it;
        if (!$cast(it, bus_item)) begin
            `uvm_fatal("ADAPTER", "bus item is not an apb_item")
            return;
        end
        rw.kind   = it.write ? UVM_WRITE : UVM_READ;
        rw.addr   = it.addr;
        rw.data   = it.data;
        rw.status = UVM_IS_OK;
    endfunction
endclass

// ---------------------------------------------------------------------------
//  uart_env — model, agent, adapter, predictor, scoreboard
// ---------------------------------------------------------------------------
class uart_env extends uvm_env;
    `uvm_component_utils(uart_env)

    uart_reg_block      regmodel;
    apb_agent           m_apb;
    uart_apb_adapter    m_adapter;
    uvm_reg_predictor #(apb_item) m_predictor;
    uart_line_agent     m_line;     // the serial side — Modules 3 to 9
    uart_scoreboard     m_sb;

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        regmodel = uart_reg_block::type_id::create("regmodel");
        regmodel.build();
        regmodel.lock_model();

        m_apb       = apb_agent::type_id::create("m_apb", this);
        m_line      = uart_line_agent::type_id::create("m_line", this);
        m_sb        = uart_scoreboard::type_id::create("m_sb", this);
        m_adapter   = uart_apb_adapter::type_id::create("m_adapter");
        m_predictor = uvm_reg_predictor#(apb_item)::type_id::create("m_predictor", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        regmodel.apb_map.set_sequencer(m_apb.sqr, m_adapter);

        // EXPLICIT prediction, not implicit. The predictor watches the bus
        // monitor and updates the mirror from what actually happened on the
        // wire, rather than from what the register layer intended. On a
        // peripheral whose registers change by themselves -- every status bit
        // here does -- that distinction is the difference between a mirror
        // that tracks the design and one that tracks the testbench's hopes.
        regmodel.apb_map.set_auto_predict(0);
        m_predictor.map     = regmodel.apb_map;
        m_predictor.adapter = m_adapter;
        m_apb.mon.ap.connect(m_predictor.bus_in);

        m_apb.mon.ap.connect(m_sb.bus_export);
        m_line.mon.ap.connect(m_sb.line_export);
    endfunction
endclass

The sequence library that the map earns you. With access policies set correctly (13.2 §6), three built-in sequences run with no test-specific code at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class uart_reg_sanity_test extends uart_base_test;
    `uvm_component_utils(uart_reg_sanity_test)

    task run_phase(uvm_phase phase);
        uvm_reg_hw_reset_seq   rst_seq;   // every reset value, from the model
        uvm_reg_bit_bash_seq   bash_seq;  // every writable bit, one at a time
        uvm_reg_access_seq     acc_seq;   // front door against back door
        phase.raise_objection(this);

        rst_seq = uvm_reg_hw_reset_seq::type_id::create("rst_seq");
        rst_seq.model = env.regmodel;
        rst_seq.start(null);
        // ... bash_seq and acc_seq the same way

        phase.drop_objection(this);
    endtask
endclass

uvm_reg_hw_reset_seq alone is worth the model: it checks every reset value in the map against the RTL, which is the table 13.2 §5 documents and which nothing else in a typical testbench ever verifies. The three exclusions from 13.2 §6 are what make it pass — and each of them is a claim about the design, not a way to make a failure go away.

8. Module 13 Verification Evidence

Every listing published in this module was extracted from the page you are reading, compiled with a real tool, and simulated. The numbers below are the output of those runs.

Three blocks, three languages, nine designs:

BlockSystemVerilogVerilog-2001VHDL-2008Chapter
uart_regs✅✅✅13.2
uart_irq✅✅✅13.3
uart_dma_if✅✅✅13.4

Nine testbenches, identical check counts across all three languages:

SuiteChecksVerilog-2001SystemVerilogVHDL-2008
uart_regs4040 / 040 / 040 / 0
uart_irq2424 / 024 / 024 / 0
uart_dma_if1818 / 018 / 018 / 0
Total per language8282 / 082 / 082 / 0

246 checks across the three languages, 0 failures. Tooling: Icarus Verilog 13.0 (-g2001 and -g2012) and NVC 1.23.0 for VHDL-2008.

Mutation campaign — twelve defects, twelve killed:

#BlockDefect installedKilled by
M1regsside effect qualified on psel alone14 checks
M2regspop not qualified on the address4
M3regsERR made clear-dominant1
M4regsflush bits held as levels2
M5regsW1C clearing every flag1
M6regsvalid bit tied high1
M7irqIRQ_STAT ignoring the enable mask4
M8irqirq_o driven from raw2
M9irqtimeout not restarting on arrival4
M10irqtimeout running while the queue is empty2
M11dmareceive request ANDing trigger and timeout5
M12dmatransmit request ignoring its enable3

Each mutation was verified to have actually modified the source before its result was scored — a patch that silently matches nothing produces a "surviving mutant" that is really the original design, and it is the easiest way to award yourself a grade you did not earn.

9. Verification

Test the register block through the bridge, not only directly. The bridge is where outstanding transactions, response ordering and strobe handling live, and a peripheral verified only against a hand-written APB master has not seen any of it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — one side effect per committed transfer, whatever the bus
// above does. Stated at the peripheral's own boundary so it holds
// regardless of how transactions arrive.
property p_one_pop_per_transfer;
    @(posedge clk) disable iff (!rst_n)
        rd_ready |=> !rd_ready || (psel_i && penable_i && !pwrite_i);
endproperty

// Assertion — every accepted transfer completes. A peripheral that can
// stall forever wedges the whole interconnect, not just itself.
property p_always_completes;
    @(posedge clk) disable iff (!rst_n)
        psel_i |-> ##[0:$] pready_o;
endproperty

Run the protocol checker the bus vendor supplies. It is free, it is more thorough than a hand-written test, and it checks the rules a peripheral is most likely to get subtly wrong.

Test at more than one clock frequency. §5's sweep is three lines of testbench and it catches a divisor derived from the wrong parameter — a failure that looks like a hardware bug and is a configuration one.

Test what happens when an undecoded address is accessed. This block returns zero and asserts no error, which makes probing harmless and a typo silent. Either choice is defensible; the test exists to pin down which one shipped.

10. Debugging

11. What This Means on an FPGA

Use the vendor's bridge. Xilinx and Intel both ship AXI-to-APB and AXI-Lite adapters, verified and free. A hand-written one is a source of exactly the bugs that are hardest to find.

AXI-Lite is often simpler than APB to attach to on an FPGA, because the interconnect generator produces it directly. If the tooling makes AXI-Lite easier, a thin AXI-Lite front end on the same register block is reasonable — the register block's internals do not change, which is the property worth preserving.

Keep the UART on the fabric clock. §5 shows it works at any plausible frequency, and it removes a clock domain from the design.

Watch the address decode width. This block decodes eight bits of address, which gives 64 word-aligned registers. That is generous for eleven and it is worth leaving generous — renumbering a shipped map breaks every driver.

12. Understanding Check

13. Summary

The UART promises a side effect per committed transfer; the system promises that a completed transfer was actually wanted. Neither promise is useful alone, and only the first is in the RTL.

APB is the easy case because it defines commitment precisely and offers no bursts, speculation or reordering — which is why it was chosen to state the rule in.

An AXI-class attachment changes three things: commitment moves to the data handshake, a burst to DATA pops per beat by design, and byte strobes exist. The real hazard is the caches, prefetchers and speculative masters an AXI-class system contains, none of which the peripheral can defend against.

Therefore the integration guide must demand device, non-cacheable, non-prefetchable, non-speculative mapping. It is the most important sentence a side-effecting peripheral writes, and its absence produces data loss triggered by an unrelated system change.

Bridge rather than rewrite. The bridge is generic and verified, and no plausible UART saturates an APB link — 86.8 µs per byte at 115,200 baud.

One clock is almost always the right answer, measured across three bus frequencies with one parameter changed and no CDC anywhere. The exceptions are a gated bus clock, a clock too slow to build, and — the dangerous one — a clock that varies at run time under a divisor fixed at elaboration.

And the requirement that outlives this chapter: everything a peripheral needs from its system has to be written down, because none of it can be enforced from inside the peripheral.

14. What Comes Next

Module 13 is complete, and so is the peripheral. The UART has been designed, integrated, parameterised, made testable, synchronised, reset, constrained, targeted at a technology, given a register map, interrupts, DMA hooks and a bus attachment. A driver can be written against it and it would survive review.

What has not been built is the environment that proves it. Every result in thirteen modules came from directed testbenches — written by hand, checking what their author thought to check, and finding defects mostly when a new consumer arrived with a new access pattern (Chapter 13.4 §5 being the clearest example).

Modules 14 through 16 replace that with a real verification environment: constrained-random stimulus, functional coverage that says what has actually been exercised, scoreboards that check independently of the design, and the UVM structure that holds it together. The UART is the device under test, and the question stops being does it work and becomes how would we know.

Browse the full path on the UART tutorials index. For the access contract this chapter turns into an integration requirement, read back to Chapter 13.1.

Continue learning

Where this fits

Part of the UART curriculum.