AMBA AHB 2.0 VIP in SystemVerilog UVM: Bursts, Constraints, Coverage and Tests
AHB is where AMBA stops being a toy. Unlike APB, an AHB transfer is pipelined, comes in bursts of up to sixteen beats, wraps at power-of-two boundaries, can be stalled by the target and interrupted by the initiator, and may be answered with four different responses. A verification IP for it has to generate legal traffic under all of those rules, which makes it a good case study in constrained-random transaction modelling. This post walks through the AHB 2.0 UVM VIP on GitHub: what it supports, how the transaction constraints encode the protocol, what the sequence library and test list look like, what the coverage model measures, and one real bug in the constraints that has been sitting in the repository since 2015. If you want the gentler introduction to the same testbench structure, start with the APB VIP walkthrough.
AHB 2.0, AHB-Lite and AHB5
The AHB family has three generations that matter, and a VIP written for one is not automatically usable on another.
| Version | Spec | What it has |
|---|---|---|
| AHB 2.0 | AMBA 2 (IHI 0011) | Multiple masters with an arbiter (HBUSREQ, HGRANT), SPLIT and RETRY responses, 2-bit HRESP |
| AHB-Lite | AMBA 3 (IHI 0033) | Single master, no arbitration, only OKAY and ERROR, 1-bit HRESP |
| AHB5 | AMBA 5 (IHI 0033B) | Adds exclusive transfers (HEXCL, HEXOKAY), extended memory types, secure transfers (HNONSEC), multiple-layer support |
This VIP targets AHB 2.0 in a single-master, single-slave configuration, so it exercises the transfer pipeline, bursts, wait states and responses, but not arbitration between masters. That is the useful subset for most block-level work, and it is also exactly the subset that AHB-Lite kept.
What the VIP supports
- Single master and single slave, with a separate reset agent.
- All eight burst types:
SINGLE,INCR,WRAP4,INCR4,WRAP8,INCR8,WRAP16,INCR16. - Transfer sizes from byte to word, with addresses aligned to the size.
BUSYcycles inserted by the master mid-burst andHREADYwait states inserted by the slave.OKAYandERRORresponses, withRETRYandSPLITdefined in the types but not driven.- Functional coverage on burst type, size, direction, response and their crosses.
ahb2_uvm_tb/
├── ahb_env/ ahb_env, env_config, ahb_coverage, ahb_vseqr, ahb_vseqs, top.sv
├── ahb_master_agent/ agent, config, driver, monitor, sequencer, sequence library, ahb_mxtn
├── ahb_slave_agent/ agent, config, driver, monitor, sequencer, sequences, ahb_sxtn
├── ahb_test/ base, reset, crt, incrx, wrapx, incrbusy, err tests, tb_defs.svh
├── reset_agent/ agent, driver, sequencer, sequences
├── rtl/ ahb_intf.sv
└── sim/ Makefile, run.pl, ahb_inc.f, testcases.txt, wave and radix scripts
Signals and transfer types
| Signal | Width | Description |
|---|---|---|
HCLK, HRESETn | 1 | Bus clock and active-low reset |
HADDR | 32 | Address |
HTRANS | 2 | Transfer type: IDLE, BUSY, NONSEQ, SEQ |
HWRITE | 1 | Direction |
HSIZE | 3 | Transfer size, BYTE to 1024-bit |
HBURST | 3 | Burst type |
HWDATA, HRDATA | 32 | Write and read data |
HREADY | 1 | Slave ready; low inserts a wait state |
HRESP | 2 | OKAY, ERROR, RETRY, SPLIT |
HTRANS is the signal that makes AHB pipelined. The first beat of a burst is NONSEQ, later beats are SEQ, the master can insert BUSY beats when it is not ready to supply or accept data, and IDLE means no transfer. Address and control for beat N are on the bus during the data phase of beat N-1. The enumerations live in tb_defs.svh and are used by transactions, drivers, monitors and coverage alike:
typedef enum bit [1:0] {IDLE, BUSY, NONSEQ, SEQ} transfer_t;
typedef enum bit {READ, WRITE} rw_t;
typedef enum bit [2:0] {SINGLE, INCR, WRAP4, INCR4, WRAP8, INCR8, WRAP16, INCR16} burst_t;
typedef enum bit [2:0] {BYTE, HALFWORD, WORD, WORDx2, WORDx4, WORDx8, WORDx16, WORDx32} size_t;
typedef enum bit [1:0] {OKAY, ERROR, RETRY, SPLIT} resp_t;
Architecture
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e0f2fe', 'primaryTextColor': '#0f172a', 'primaryBorderColor': '#0066cc', 'lineColor': '#475569', 'secondaryColor': '#f8fafc'}}}%%
flowchart TB
subgraph TEST["ahb_test"]
TC[Test cases]
VSEQ[Virtual sequences]
end
subgraph ENV["ahb_env"]
VSEQR([Virtual sequencer])
subgraph MAGT["ahb_master_agent"]
MDRV[Driver]
MMON[Monitor]
MSEQR([Sequencer])
end
subgraph SAGT["ahb_slave_agent"]
SDRV[Slave driver]
SMON[Slave monitor]
SSEQR([Sequencer])
end
COV[Coverage subscriber]
end
subgraph RAGT["reset_agent"]
RST[Reset driver]
end
IF{{ahb_intf}}
TC --> VSEQ --> VSEQR
VSEQR --> MSEQR --> MDRV
VSEQR --> SSEQR --> SDRV
MDRV -->|address, control, write data| IF
IF -->|read data, HREADY, HRESP| MDRV
SDRV <--> IF
MMON -.-> COV
RST -->|HRESETn| IF
style IF fill:#d1fae5,stroke:#10b981,stroke-width:2px
style COV fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
The master agent and slave agent each have a sequencer, and a virtual sequencer in the environment coordinates them. A test starts a virtual sequence, which starts a master sequence and a slave sequence together, so the slave's wait-state and response behaviour is stimulus in its own right rather than a fixed model. The reset agent lets tests assert HRESETn in the middle of a burst, which is what ahb_reset_test does.
The transaction: a burst is one item
The master transaction represents a whole burst, not a beat. address, trans_type, write_data and busy are dynamic arrays whose sizes are decided by the burst type, and the constraints are where the protocol lives.
class ahb_mxtn extends uvm_sequence_item;
rand bit reset;
rand transfer_t trans_type[];
rand bit [31:0] address[];
rand size_t trans_size;
rand burst_t burst_mode;
rand rw_t read_write;
rand bit [31:0] write_data[];
rand bit busy[];
rand int no_of_busy;
bit ready;
resp_t response;
bit [31:0] read_data;
// Beat count follows the burst type
constraint addr {
if (burst_mode == SINGLE) address.size == 1;
if (burst_mode == INCR) address.size < (1024 / (2 ** trans_size));
if (burst_mode == WRAP4 || burst_mode == INCR4) address.size == 4;
if (burst_mode == WRAP8 || burst_mode == INCR8) address.size == 8;
if (burst_mode == WRAP16 || burst_mode == INCR16) address.size == 16;
}
constraint min_size_limit { address.size > 0; }
// A burst must not cross a 1 KB address boundary
constraint kb_boundry {
if (burst_mode == INCR)
address[0][10:0] <= (1024 - address.size * (2 ** trans_size));
if (burst_mode == WRAP4 || burst_mode == INCR4)
address[0][10:0] <= (1024 - 4 * (2 ** trans_size));
if (burst_mode == WRAP8 || burst_mode == INCR8)
address[0][10:0] <= (1024 - 8 * (2 ** trans_size));
if (burst_mode == WRAP16 || burst_mode == INCR16)
address[0][10:0] <= (1024 - 16 * (2 ** trans_size));
}
// Every beat is aligned to the transfer size
constraint word_boundary {
if (trans_size == HALFWORD) foreach (address[i]) address[i][0] == 1'b0;
if (trans_size == WORD) foreach (address[i]) address[i][1:0] == 2'b0;
}
// ... busy placement, data array sizing
endclass
Three protocol rules are encoded here, and each is a classic AHB interview question:
- Beat count follows
HBURST. Fixed-length bursts have 4, 8 or 16 beats.INCRis unbounded by the spec, so the model bounds it by the next rule. - No burst crosses a 1 KB boundary. AHB decoders work on 1 KB granularity, so a burst that crossed the line could straddle two slaves. The constraint places the start address so that the whole burst fits below the boundary. This is the rule most hand-written stimulus gets wrong.
- Addresses are aligned to
HSIZE. A halfword transfer has bit 0 clear, a word transfer has bits 1:0 clear, and so on. Wrapping bursts wrap within the aligned block ofbeats * sizebytes, which is why alignment and the beat count interact.
The bug that has been there since 2015
The version in the repository writes the INCR bound as address.size < (1024/(2^trans_size)). In SystemVerilog ^ is XOR, not exponentiation; power is . With trans_size ranging 0 to 7, 2 ^ trans_size evaluates to 2, 3, 0, 1, 6, 7, 4, 5, so for WORD transfers (trans_size = 2) the expression divides by zero and the constraint is silently unsatisfiable or, on some solvers, dropped. The other constraints in the same file use correctly, which is how the typo survived: the fixed-length bursts, which every test exercises, were fine, and only unbounded INCR bursts of word size were affected. The listing above shows the corrected form. The lesson is the usual one: a constraint that never fails randomization is not evidence that it is right. Add a coverpoint on the thing you constrained.
Sequences and the test list
The master sequence library builds up from a base class, each sequence randomizing one burst with an inline constraint that selects the corner of interest:
class ahb_wrapx_mseq extends ahb_mbase_seq;
`uvm_object_utils(ahb_wrapx_mseq)
task body();
req = ahb_mxtn::type_id::create("req");
start_item(req);
assert(req.randomize() with {
burst_mode inside {WRAP4, WRAP8, WRAP16};
});
finish_item(req);
endtask
endclass
Tests select sequences through the virtual sequencer, and sim/testcases.txt is the regression list:
| Test | Exercises |
|---|---|
ahb_reset_test | Reset asserted mid-transfer; driver and monitor recover |
ahb_crt_test | Fully constrained-random bursts, all types and sizes |
ahb_incrx_test | INCR4, INCR8, INCR16 fixed-length increments |
ahb_wrapx_test | WRAP4, WRAP8, WRAP16 wrapping bursts |
ahb_incrbusy_test | Increments with master BUSY beats inserted |
ahb_err_test | Slave returns ERROR; master terminates the burst |
Coverage: what the VIP measures
The coverage subscriber samples every monitored master transaction. The crosses are the interesting part, because a burst type that only ever appeared with one size is a hole that a per-signal coverpoint would hide.
covergroup ahb_cg;
option.per_instance = 1;
RST: coverpoint ahb_xtn.reset;
WR: coverpoint ahb_xtn.read_write;
TRANS: coverpoint ahb_xtn.trans_type[0];
SIZE: coverpoint ahb_xtn.trans_size { bins s[] = {[BYTE:WORD]}; }
BURST: coverpoint ahb_xtn.burst_mode;
ADDR: coverpoint ahb_xtn.address[0] { option.auto_bin_max = 32; }
RESP: coverpoint ahb_xtn.response { bins rsp[] = {OKAY, ERROR}; }
RDY: coverpoint ahb_xtn.ready;
WRxSIZE: cross WR, SIZE;
BURSTxSIZE: cross BURST, SIZE;
WRxBURST: cross WR, BURST;
WRxBURSTxSIZE: cross WR, BURST, SIZE;
endgroup
What it does not measure is as informative as what it does. There is no coverpoint for the 1 KB boundary case, so the constraint bug above could never have been caught by coverage. There is no bin for RETRY or SPLIT, which is honest, since the slave never drives them. And HPROT is commented out in the transaction, so protection attributes are neither driven nor covered. A coverage model is a statement of what you claim to have verified; reading it this way is a habit worth building.
Running it
The sim directory carries a Makefile with per-test targets and a Perl runner that loops over testcases.txt, both written for QuestaSim:
cd sim
make run_test3 # one test, coverage saved and reported as HTML
perl run.pl # every test in testcases.txt, random seed each
Each target compiles with vlog, runs vsim -c -coverage -sv_seed random +UVM_TESTNAME=, and calls vcover report -html. The UVM quick start explains the switches if the flow is new to you.
What I would change today
- Fix the
^typo and add a coverpoint that binsaddress[0][10:0]near the 1 KB boundary so the fix is observable. - Add a scoreboard. The master monitor and slave monitor both reconstruct the same beats; comparing them is a twenty-line
uvm_scoreboardand makes the VIP self-checking instead of coverage-only. - Drive
RETRYandSPLITfrom the slave sequence, or delete them from the enum. A response type that exists in the types but never on the wire is a false promise to the next user. - Protocol assertions in the interface. AHB has crisp rules that are easy to assert: control signals stable while
HREADYis low,SEQonly afterNONSEQorSEQ, noBUSYon aSINGLE, and the 1 KB rule itself. An interface with assertions catches driver bugs at the source instead of in a scoreboard mismatch three beats later. - Move to AHB5 signal names if the VIP will meet modern RTL. The transfer pipeline is unchanged, so the driver and monitor survive; the interface and the response handling are what grow.
Key takeaways
- An AHB burst is one transaction, and the protocol rules become constraints: beat count from
HBURST, no 1 KB boundary crossing, alignment toHSIZE. ^is XOR in SystemVerilog. Power is**. A constraint with a typo can pass every regression for years if nothing covers the case it governs.- Master and slave are both agents with sequencers, coordinated by a virtual sequencer, so wait states and responses are stimulus you control.
- Read a coverage model for what it omits. Here: boundary cases,
RETRY,SPLITandHPROT. - AHB 2.0's single-master subset is what AHB-Lite standardized and what AHB5 extends. A VIP built on that subset ports forward with an interface change.
Source: ahb2_uvm_tb on GitHub. Protocol references: AMBA AHB Protocol Specification (Arm IHI 0033), AMBA 2 Specification (IHI 0011).
Comments (0)
Leave a Comment