← Back to list

PYUVM RAL(Register abstraction layer)

Recently, pyuvm contributors added the UVM Register Abstraction Layer to pyuvm. So, I wanted to give it a try for fun. It is really good to…

Muhammed Kocaoğlu · 2024-05-19 16:35 · 2 claps · 3.3 min read
#uvm-register #uvm-training #rals #vmu #pyuvm
Open on Medium ↗
Wiki topics: TLS · Design Tools & Workflow

PYUVM RAL(Register abstraction layer)

Recently, pyuvm contributors added the UVM Register Abstraction Layer to pyuvm. So, I wanted to give it a try for fun. It is really good to see the community is bringing pyuvm to a better level.

Those who want to have an introductory level of knowledge of Cocotb may have a look at my previous post where I made an example without utilizing pyuvm. I’ll utilize pyuvm this time and will make a demo on pyuvm RAL.

What is pyuvm and how to install the version with RAL?

Pyuvm is an open-source Python implementation of the Universal Verification Methodology(UVM). Pyuvm allows us to use the same verification concepts and methodologies using Python. However, it is not mature yet and I think has a long way to go.

To get the version with RAL support, we need to run the command below. Note that, this is not the latest but a pre-release. https://pypi.org/project/pyuvm/3.0.0.dev0/

$ pip install pyuvm==3.0.0.dev0

After installing we are good to go.

What is RAL (Register Abstraction Layer)

RAL is a concept used in verification to abstract and manage register and memories of a hardware design. This demo will be on only register abstraction. Memory declaration is not supported yet. And we will use front-door access, back-door access is not supported either.

Step 1: Abstracting the registers As a first step, we’ll define the registers to be abstracted. We’ll have 4 registers in this example with names reg0, reg1, reg2, and reg3. Only reg0 implementation is given. The others are added the same way. I gave the complete code on my GitHub page for you to have a look at. Assuming that you have a basic knowledge of UVM implementation and skipping detailed explanation as it is quite similar.

class REG0(uvm_reg):
  def __init__(self, name="REG0", reg_width=32):
    super().__init__(name, reg_width)
    self.f0 = uvm_reg_field('f0')
  def build(self):
    self.f0.configure(self, 32, 0, 'RW', 0, 0)
    self._set_lock()

class reg_block(uvm_reg_block):
  def __init__(self, name="reg_block"):
    super().__init__(name)
    self.def_map = uvm_reg_map('map')
    self.def_map.configure(self, 0)

    # reg0
    self.reg0 = REG0('reg0')
    self.reg0.configure(self, "0x0", "", False, False)
    self.def_map.add_reg(self.reg0, "0x0", "RW")

Step 2: Adapter The responsibility of the adapter is to convert register transaction to its equivalent bus transaction and the bus transaction to its equivalent register transaction. In this demo, we are not using any specific bus model.

# ADAPATER
class bus_adapter(uvm_reg_adapter):
  def __init__(self, name="bus_adapter"):
    super().__init__(name)
  def reg2bus(self, rw: uvm_reg_bus_op) -> uvm_sequence_item:
    item = simple_bus_item("item")
    if (rw.kind == access_e.UVM_READ):
      item.rd = 1
      item.rdata = rw.data
    else:
      item.rd = 0
      item.wdata = rw.data
    item.addr = rw.addr
    return item
  def bus2reg(self, bus_item: uvm_sequence_item, rw: uvm_reg_bus_op):
    if bus_item.rd == 1:
      rw.kind = access_e.UVM_READ
      rw.data = bus_item.rdata
    else:
      rw.data = bus_item.wdata
      rw.kind = access_e.UVM_WRITE
    rw.addr = bus_item.addr

Step 3: Sequence item Required bus signals are defined in this class.

# SEQUENCE ITEM
class simple_bus_item(uvm_sequence_item):
  def __init__(self, name):
    super().__init__(name)
    self.rdata: int = 0
    self.rd: int = 0
    self.addr: int = 0
    self.wdata: int = 0

Step 4: Driver DUT is driven in this class.

  async def run_phase(self):
    self.reset()
    while True:
      cmd = await self.seq_item_port.get_next_item()
      if (cmd.rd == 0):
        await self.write_reg(int(cmd.addr, 16), cmd.wdata)
      else:
        read_data = await self.read_reg(int(cmd.addr, 16))
        cmd.rdata = read_data
      self.seq_item_port.item_done()

Step 5: Agent In this class, driver and sequencer connections are made.

class Agent(uvm_agent):
  def build_phase(self):
    self.seqr = uvm_sequencer("seqr", self)
    self.drv = Driver.create("drv", self)
  def connect_phase(self):
    self.drv.seq_item_port.connect(self.seqr.seq_item_export)

Step 6: Environment In this class, register block connections to the agent sequencer and adapter are made.

class Env(uvm_env):
  def build_phase(self):
    self.agt         = Agent.create("agt", self)
    self.reg_adapter = bus_adapter("reg_adapter")
    self.reg_block   = reg_block("reg_block")
  def connect_phase(self):
    self.reg_block.def_map.set_sequencer(self.agt.seqr)
    self.reg_block.def_map.set_adapter(self.reg_adapter)

Step 7: Describe the hardware to test RAL front-door write and read A very simple RTL is described to test RAL. In the RTL, there are 4 registers, each has 32 bits length.

module bus_slave (
  input  logic        clk,
  input  logic        rstn,
  input  logic        rd,
  input  logic        wr,
  input  logic [1:0]  addr,
  input  logic [31:0] din,
  output logic [31:0] dout
);

  logic [31:0] regs_array [0:3];

  always_ff @(posedge clk) begin
    if (!rstn)  begin
      dout <= 0;
    end else begin
      if (wr) begin
        regs_array[addr] = din;
      end else if (rd) begin
        dout <= regs_array[addr];
      end
    end
  end
endmodule

Step 8: Write to the hardware registers using RAL front-door access We will use built-in read and write methods in uvm_reg class.

status = await self.env.reg_block.reg0.write(15,self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
(status, rdata) = await self.env.reg_block.reg0.read(self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
self.logger.info(f"reg model rdata: {int(rdata)} @addr: {self.env.reg_block.reg0.get_address()}")

status = await self.env.reg_block.reg1.write(25,self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
(status, rdata) = await self.env.reg_block.reg1.read(self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
self.logger.info(f"reg model rdata: {int(rdata)} @addr: {self.env.reg_block.reg1.get_address()}")

status = await self.env.reg_block.reg2.write(35,self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
(status, rdata) = await self.env.reg_block.reg2.read(self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
self.logger.info(f"reg model rdata: {int(rdata)} @addr: {self.env.reg_block.reg2.get_address()}")

status = await self.env.reg_block.reg3.write(45,self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
(status, rdata) = await self.env.reg_block.reg3.read(self.env.reg_block.def_map,path_t.FRONTDOOR,check_t.NO_CHECK)
self.logger.info(f"reg model rdata: {int(rdata)} @addr: {self.env.reg_block.reg3.get_address()}")

In the test, we are writing reg0, reg1, reg2, reg3, and reading respectively. We expect to read 15, 25, 35, and 45 decimals and the log is given below.

make

As you can see, we are reading 15, 25, 35, and 45 decimals respectively.

If you have any questions, feel free to ask on Linkedin. https://www.linkedin.com/in/muhammedkocaoglu/

Github Repo: https://github.com/muhammedkocaoglu/Cocotb-Examples/tree/main/pyuvm_ral_demo


메타데이터
post_id
2bc844c0b01e
slug
pyuvm-ral-register-abstraction-layer-2bc844c0b01e
url
https://medium.com/@kocamuha/pyuvm-ral-register-abstraction-layer-2bc844c0b01e
canonical_url
https://medium.com/@kocamuha/pyuvm-ral-register-abstraction-layer-2bc844c0b01e
author_url
https://medium.com/@kocamuha
status
ok
fetched_at
2026-07-23 20:09:59