UART · Module 16
Sequence Item and Sequence Library
Modelling a UART frame as a transaction, the constraints that keep a random frame legal and the soft ones that let a sequence ask for an illegal frame on purpose, and the distribution that puts stimulus where the failures are.
A sequence item is a description of what should happen, not of what the wires do. Getting that boundary right is most of the work: a transaction that carries tx_o has become a driver, and a transaction that carries a bit period in clock cycles has welded itself to one clock frequency.
This chapter models a UART frame as a transaction, constrains it so a random one is legal, and then — deliberately — provides the mechanism to make it illegal.
1. The Transaction Is the Task's Arguments
Module 14's BFM had this signature:
task automatic send_frame(input logic [8:0] data,
input int nbits,
input int parity_mode,
input int stop_halves,
input real tbit_ns,
input int err);Six arguments. A uvm_sequence_item is those six arguments in a class, plus the ability to randomise and print them:
// ---------------------------------------------------------------------------
// uart_frame_item — one UART frame, described the way a TEST thinks about it
//
// Every field is a parameter of the FRAME, not a signal on the wire. There
// is no tx_o here, no baud tick and no clock: a transaction says what should
// appear on the line, and the driver decides how to produce it.
//
// That boundary is what lets the same item be driven at 9,600 baud on one
// test and 3 Mbaud on another without touching the class.
// ---------------------------------------------------------------------------
class uart_frame_item extends uvm_sequence_item;
rand bit [8:0] data;
rand int unsigned nbits; // 5..9 data bits
rand int unsigned parity_mode; // 0 none, 1 even, 2 odd, 3 mark
rand int unsigned stop_halves; // 2 = 1 bit, 3 = 1.5, 4 = 2
rand real tbit_ns; // THE argument that makes the BFM useful
rand int unsigned err; // ERR_NONE .. ERR_BREAK
// Observed-only: filled in by the monitor, never randomised.
bit parity_err;
bit frame_err;
`uvm_object_utils_begin(uart_frame_item)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (nbits, UVM_ALL_ON)
`uvm_field_int (parity_mode, UVM_ALL_ON)
`uvm_field_int (stop_halves, UVM_ALL_ON)
`uvm_field_real(tbit_ns, UVM_ALL_ON)
`uvm_field_int (err, UVM_ALL_ON)
`uvm_field_int (parity_err, UVM_ALL_ON | UVM_NOCOMPARE)
`uvm_field_int (frame_err, UVM_ALL_ON | UVM_NOCOMPARE)
`uvm_object_utils_end
function new(string name = "uart_frame_item");
super.new(name);
endfunction
endclass2. Constraints: Legal by Default
An unconstrained randomize() on that class produces nbits = 2_147_483_621 and a tbit_ns of whatever the random number generator felt like. Every random frame must be legal unless a test asks otherwise.
// ---- the frame must be a frame -----------------------------------
constraint c_format {
nbits inside {[5:9]};
parity_mode inside {[0:3]};
stop_halves inside {[2:4]};
}
// ---- the payload must fit the width ------------------------------
// Written as a relation, not as `data < 512`. A 5-bit frame carrying
// 0x1FF is not a legal frame with an unusual value -- it is a request
// the driver cannot honour, and it will silently truncate.
constraint c_payload { data < (1 << nbits); }
// ---- and it must be sent at a rate the receiver can follow --------
constraint c_baud_legal {
soft tbit_ns inside {[8160.0 : 9200.0]}; // about +/-6% of nominal
}3. Distribution: Put the Stimulus Where the Failures Are
Uniform randomisation spends its time in the middle of every range, and the middle is where nothing happens.
// Clean traffic must dominate, or the error cases stop being corners
// and the environment is no longer testing normal operation at all.
constraint c_err_mix {
err dist { 0 := 70, [1:5] := 30 };
}
// Baud error clusters at the EDGE of the tolerance window, because that
// is where behaviour changes. Chapter 15.5 measured the edge at +/-6%
// against a prediction of 5.88%; uniform randomisation across +/-10%
// would spend most of its samples nowhere near it.
constraint c_baud_shape {
soft tbit_ns dist {
8680.5556 := 40, // nominal
[8160.0 : 8420.0] := 15, // about -5%
[8940.0 : 9200.0] := 15, // about +5%
[7810.0 : 8160.0] := 5, // beyond the edge
[9200.0 : 9550.0] := 5
};
}
// Data values that are corners for a reason, not because they look odd.
// 0x00 is the longest run of zeros and produced the false break of
// Chapter 11.4; 0xFF is the longest run of ones.
constraint c_data_corners {
soft data dist { 9'h000 := 10, 9'h0FF := 10, [1:254] := 80 };
}Writing that distribution by hand in a directed loop is possible and nobody does it. This is the one place where UVM's machinery genuinely changes what gets tested rather than how it is organised.
4. The Sequence Library
A sequence is a for loop that can be started on any sequencer, nested inside another, and randomised.
// Every byte value at the nominal rate -- the coverage floor.
class uart_all_bytes_seq extends uvm_sequence #(uart_frame_item);
`uvm_object_utils(uart_all_bytes_seq)
function new(string name = "uart_all_bytes_seq"); super.new(name); endfunction
task body();
for (int d = 0; d < 256; d++)
`uvm_do_with(req, { data == d; nbits == 8; parity_mode == 0;
stop_halves == 2; err == 0;
tbit_ns == 8680.5556; })
endtask
endclass
// The full format cross of Chapter 15.3 -- 60 bins, four frames each.
class uart_format_cross_seq extends uvm_sequence #(uart_frame_item);
`uvm_object_utils(uart_format_cross_seq)
function new(string name = "uart_format_cross_seq"); super.new(name); endfunction
task body();
for (int nb = 5; nb <= 9; nb++)
for (int pm = 0; pm <= 3; pm++)
for (int sh = 2; sh <= 4; sh++)
repeat (4)
`uvm_do_with(req, { nbits == nb; parity_mode == pm;
stop_halves == sh; err == 0; })
endtask
endclass
// Each injected fault in turn -- Chapter 15.4's detection matrix.
class uart_fault_sweep_seq extends uvm_sequence #(uart_frame_item);
`uvm_object_utils(uart_fault_sweep_seq)
function new(string name = "uart_fault_sweep_seq"); super.new(name); endfunction
task body();
for (int e = 0; e <= 5; e++)
repeat (8)
`uvm_do_with(req, { err == e; nbits == 8; parity_mode == 1; })
endtask
endclass
// And a virtual sequence: two of the above on the same sequencer, interleaved.
class uart_mixed_vseq extends uvm_sequence #(uart_frame_item);
`uvm_object_utils(uart_mixed_vseq)
function new(string name = "uart_mixed_vseq"); super.new(name); endfunction
task body();
uart_format_cross_seq cross_seq;
uart_fault_sweep_seq fault_seq;
cross_seq = uart_format_cross_seq::type_id::create("cross_seq");
fault_seq = uart_fault_sweep_seq ::type_id::create("fault_seq");
fork
cross_seq.start(m_sequencer);
fault_seq.start(m_sequencer);
join
endtask
endclass5. Verification
Randomise legally by default. An unconstrained randomize() on this class produces a nbits in the billions. Every field that has a legal range must have a hard constraint stating it.
Relate the payload to the width. data < (1 << nbits), not data < 512. A 5-bit frame carrying 0x1FF is not an unusual value; it is a request the driver will silently truncate.
Use soft for defaults a test may need to break, and hard for statements about what the transaction is. The test: would violating it produce a frame, or produce nothing?
Exclude observed-only fields from compare(). parity_err and frame_err are filled in by the monitor; comparing them against an unfilled expected item fails on every correctly-detected error.
Shape the distribution towards the edges. Uniform randomisation across a tolerance window spends most of its samples where nothing happens; Chapter 15.5 measured the interesting region at ±6%.
And check randomize()'s return value. A failed randomisation with unchecked status leaves the item at its previous values and drives the same frame repeatedly — a suite that appears to run and tests one thing.
6. Debugging
7. Understanding Check
8. Summary
A sequence item is the arguments of Module 14's send_frame task, in a class. Every field describes the frame; none describes a wire.
Hard constraints state what the transaction is; soft constraints are defaults a test may override. The test is whether violating it produces a frame or produces nothing.
The payload is constrained relative to the width, so no randomised item can request bits the format cannot carry.
Observed-only fields are excluded from compare(), or every correctly-detected error reads as a mismatch.
The distribution is weighted towards the edges — 70/30 clean to faulty, and baud clustered at the ±6% boundary rather than spread uniformly across ±10%.
And a virtual sequence running two sequences on one sequencer is why a sequencer exists — the directed environment of Module 14 could not have expressed it at all.
9. What Comes Next
Chapter 16.3 takes a transaction and puts it on a wire — and builds the sequencer that decides whose transaction goes next, as running code in three languages.
Browse the full path on the UART tutorials index. For the task whose arguments became this class, read back to Chapter 14.2.
Continue learning
Related tutorials
- Related topic
Synthesis and Timing Constraints
Constraining an input that has no real setup requirement, and the two structures a tool will silently build from ambiguous RTL — both demonstrated in simulation.
- Related topic
UART UVM Environment Architecture
How agent, scoreboard, reference model, coverage and configuration fit together for a serial asynchronous interface, what each UVM class replaces from the directed environment, and what the methodology does not change.
- Related topic
The Driver and the Bit-Timing Problem
Driving a transaction onto a wire with no clock to synchronise to, and the sequencer that decides whose transaction goes next — built as running code in three languages, with both halves of its contract measured.
- Related topic
Monitor, Agent and Active/Passive Reuse
Recovering frames independently of the driver, the analysis port that broadcasts without coupling — built as running code with its dangerous property measured — and the flag that makes one agent serve two testbenches.
Where this fits
Part of the UART curriculum.
