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 provides | The interconnect provides |
|---|---|
| a register block with a defined access contract | address decode down to this peripheral |
| completion of every transfer it accepts | ordering between masters |
| status that is true when read | the memory type of the region |
| side effects tied to a committed transfer | not 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:
| Property | Why it helps |
|---|---|
| one transfer at a time | no outstanding reads to reason about |
| explicit setup and access phases | the committed cycle is unambiguous |
| no bursts | a transfer is one access to one address |
| no speculation | a transfer happened because a master issued it |
| no reordering | reads and writes arrive in program order |
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.
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).
-- 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 neededThree 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:
| Requirement | Why |
|---|---|
| region is Device / strongly-ordered, non-cacheable, non-prefetchable | DATA has a read side effect — §3 |
| accesses are word-aligned and word-sized | byte strobes are ignored — §3 |
a burst read of DATA returns N different bytes | it is N pops, by design |
| reading any other register is free of side effects | debuggers and pollers depend on it |
CLK_HZ must match the actual clock frequency | the baud divisor is derived from it |
| the interrupt output is a level | Chapter 13.3 |
| the DMA requests are levels | Chapter 13.4 §11 |
| trigger level should match the DMA burst size | Chapter 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.
// ---------------------------------------------------------------------------
// 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
endclassThe 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:
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
endclassuvm_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:
Nine testbenches, identical check counts across all three languages:
| Suite | Checks | Verilog-2001 | SystemVerilog | VHDL-2008 |
|---|---|---|---|---|
uart_regs | 40 | 40 / 0 | 40 / 0 | 40 / 0 |
uart_irq | 24 | 24 / 0 | 24 / 0 | 24 / 0 |
uart_dma_if | 18 | 18 / 0 | 18 / 0 | 18 / 0 |
| Total per language | 82 | 82 / 0 | 82 / 0 | 82 / 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:
| # | Block | Defect installed | Killed by |
|---|---|---|---|
| M1 | regs | side effect qualified on psel alone | 14 checks |
| M2 | regs | pop not qualified on the address | 4 |
| M3 | regs | ERR made clear-dominant | 1 |
| M4 | regs | flush bits held as levels | 2 |
| M5 | regs | W1C clearing every flag | 1 |
| M6 | regs | valid bit tied high | 1 |
| M7 | irq | IRQ_STAT ignoring the enable mask | 4 |
| M8 | irq | irq_o driven from raw | 2 |
| M9 | irq | timeout not restarting on arrival | 4 |
| M10 | irq | timeout running while the queue is empty | 2 |
| M11 | dma | receive request ANDing trigger and timeout | 5 |
| M12 | dma | transmit request ignoring its enable | 3 |
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.
// 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;
endpropertyRun 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
Related tutorials
- Related topic
UART as a Memory-Mapped Peripheral
The register block between a bus and the UART core, and the read and write side effects that make UART registers unlike memory — with a measured demonstration of getting one of them wrong.
- Related topic
Typical APB SoC Architecture
Where APB sits in a real SoC — downstream of the AXI/AHB bridge, fanning out to peripherals through a one-hot decode and return mux.
- Related topic
SoC to SPI Peripheral Through a Register Bus
Software becomes part of the design. A configuration shadow, two sticky status bits and a toggle handshake — the three races that ship, measured at four unrelated clock ratios.
- Related topic
The Ethernet MAC as an SoC IP Block
A MAC integrated into an SoC demands 1.143 times its line rate in memory bandwidth, crosses four clock domains, and at 100 Gb/s asks for 1.79 bus transactions per cycle.
Where this fits
Part of the UART curriculum.
