0% found this document useful (0 votes)
49 views

apb code

The document outlines the design and implementation of an APB (Advanced Peripheral Bus) testbench using SystemVerilog and UVM (Universal Verification Methodology). It details the APB protocol, the structure of the testbench including the interface, slave module, transaction class, sequence, sequencer, and driver, emphasizing their roles in simulating and verifying APB transactions. The code demonstrates how to generate and manage read/write operations, ensuring proper communication between the master and slave components of the APB interface.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
49 views

apb code

The document outlines the design and implementation of an APB (Advanced Peripheral Bus) testbench using SystemVerilog and UVM (Universal Verification Methodology). It details the APB protocol, the structure of the testbench including the interface, slave module, transaction class, sequence, sequencer, and driver, emphasizing their roles in simulating and verifying APB transactions. The code demonstrates how to generate and manage read/write operations, ensuring proper communication between the master and slave components of the APB interface.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

APB UVM

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 [31:0] paddr;

logic psel;

logic penable;

logic pwrite;

logic [31:0] pwdata;

logic pready;

logic [31:0] prdata;

//Master Clocking block - used for Drivers

clocking master_cb @(posedge pclk);

output paddr, psel, penable, pwrite, pwdata;

input prdata;

endclocking: master_cb

//Slave Clocking Block - used for any Slave BFMs

clocking slave_cb @(posedge pclk);

input paddr, psel, penable, pwrite, pwdata;

output prdata;

endclocking: slave_cb

//Monitor Clocking block - For sampling by monitor components

clocking monitor_cb @(posedge pclk);

input paddr, psel, penable, pwrite, prdata, pwdata;

endclocking: monitor_cb
modport master(clocking master_cb);

modport slave(clocking slave_cb);

modport passive(clocking monitor_cb);

endinterface

module apb_slave(dut_if dif);

logic [31:0] mem [0:256];

logic [1:0] apb_st;

const logic [1:0] SETUP=0;

const logic [1:0] W_ENABLE=1;

const logic [1:0] R_ENABLE=2;

always @(posedge dif.pclk or negedge dif.rst_n) begin

if (dif.rst_n==0) begin

apb_st <=0;

dif.prdata <=0;

dif.pready <=1;

for(int i=0;i<256;i++) mem[i]=i;

end

else begin

case (apb_st)

SETUP: begin

dif.prdata <= 0;

if (dif.psel && !dif.penable) begin

if (dif.pwrite) begin

apb_st <= W_ENABLE;


end

else begin

apb_st <= R_ENABLE;

dif.prdata <= mem[dif.paddr];

end

end

end

W_ENABLE: begin

if (dif.psel && dif.penable && dif.pwrite) begin

mem[dif.paddr] <= dif.pwdata;

end

apb_st <= SETUP;

end

R_ENABLE: begin

apb_st <= SETUP;

end

endcase

end

end

/*

always @(posedge dif.clock)

begin

`uvm_info("", $sformatf("DUT received cmd=%b, addr=%d, data=%d",

dif.cmd, dif.addr, dif.data), UVM_MEDIUM)

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 macros uvm_object_utils_begin and uvm_object_utils_end register the class with


the UVM factory, making it usable for UVM automation features like create(), printing,
recording, copying, etc. Inside these macros:

 addr, data, and pckt_id are registered as integer fields.


 pwrite is registered as an enum field. The UVM_ALL_ON flag ensures that all
operations (copy, compare, print, pack, etc.) are turned on for these fields.

The new function is a constructor that initializes the object with a name, defaulting to
"apb_transaction" if no name is provided.

There are two constraints defined:

 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.

Thus, this apb_transaction class acts as a self-contained, constrained-random


transaction item that can generate various valid APB read/write operations for driving into
the APB interface during simulation.
SEQUENCE ITEM CODE:
class apb_transaction extends uvm_sequence_item;

//typedef for READ/Write transaction type

typedef enum {READ, WRITE} oprtn;

rand bit [31:0] addr; //Address

rand bit [31:0] data; //Data - For write or read response

rand oprtn pwrite; //command type

static int pckt_id;

`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

function new (string name = "apb_transaction");

super.new(name);

endfunction

constraint c1{addr[31:0]>=32'd0; addr[31:0] <32'd256;};

constraint c2{data[31:0]>=32'd0; data[31:0] <32'd256;};

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 class contains two integer variables:

 no_of_pckts — initialized to 2, specifying the number of packets (read and write


pairs) to be generated.
 no_of_pckts_gen — declared but not used in this code; could be used later to track
how many packets are actually generated.

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:

 It first declares an apb_transaction handle rw_tr.


 It uses a repeat(no_of_pckts) loop to generate the specified number of packet
pairs.
 Inside each loop iteration:
o A write transaction is initiated using the macro `uvm_do_with:
 It creates and randomizes rw_tr with constraints:
 pwrite == 1 (write operation),
 addr == data (forcing the address and data values to be the
same).
 After the write transaction is created, pckt_id is incremented by 1 to
keep a unique ID.
 An info message is printed indicating a "packet sequence."
o A read transaction is then initiated:
 It creates and randomizes rw_tr with the constraint pwrite == 0
(read operation),
 Again, pckt_id is incremented by 1.
 Another info message is printed indicating "packet in sequence."

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 =2;

int no_of_pckts_gen;

function new(string name = "apb_sequence");

super.new(name);

endfunction

task body();

apb_transaction rw_tr;

repeat(no_of_pckts) begin

// repeat(3) begin

`uvm_info("","initiating W_transaction", UVM_MEDIUM);

`uvm_do_with(rw_tr,{rw_tr.pwrite == 1; rw_tr.addr == rw_tr.data;});

rw_tr.pckt_id = rw_tr.pckt_id+1;

`uvm_info("","packet sequence", UVM_MEDIUM);

`uvm_info("","initiating R_transaction", UVM_MEDIUM);

`uvm_do_with(rw_tr,{rw_tr.pwrite == 0;});

rw_tr.pckt_id = rw_tr.pckt_id+1;

`uvm_info("","packet in sequence", UVM_MEDIUM);

//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.

First, the macro uvm_component_utils(apb_sequencer) registers the sequencer class with


the UVM factory, enabling features like dynamic creation ( create) and automation for
reporting, recording, etc.

The class has two main functions:

 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.

In short, apb_sequencer is a simple, standard UVM sequencer that enables


communication between an APB sequence (like apb_sequence) and a driver. It handles flow
control and arbitration of transactions from the sequence to the driver. Currently, it has no
extra logic because the base uvm_sequencer provides all basic functionality needed.

SEQUENCER CODE :
class apb_sequencer extends uvm_sequencer#(apb_transaction);

`uvm_component_utils(apb_sequencer)

function new(string name = "apb_sequencer",uvm_component parent);

super.new(name,parent);

endfunction

function void build_phase(uvm_phase phase);

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.

The important phases and methods are:

 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)

virtual dut_if vif;

function new(string name, uvm_component parent);

super.new(name,parent);

endfunction

function void build_phase(uvm_phase phase);

super.build_phase(phase);

if(!uvm_config_db#(virtual dut_if)::get(this,"","vif",vif)) begin

`uvm_error("build_phase","driver virtual interface failed")

end

endfunction

virtual task run_phase(uvm_phase phase);

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);

//First get an item from sequencer

seq_item_port.get_next_item(tr);

tr.print();

@ (this.vif.master_cb);
//command to call either the read/write function

case (tr.pwrite)

apb_transaction::READ: drive_read(tr.addr, tr.data);

apb_transaction::WRITE: drive_write(tr.addr, tr.data);

endcase

//Handshake DONE back to sequencer

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.paddr <= addr;

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.paddr <= addr;

this.vif.master_cb.pwdata <= 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.

Let's break it down:

Key Points:

 vif: Virtual interface handle (dut_if) to observe DUT signals.


 ap: uvm_analysis_port to broadcast observed transactions (apb_transaction) to
other components.
 uvm_component_utils(apb_monitor): Registers the monitor with the UVM factory.

Constructor new

 Calls base constructor.


 Creates an instance of ap (analysis port) using new("ap",this).

build_phase

 Fetches the virtual interface from the UVM config database.


 If vif isn't found, raises an error.

run_phase

 Main phase where monitoring happens forever.


Steps inside the forever loop:

1. Wait for SETUP phase:


o Waits until psel=1 and penable=0, which indicates the start of a valid APB
transaction (setup phase).
2. Create a new transaction:
o Creates an instance tr of apb_transaction using the factory method.
3. Capture transaction information:
o Captures pwrite signal: If 1, it's a WRITE; if 0, it's a READ.
o Captures the address from paddr.
4. Wait for ENABLE phase:
o Waits for next clock.
o Checks if penable is 1.
o If not, reports a protocol violation (because, in APB, SETUP must be immediately
followed by ENABLE).
5. Capture data:
o If it was a READ, captures prdata.
o If it was a WRITE, captures pwdata.
6. Broadcast the transaction:
o Calls ap.write(tr) to send the transaction to any component connected to the
analysis port (like scoreboard).

MONITOR CODE:
class apb_monitor extends uvm_monitor;

virtual dut_if vif;

//Analysis port -parameterized to apb_rw transaction

uvm_analysis_port#(apb_transaction) ap;

`uvm_component_utils(apb_monitor)

function new(string name = "apb_monitor", uvm_component parent);

super.new(name,parent);

ap = new("ap",this);

endfunction

function void build_phase(uvm_phase phase);


super.build_phase(phase);

if(!uvm_config_db#(virtual dut_if)::get(this,"","vif",vif)) begin

`uvm_error("build_phase", "No virtual interface specifies for this monitor instance")

end

endfunction: build_phase

virtual task run_phase(uvm_phase phase);

super.run_phase(phase);

forever begin

apb_transaction tr;

// wait for a SETUP cycle

do begin

@ (this.vif.monitor_cb);

end

while (this.vif.monitor_cb.psel !== 1'b1 ||

this.vif.monitor_cb.penable !== 1'b0); // waits until setupphase condition becomes true


(sel=0,ena=1)

tr = apb_transaction::type_id::create("tr",this);

//populate fields based on values seen on interface

tr.pwrite = (this.vif.monitor_cb.pwrite) ? apb_transaction:: WRITE : apb_transaction::READ;

tr.addr = this.vif.monitor_cb.paddr;

@ (this.vif.monitor_cb);

if (this.vif.monitor_cb.penable !== 1'b1) begin


`uvm_error("APB", "APB protocol violation: SETUP CYCLE not followed br ENABLE Cycle");

end

if(tr.pwrite == apb_transaction::READ) begin

tr.data = this.vif.monitor_cb.prdata;

end

else if(tr.pwrite == apb_transaction::WRITE) begin

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;

virtual dut_if vif;

// `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

function new(string name, uvm_component parent);

super.new(name,parent);

endfunction

//Build phase of agent - construct sequencer, driver and monitor

//get handle to virtual interface from env (parent) config_db

//and pass handle down to srq/driver/monitor

virtual function void build_phase(uvm_phase phase);

sqr = apb_sequencer::type_id::create("sqr", this);

drv = apb_driver::type_id::create("drv", this);

mon = apb_monitor::type_id::create("mon", this);

if (!uvm_config_db#(virtual dut_if)::get(this, "", "vif", vif)) begin


`uvm_fatal("build phase", "No virtual interface specified for this agent instance")

end

uvm_config_db#(virtual dut_if)::set( this, "sqr", "vif", vif);

uvm_config_db#(virtual dut_if)::set( this, "drv", "vif", vif);

uvm_config_db#(virtual dut_if)::set( this, "mon", "vif", vif);

endfunction

//Connect - driver and sequencer port to export

virtual function void connect_phase(uvm_phase phase);

drv.seq_item_port.connect(sqr.seq_item_export);

uvm_report_info("APB_AGENT", "connect_phase, Connected driver to sequencer");

endfunction

endclass

ENV CODE:
class apb_env extends uvm_env;

`uvm_component_utils(apb_env);

//ENV class will have agent as its sub component

apb_agent agt;

apb_scoreboard scb;

//vif for APB interface

virtual dut_if vif;


function new(string name, uvm_component parent);

super.new(name, parent);

endfunction

//Build phase - Construct agent and get virtual interface handle from test and pass it down to agent

function void build_phase(uvm_phase phase);

super.build_phase(phase);

agt = apb_agent::type_id::create("agt", this);

scb = apb_scoreboard::type_id::create("scb", this);

if (!uvm_config_db#(virtual dut_if)::get(this, "", "vif", vif)) begin

`uvm_fatal("build phase", "No virtual interface specified for this env instance")

end

uvm_config_db#(virtual dut_if)::set( this, "agt", "vif", vif);

endfunction

function void connect_phase(uvm_phase phase);

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[$];

bit [31:0] sc_mem [0:256];

function new(string name, uvm_component parent);

super.new(name,parent);

mon_export = new("mon_export", this);

endfunction

function void build_phase(uvm_phase phase);

super.build_phase(phase);

foreach(sc_mem[i]) sc_mem[i] = i;

endfunction

// write task - recives the pkt from monitor and pushes into queue

function void write(apb_transaction tr);

//tr.print();

exp_queue.push_back(tr);

endfunction

virtual task run_phase(uvm_phase phase);

//super.run_phase(phase);

apb_transaction expdata;

forever begin

wait(exp_queue.size() > 0);

expdata = exp_queue.pop_front();
if(expdata.pwrite == apb_transaction::WRITE) begin

sc_mem[expdata.addr] = expdata.data;

`uvm_info("APB_SCOREBOARD",$sformatf("------ :: WRITE DATA :: ------"),UVM_LOW)

`uvm_info("",$sformatf("Addr: %0h",expdata.addr),UVM_LOW)

`uvm_info("",$sformatf("Data: %0h",expdata.data),UVM_LOW)

end

else if(expdata.pwrite == apb_transaction::READ) begin

if(sc_mem[expdata.addr] == expdata.data) begin

`uvm_info("APB_SCOREBOARD",$sformatf("------ :: READ DATA Match :: ------"),UVM_LOW)

`uvm_info("",$sformatf("Addr: %0h",expdata.addr),UVM_LOW)

`uvm_info("",$sformatf("Expected Data: %0h Actual Data:


%0h",sc_mem[expdata.addr],expdata.data),UVM_LOW)

end

else begin

`uvm_error("APB_SCOREBOARD","------ :: READ DATA MisMatch :: ------")

`uvm_info("",$sformatf("Addr: %0h",expdata.addr),UVM_LOW)

`uvm_info("",$sformatf("Expected Data: %0h Actual Data:


%0h",sc_mem[expdata.addr],expdata.data),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;

virtual dut_if vif;

function new(string name = "apb_test", uvm_component parent);

super.new(name,parent);

endfunction

function void build_phase(uvm_phase phase);

super.build_phase(phase);

env = apb_env::type_id::create("env", this);

if (!uvm_config_db#(virtual dut_if)::get(this, "", "vif", vif)) begin

`uvm_fatal("build_phase", "No virtual interface specified for this test instance");

end

uvm_config_db#(virtual dut_if)::set( this, "env", "vif", vif);

endfunction

//Run phase - Create an abp_sequence and start it on the apb_sequencer

task run_phase(uvm_phase phase);

super.run_phase(phase);

apb_seq = apb_sequence::type_id::create("apb_seq");

phase.raise_objection( this, "Starting apb_base_seqin main phase" );

$display("%t Starting sequence apb_seq run_phase",$time);

apb_seq.start(env.agt.sqr);

// #100ns;
phase.drop_objection( this , "Finished apb_seq in main phase" );

endtask

endclass

TOP FILE:

// Code your testbench here

// or browse Examples

`include "uvm_macros.svh"

module top;

import uvm_pkg::*; //compilation of uvm library files

`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 [31:0] paddr;

logic psel;

logic penable;

logic pwrite;

logic [31:0] prdata;

logic [31:0] pwdata;


dut_if apb_if();

apb_slave dut(.dif(apb_if));

initial begin

apb_if.pclk=0;

end

//Generate a clock

always begin

#10 apb_if.pclk = ~apb_if.pclk;

end

initial begin

apb_if.rst_n=0;

repeat (1) @(posedge apb_if.pclk);

apb_if.rst_n=1;

end

initial begin

uvm_config_db#(virtual dut_if)::set( null, "uvm_test_top", "vif", apb_if);

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.

Why we are using APB:


We use APB because it provides an easy, reliable, and low-latency method to access low-
bandwidth peripherals like UARTs, timers, GPIOs, and interrupt controllers. It reduces
design complexity, saves power, and improves area efficiency compared to more complex
buses like AHB or AXI.

Where we use APB:


APB is typically used in the peripheral subsystem of an SoC where speed is less important
but simple access to registers is necessary. Examples include connecting processors to
configuration registers, status monitors, control interfaces, low-speed communication
devices, and debug modules.

You might also like