apb code
apb code
TESTBENCH CODE
APB (Advanced Peripheral Bus):
The AMBA APB (Advanced Peripheral Bus) protocol is a simple, low-power, low-bandwidth interface
protocol mainly used to connect peripherals (like UART, timers, GPIO) to a system bus. APB is
designed to be simple, with minimal power consumption and reduced interface complexity. It uses a
straightforward address, control, and data interface, where data transfer typically happens in two
phases: SETUP and ENABLE. During the SETUP phase, address and control signals are provided and
stable, while during the ENABLE phase, the actual data transfer occurs. APB is not pipelined like
other AMBA buses; it supports only one transfer at a time, making it easier to design.
In the given code, the important APB signals defined inside the interface are as follows:
pclk is the clock signal that synchronizes all the APB activities.
rst_n is the active-low reset signal, which resets the slave device when asserted low.
paddr is the address bus, carrying the address of the peripheral register being
accessed.
psel (Peripheral Select) is used to select the slave device; it indicates that a valid
transfer is requested.
penable indicates the second phase of the transfer (ENABLE phase) and confirms
that the transfer is active.
pwrite is the write control signal; when high, it indicates a write operation;
otherwise, it is a read.
pwdata carries the data from the master to the slave during write operations.
pready is driven by the slave to signal that it is ready to complete the transfer.
prdata carries the data from the slave to the master during read operations.
In the design part, an interface named dut_if encapsulates all these APB signals and defines
three clocking blocks: master_cb for the driver side, slave_cb for the responder side, and
monitor_cb for the passive monitoring side. Modports are defined to control access to these
clocking blocks by different components. The apb_slave module represents a simple APB
slave device that connects to this interface. Inside the slave, a 256-location memory array
(mem) is defined to simulate register storage. The APB slave works based on a state machine
with three states: SETUP, W_ENABLE, and R_ENABLE. During reset, the memory is initialized
with incremental values. In the SETUP state, if psel is high and penable is low, the slave
checks whether it is a read or write operation. For write operations, it transitions to W_ENABLE,
and for read operations, it immediately drives prdata with the memory content
corresponding to paddr and moves to R_ENABLE. In the W_ENABLE state, if the signals confirm
a write operation (psel, penable, and pwrite all active), the slave writes pwdata into the
addressed memory location and then returns to the SETUP state. The R_ENABLE state simply
transitions back to SETUP after serving the read. The slave is always ready ( pready is
permanently high) in this simple model. Thus, the design models a basic APB-compliant
memory-mapped peripheral with read and write capabilities.
DESIGN CODE :
interface dut_if;
logic pclk;
logic rst_n;
logic psel;
logic penable;
logic pwrite;
logic pready;
input prdata;
endclocking: master_cb
output prdata;
endclocking: slave_cb
endclocking: monitor_cb
modport master(clocking master_cb);
endinterface
if (dif.rst_n==0) begin
apb_st <=0;
dif.prdata <=0;
dif.pready <=1;
end
else begin
case (apb_st)
SETUP: begin
dif.prdata <= 0;
if (dif.pwrite) begin
else begin
end
end
end
W_ENABLE: begin
end
end
R_ENABLE: begin
end
endcase
end
end
/*
begin
end
*/
Endmodule
The given code defines a SystemVerilog class apb_transaction that extends
uvm_sequence_item, which is the basic unit of data transfer in UVM (Universal Verification
Methodology). This class models a single APB bus transaction (either a read or a write)
and is used for stimulus generation during verification.
The typedef enum {READ, WRITE} oprtn; defines an enumerated type called oprtn that
represents the type of APB operation — either a READ or WRITE command. The class has
three rand variables:
addr — the 32-bit randomizable address where the APB transaction will occur.
data — the 32-bit randomizable data for either writing to or reading from the slave.
pwrite — the transaction type, randomly chosen as either READ or WRITE using
the defined enum.
The static variable pckt_id is used to uniquely identify packets (transactions), and since it is
static, it is shared across all instances of the class.
The new function is a constructor that initializes the object with a name, defaulting to
"apb_transaction" if no name is provided.
c1 constrains the address (addr) to be between 0 and 255 (only valid memory
locations).
c2 constrains the data (data) to also be between 0 and 255, ensuring the data is within
the 8-bit range, similar to a byte.
`uvm_object_utils_begin(apb_transaction)
`uvm_field_int(addr,UVM_ALL_ON)
`uvm_field_int(data,UVM_ALL_ON)
`uvm_field_int(pckt_id,UVM_ALL_ON)
`uvm_field_enum(oprtn,pwrite,UVM_ALL_ON)
`uvm_object_utils_end
super.new(name);
endfunction
endclass
The given code defines a UVM sequence class called apb_sequence, which extends
uvm_sequence parameterized with apb_transaction. In UVM, sequences are used to
generate stimulus — here, the sequence is creating and sending multiple APB read and write
transactions to a driver.
First, the macro uvm_object_utils(apb_sequence) registers the class with the UVM
factory, enabling features like create() and automation for reporting, recording, etc.
The new function is a constructor that initializes the sequence object with a default name
"apb_sequence".
The main logic happens inside the body task, which UVM automatically calls when the
sequence starts execution:
Thus, for each iteration, one write transaction and one read transaction are generated
back-to-back. This structure ensures that every address written with some data is immediately
read back in the sequence, enabling easy checking of data consistency later. This
apb_sequence is a simple generator of predictable APB read/write traffic for testing the
APB slave device.
SEQUENCE CODE:
class apb_sequence extends uvm_sequence#(apb_transaction);
`uvm_object_utils(apb_sequence)
int no_of_pckts_gen;
super.new(name);
endfunction
task body();
apb_transaction rw_tr;
repeat(no_of_pckts) begin
// repeat(3) begin
rw_tr.pckt_id = rw_tr.pckt_id+1;
`uvm_do_with(rw_tr,{rw_tr.pwrite == 0;});
rw_tr.pckt_id = rw_tr.pckt_id+1;
//end
end
endtask
endclass
The given code defines a UVM sequencer class called apb_sequencer, which extends
uvm_sequencer parameterized with apb_transaction. In UVM, a sequencer acts as a
controller that manages sequences and sends sequence items (transactions) to the driver in
an orderly manner.
new constructor: It initializes the sequencer object, passing its name and its parent
component (typically an agent) to the base class constructor using super.new(name,
parent). The parent connects this sequencer properly into the UVM hierarchy.
build_phase: It overrides the UVM build_phase function. Here, it simply calls
super.build_phase(phase), which is mandatory to ensure proper base class
initialization, but it leaves space for adding any custom build-phase logic if needed in
the future.
SEQUENCER CODE :
class apb_sequencer extends uvm_sequencer#(apb_transaction);
`uvm_component_utils(apb_sequencer)
super.new(name,parent);
endfunction
super.build_phase(phase);
endfunction: build_phase
endclass
The given code defines a UVM driver class named apb_driver, which extends uvm_driver
parameterized with apb_transaction. In UVM, the driver’s job is to take transactions
from the sequencer and drive them onto the DUT interface (DUT_IF) according to the
protocol timing.
First, the macro uvm_component_utils(apb_driver) registers the driver with the UVM
factory for dynamic creation and factory overrides.
The class contains a handle vif to the virtual interface dut_if, through which the driver
drives APB signals.
new constructor: Initializes the driver by calling the base class constructor with the
provided name and parent.
build_phase:
o Calls super.build_phase(phase) to ensure proper base class setup.
o Then it fetches the virtual interface dut_if from the UVM configuration
database.
o If the vif is not set properly, an error is reported.
run_phase:
o Called automatically when the simulation enters the run phase.
o Initially, it sets psel and penable signals to 0 (idle condition).
o Then it enters a forever loop where it continuously performs the following:
Waits for a clocking block event (@(this.vif.master_cb)).
Retrieves a transaction tr from the sequencer using
seq_item_port.get_next_item(tr).
Prints the transaction details.
Waits for another clock edge to ensure setup timing.
Based on the transaction type (pwrite value):
If it is a read operation, it calls drive_read.
If it is a write operation, it calls drive_write.
After driving the transaction onto the bus, it notifies the sequencer that
the transaction is done using seq_item_port.item_done().
drive_read task:
o Drives a read transaction onto the APB bus.
o Sets paddr, sets pwrite to 0, asserts psel, waits for clock, asserts penable,
and finally samples prdata after another clock cycle.
o After completing the read, de-asserts psel and penable.
drive_write task:
o Drives a write transaction onto the APB bus.
o Sets paddr and pwdata, sets pwrite to 1, asserts psel, waits for clock, asserts
penable, and drives the data to the slave.
o After completing the write, de-asserts psel and penable.
In summary, the apb_driver takes apb_transaction items from the sequencer, drives
them correctly following APB protocol timing using the virtual interface, and
coordinates handshakes with the sequencer. It models a master-side APB driver that can
handle both read and write operations properly.
DRIVER CODE:
class apb_driver extends uvm_driver#(apb_transaction);
`uvm_component_utils(apb_driver)
super.new(name,parent);
endfunction
super.build_phase(phase);
end
endfunction
super.run_phase(phase);
this.vif.master_cb.psel <= 0;
this.vif.master_cb.penable <= 0;
forever begin
apb_transaction tr; //we can use instead of this default handle (i.e., req)
@ (this.vif.master_cb);
seq_item_port.get_next_item(tr);
tr.print();
@ (this.vif.master_cb);
//command to call either the read/write function
case (tr.pwrite)
endcase
seq_item_port.item_done();
end
endtask
virtual protected task drive_read(input bit [31:0] addr, output logic [31:0] data);
this.vif.master_cb.pwrite <= 0;
this.vif.master_cb.psel <= 1;
@ (this.vif.master_cb);
this.vif.master_cb.penable <= 1;
@ (this.vif.master_cb);
data = this.vif.master_cb.prdata;
this.vif.master_cb.psel <= 0;
this.vif.master_cb.penable <= 0;
endtask
virtual protected task drive_write(input bit [31:0] addr, input bit [31:0] data);
this.vif.master_cb.pwrite <= 1;
this.vif.master_cb.psel <= 1;
@ (this.vif.master_cb);
this.vif.master_cb.penable <= 1;
@ (this.vif.master_cb);
this.vif.master_cb.psel <= 0;
this.vif.master_cb.penable <= 0;
endtask
endclass
This code defines a UVM monitor class named apb_monitor for the APB protocol.
In UVM, a monitor passively observes DUT signals without driving them and sends observed
transactions to other components (like scoreboard, coverage collector) using an analysis port.
Key Points:
Constructor new
build_phase
run_phase
MONITOR CODE:
class apb_monitor extends uvm_monitor;
uvm_analysis_port#(apb_transaction) ap;
`uvm_component_utils(apb_monitor)
super.new(name,parent);
ap = new("ap",this);
endfunction
end
endfunction: build_phase
super.run_phase(phase);
forever begin
apb_transaction tr;
do begin
@ (this.vif.monitor_cb);
end
tr = apb_transaction::type_id::create("tr",this);
tr.addr = this.vif.monitor_cb.paddr;
@ (this.vif.monitor_cb);
end
tr.data = this.vif.monitor_cb.prdata;
end
tr.data = this.vif.monitor_cb.pwdata;
end
ap.write(tr);
end
endtask
endclass
AGENT CODE :
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
//Agent will have the sequencer, driver and monitor components for the APB interface
apb_sequencer sqr;
apb_driver drv;
apb_monitor mon;
// `uvm_component_utils_begin(apb_agent)
// `uvm_field_object(sqr, UVM_ALL_ON)
// `uvm_field_object(drv, UVM_ALL_ON)
// `uvm_field_object(mon, UVM_ALL_ON)
// `uvm_component_utils_end
super.new(name,parent);
endfunction
end
endfunction
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass
ENV CODE:
class apb_env extends uvm_env;
`uvm_component_utils(apb_env);
apb_agent agt;
apb_scoreboard scb;
super.new(name, parent);
endfunction
//Build phase - Construct agent and get virtual interface handle from test and pass it down to agent
super.build_phase(phase);
`uvm_fatal("build phase", "No virtual interface specified for this env instance")
end
endfunction
super.connect_phase(phase);
agt.mon.ap.connect(scb.mon_export);
endfunction
endclass
SCOREBOARD CODE:
class apb_scoreboard extends uvm_scoreboard;
`uvm_component_utils(apb_scoreboard)
uvm_analysis_imp#(apb_transaction, apb_scoreboard) mon_export;
apb_transaction exp_queue[$];
super.new(name,parent);
endfunction
super.build_phase(phase);
foreach(sc_mem[i]) sc_mem[i] = i;
endfunction
// write task - recives the pkt from monitor and pushes into queue
//tr.print();
exp_queue.push_back(tr);
endfunction
//super.run_phase(phase);
apb_transaction expdata;
forever begin
expdata = exp_queue.pop_front();
if(expdata.pwrite == apb_transaction::WRITE) begin
sc_mem[expdata.addr] = expdata.data;
`uvm_info("",$sformatf("Addr: %0h",expdata.addr),UVM_LOW)
`uvm_info("",$sformatf("Data: %0h",expdata.data),UVM_LOW)
end
`uvm_info("",$sformatf("Addr: %0h",expdata.addr),UVM_LOW)
end
else begin
`uvm_info("",$sformatf("Addr: %0h",expdata.addr),UVM_LOW)
end
end
end
endtask
endclass
TEST CODE:
class apb_test extends uvm_test;
`uvm_component_utils(apb_test)
apb_env env;
apb_sequence apb_seq;
super.new(name,parent);
endfunction
super.build_phase(phase);
end
endfunction
super.run_phase(phase);
apb_seq = apb_sequence::type_id::create("apb_seq");
apb_seq.start(env.agt.sqr);
// #100ns;
phase.drop_objection( this , "Finished apb_seq in main phase" );
endtask
endclass
TOP FILE:
// or browse Examples
`include "uvm_macros.svh"
module top;
`include "apb_transaction.sv"
`include "apb_sequence.sv"
`include "apb_sequencer.sv"
`include "apb_driver.sv"
`include "apb_monitor.sv"
`include "apb_agent.sv"
`include "apb_scoreboard.sv"
`include "apb_env.sv"
`include "apb_test.sv"
logic pclk;
logic rst_n;
logic psel;
logic penable;
logic pwrite;
apb_slave dut(.dif(apb_if));
initial begin
apb_if.pclk=0;
end
//Generate a clock
always begin
end
initial begin
apb_if.rst_n=0;
apb_if.rst_n=1;
end
initial begin
end
initial begin
run_test();
end
endmodule
Conclusion of APB Protocol:
APB (Advanced Peripheral Bus) is a simple, low-power, and low-complexity bus protocol
mainly used to connect peripheral devices in a system-on-chip (SoC) environment. It is
optimized for minimal power consumption and straightforward control, especially where high
performance is not critical. It operates with a simple two-phase transfer (SETUP and
ENABLE) without requiring complex handshakes or bursts, making it highly efficient for
register-level communications.