首页 > 其他分享 >UVM寄存器层

UVM寄存器层

时间:2023-01-17 13:00:56浏览次数:46  
标签:adapter predictor uvm 寄存器 phase reg UVM

1 UVM 寄存器层

 UVM寄存器层类用于为正在验证的设计 (DUV) 中的内存映射寄存器和内存创建高级的面向 对象模型。寄存器层定义了许多基类,可以适当地扩展这些基类以抽象对 DUV 的读写操作。 在这之前,需要了解一下寄存器是如何组织的以及它们在数字设计中的作用。

1.1 寄存器

大多数数字设计模块都具有可通过外围总线访问的软件可控寄存器。这些寄存器允许硬件在使用特定值编程时以特定方式运行。例如,可能有一个 32 位寄存器,其中包含多个单独的字段field。每个字段field代表一个特定的功能,可以在需要时由软件配置。

 

 上图是设计中具有五个不同功能字段field的单个寄存器的示例。字段field属性可以由设计者指定如下。

Bit Offset

Name

Read/Write

Width

Description

0

En

RW

1

Enable. Set this bit to 1 to turn on the entire module, set it to 0 to disable

1

Mode

RW

3

Supports the following modes :

Low Energy : 000

Medium Energy : 001

High Speed : 010

Auto Adjust : 011

Cruise Mode : 100

Balanced : 101

4

Halt

RW

1

Halt. Set this bit to 1 to stop this module temporarily and set it to 0 to resume

5

Auto

RW

1

Auto shutdown mode. Set this bit to 1 to start auto shutdown after 5 min

6

Reserved

RO

5

Reserved for future additions

11

Speed

RW

5

Set a value to control the speed from 100 rpm to 2000 rpm.

16

Reserved

RO

16

Reserved for future additions

设计文件可能在 RTL 中有一个模块来实现这些寄存器。

// RTL representation
module reg_block (...);
wire [31:0] reg_ctl;
// Declare reg variables to store register field values
reg ctl_en; // Enable for the module
reg [2:0] ctl_mode; // Mode
reg ctl_halt; // Halt
reg ctl_auto; // Auto shutdown
reg [4:0] ctl_speed; // Speed control
assign reg_ctl = {16'b0, ctl_speed, 5'b0, ctl_auto, ctl_halt, ctl_mode
// Logic for getting individual fields from the bus
...
endmodule

1.2 寄存器块block

会有这样的寄存器集合,其中每个寄存器都有一组不同的字段field和配置。例如,可以将 REG_CTL 放置在偏移地址 0x0 处,以强调它是模块的主控制寄存器。因为它占用 32 位(4 个字节),所以下一个寄存器 REG_STAT 将位于 0x4 并保存设计的状态相关标志。

 

 1.3 内存映射

 SoC 通常具有一个或多个处理器内核、DMA 引擎、总线网络互连和许多外围模块。每个外围模块都有一个关联的寄存器块,它们都被布置在称为内存映射的东西中。内存映射就像一个巨大的表,它定义了处理器总线上各种内存和外围设备/设备寄存器的地址范围。处理器可以通过APB或WishBone 等外围总线协议访问这些寄存器块。因此,每个外围块都需要一个基地址,各个寄存器块可以驻留在该基地址上。

上述寄存器块register block也是Timer设计模块的一部分,其基地址为 0xE7B3_0000。为了访问 Timer 模块的REG_CTL,内核必须发送地址为 0xE7B3_0000 的事务,因为REG_CTL的基地 址偏移量为 0x0。要访问 REG_DMACTL,内核将发送地址为 (0xE7B3_0000 + 0xC) 的事务,因为 REG_DMACTL 的偏移量为 0xC。

1.4 UVM 寄存器模型

上面介绍了寄存器在内存映射中的布局方式。 因此,我们将简单地使用现有的 UVM RAL(寄存器抽象层Register abstraction layer)类来定义各个字段field、寄存器register和寄存器块block。寄存器模型是一个实体,它包含并描述每个寄存器及其各个字段filed的类对象的层次结构。我们可以使用寄存器模型对象对设计执行读写操作。模型中的每个寄存器对应于设计中的实际硬件寄存器。模型中的寄存器内有两种变量。

1.4.1 期望值

这是我们希望设计具有的值。换句话说,模型有一个内部变量来存储期望值,该值可以在以后的设计中更新。例如,如果我们希望设计中的寄存器 REG_STAT 的值为 0x1234_5678,那么该寄存器的期望值必须在模型中设置为 0x1234_5678,并且应该调用更新任务以将其反映在设计中.

1.4.2 镜像值

每次在设计上发生读取或写入操作时,都会更新该特定寄存器的镜像值。因此,模型中的镜像值是设计中最新的已知值。这非常有用,因为我们不必每次想知道设计中寄存器的值时都发出读取操作。

每个寄存器字段也存在期望值和镜像的值。

1.4.3 创建寄存器类

考虑我们正在逐步构建模型,第一个合乎逻辑的步骤是识别设计中的所有寄存器并为每个寄存器创建一个对象。从 Introduction (/uvm/register-layer) 开始,我们将首先为 REG_CTL 创建一个寄存器类对象,因为它的字段已经定义好。每个寄存器对象都应该从 uvm_reg 类继承,每个寄存器字段应该是uvm_reg_field 对象.不必为寄存器字段register field创建单独的类,只需为每个寄存器字段register field创建一个对象,如下例所示。

class reg_ctl extends uvm_reg;
rand uvm_reg_field En;
rand uvm_reg_field Mode;
rand uvm_reg_field Halt;
rand uvm_reg_field Auto;
rand uvm_reg_field Speed;

这些字段已被声明为 rand 可以在需要时随机化,并且类型为 uvm_reg_field 。这只是对所有字段field的声明,实际定义将在稍后出现。接下来,我们必须像通常使用任何 UVM 类一样编写new()函数。该 super 调用将需要三个参数,第一个是寄存器的名称,第二个表示寄存器的大小,在我们的例子中是 32 位。最后一个参数指定寄存器抽象类的扩展中存在哪些功能覆盖模型。

function new (string name = "reg_ctl");
super.new (name, 32, UVM_NO_COVERAGE);
endfunction

到目前为止,我们还没有定义任何与register field相关的内容,因此,接下来让我们在一个名为build()的自定义用户函数中执行此操作。我们将保留该函数 virtual ,以防其他人试图覆盖此方法并调用他们自己的 build() 版本。

virtual function void build ();
// Create object instance for each field
this.En = uvm_reg_field::type_id::create ("En");
this.Mode = uvm_reg_field::type_id::create ("Mode");
this.Halt = uvm_reg_field::type_id::create ("Halt");
this.Auto = uvm_reg_field::type_id::create ("Auto");
this.Speed = uvm_reg_field::type_id::create ("Speed");
// Configure each field
this.En.configure (this, 1, 0, "RW", 0, 1'h0, 1, 1, 1);
this.Mode.configure (this, 3, 1, "RW", 0, 3'h2, 1, 1, 1);
this.Halt.configure (this, 1, 4, "RW", 0, 1'h1, 1, 1, 1);
this.Auto.configure (this, 1, 5, "RW", 0, 1'h0, 1, 1, 1);
this.Speed.configure (this, 5, 11, "RW", 0, 5'h1c, 1, 1, 1);
endfunction
endclass

该 configure() 方法用于定义类对象的字段属性,并具有以下参数。

function void configure(
 uvm_reg parent,
 int unsigned size,
 int unsigned lsb_pos,
 string access,
 bit volatile,
 uvm_reg_data_t reset,
 bit has_reset,
 bit is_rand,
 bit individually_accessible
 );
class reg_stat extends uvm_reg; ... endclass
class reg_dmactl extends uvm_reg; ... endclass ...

1.4.4 创建寄存器块

在定义了所有寄存器之后,它需要成为寄存器块block的一部分。为此,我们将定义一个寄存器块,该块继承自 uvm_reg_block,需要为其中的所有寄存器创建对象。为了我们的目的,让我们 只将三个寄存器放入这个块.

class reg_block extends uvm_reg_block;
rand reg_ctl m_reg_ctl;
rand reg_stat m_reg_stat;
rand reg_inten m_reg_inten;
function new (string name = "reg_block");
super.new (name, UVM_NO_COVERAGE);
endfunction
virtual function void build ();
// Create an instance for every register
this.default_map = create_map ("", 0, 4, UVM_LITTLE_ENDIAN, 0);
this.m_reg_ctl = reg_ctl::type_id::create ("m_reg_ctl", , get_full_name);
this.m_reg_stat = reg_stat::type_id::create ("m_reg_stat", , get_full_name);
this.m_reg_inten = reg_inten::type_id::create ("m_reg_inten", , get_full_name);
// Configure every register instance
this.m_reg_ctl.configure (this, null, "");
this.m_reg_stat.configure (this, null, "");
this.m_reg_inten.configure (this, null, "");
// Call the build() function to build all register fields within e
this.m_reg_ctl.build();
this.m_reg_stat.build();
this.m_reg_inten.build();
// Add these registers to the default map
this.default_map.add_reg (this.m_reg_ctl, `UVM_REG_ADDR_WIDTH'h0,
this.default_map.add_reg (this.m_reg_stat, `UVM_REG_ADDR_WIDTH'h4,
this.default_map.add_reg (this.m_reg_inten, `UVM_REG_ADDR_WIDTH'h8
endfunction
endclass

reg_block类的一个对象是寄存器模型,可用于访问所有寄存器并对它们执行读写操作。上面只介绍了如何定义寄存器模型。但未讨论如何写入和读取实际 DUV 的问题。要执行寄存器操作,必须通过外围总线发送有效的总线事务.

1.5 UVM 寄存器环境

在寄存器模型 (/uvm/register-model) 中,我们看到了如何创建一个模型来表示设计中的实际寄存器。现在我们将看看寄存器环境中执行寄存器访问(例如读取和写入操作)所需的不同组件。 寄存器环境基本上需要四个组件:

基于 UVM 类的寄存器模型register model,可准确反映设计寄存器的值

基于某种协议将实际总线事务驱动到设计的代理uvm-agent

将模型中的读写语句转换为基于协议的总线事务的适配器adapter

用于了解总线活动并更新寄存器模型以匹配设计内容的预测器predictor

 1.5.1 register adapter

uvm_reg 具有调用的内置方法 read() 和 write() 启动对设计的读写操作。

class reg_ctl extends uvm_reg;

...

endclass

m_reg_ctl.write (status, addr, wdata); // Write wdata to addr

m_reg_ctl.read (status, addr, rdata); // Read rdata from addr

这些寄存器读/写访问调用创建了一个内部通用寄存器项类型 uvm_reg_bus_op ,它是一个简 单的结构,如下所示。

typedef struct {
uvm_access_e kind; // Access type: UVM_READ/UVM_WRITE
uvm_reg_addr_t addr; // Bus address, default 64 bits
uvm_reg_data_t data; // Read/Write data, default 64 bits
int n_bits; // Number of bits being transferred
uvm_reg_byte_en byte_en; // Byte enable
uvm_status_e status; // Result of transaction: UVM_IS_OK, UVM_HAS_X, UVM_NOT_OK
} uvm_reg_bus_op;

为了将读/写方法调用转换为实际的总线协议访问,通用寄存器项由称为适配器adapter的组件转换为协议特定的总线事务项。适配器adapter需要是双向的,以便能够将通用寄存器项转换为总线事务,并将总线事务响应转换回通用寄存器项,以便可以在寄存器模型中对其进行更新。

适配器adapter通过reg2bus() 和 bus2reg() 函数促进了这个转换过程。顾名思义, reg2bus() 将寄存器级对象 uvm_reg_bus_op 转换为协议事务,而 bus2reg() 将总线级事务转换为寄存器级对象。总线协议因设计而异,因此必须继承自定义适配器 uvm_reg_adapter 才能覆盖这些功 能。适配器adapter中还有两个变量来处理字节启用和响应项。如果总线协议允许启用某些字节通道以选择数据总线的某些字节为有效,则该位supports_byte_enable 应设置为 1。如果目标agent驱动程序发送需要响应处理的单独响应项,则该位 provides_responses 应设置为 1。

// apb_adapter is inherited from "uvm_reg_adapter"
class reg2apb_adapter extends uvm_reg_adapter;
 `uvm_object_utils (apb_adapter)
 // Set default values for the two variables based on bus protocol
 // APB does not support either, so both are turned off
 function new(string name="apb_adapter");
 super.new(name);
 supports_byte_enable = 0;
 provides_responses = 0;
 endfunction

// This function accepts a register item of type "uvm_reg_bus_op" and assigns

// address, data and other required fields to the bus protocol sequence_item

virtual function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw)
 bus_pkt pkt = bus_pkt::type_id::create ("pkt");
 pkt.write = (rw.kind == UVM_WRITE) ? 1: 0;
 pkt.addr = rw.addr;
 pkt.data = rw.data;
 `uvm_info ("adapter", $sformatf ("reg2bus addr=0x%0h data=0x%0h kind=%s”,pkt.addr, pkt.data, rw.kind.name), UVM_DEBUG)
 return pkt;
 endfunction

// This function accepts a bus sequence_item and assigns address/data fields to

// the register item

 virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);

 bus_pkt pkt;

 // bus_item is a base class handle of type "uvm_sequence_item" and hence does not

 // contain addr, data properties in it. Hence bus_item has to be cast into bus_pkt

 if (! $cast (pkt, bus_item)) begin

 `uvm_fatal ("reg2apb_adapter", "Failed to cast bus_item to pkt")

 end

 rw.kind = pkt.write ? UVM_WRITE : UVM_READ;

 rw.addr = pkt.addr;

 rw.data = pkt.data;

 rw.status = UVM_IS_OK; // APB does not support slave response

 `uvm_info ("adapter", $sformatf("bus2reg : addr=0x%0h data=0x%0h kind=%s status=%s”, rw.addr, rw.data, rw.kind.name(), rw.status.name()),UVM_DEBUG)

 endfunction

endclass

由于 APB 总线协议不支持字节使能,因此位 supports_byte_enable put() 设置为 0。如果代理驱动程序通过put or item_done()提供单独的响应项,则应将位providers_responses设置为 1,以便寄存器模型知道它必须等待在将其转换为register item之前获得响应。如果设置了该位并且agent驱动程序未提供任何响应,则仿真可能会挂起。由于 APB 协议不支持从机响应,因此该位设置为 0。

1.5.2 register predictor

寄存器模型有不同的方法来更新模型并保持其寄存器副本与 DUT 中的值同步。默认情况下,它会在每次执行读取或写入事务时更新寄存器模型。例如,如果使用 write() 方法将一个值写入设计,它可以很容易地使用写出的数据更新模型中该寄存器的镜像值。类似地,当 read() 方法从设计中获取读取数据时,它可以相应地更新镜像值。 

然而,不需要总是使用寄存器模型来写入设计,因为可以在同一个目标agent上启动具有地址 和数据的单独序列以写入设计寄存器。这会使寄存器模型中的值过时,并且每次其他序列读 取或写入设计时都需要更新。可以在目标总线agent接口上放置一个称为predictor的组件,以监 视任何事务并相应地更新寄存器模型。

// uvm_reg_predictor class definition
class uvm_reg_predictor #(type BUSTYPE=int) extends uvm_component;
 `uvm_component_param_utils(uvm_reg_predictor#(BUSTYPE))
uvm_analysis_imp #(BUSTYPE, uvm_reg_predictor #(BUSTYPE)) bus_in;
 uvm_reg_map map;
 uvm_reg_adapter adapter;
...
endclass

该 uvm_reg_predictor 组件是uvm_subscriber的一个子类,并具有一个分析实现端口,能 够从目标监视器接收总线序列项。它使用寄存器apapter将传入的总线数据包转换为通用寄存 器项,然后从寄存器映射中查找地址以找到正确的寄存器并更新其内容。这是独立于协议的,因此我们不需要定义自定义类。但是,我们必须创建一个寄存器predictor的参数化版本, 如下所示,它可以集成到我们的寄存器环境中。

1.5.3 集成predictor的步骤

1.声明具有目标总线事务类型的寄存器预测器的参数化版本

// Here "bus_pkt" is the sequence item sent by the target monitor to this predictor

uvm_reg_predictor #(bus_pkt) m_apb_predictor;

2.在寄存器环境中构建predictor

virtual function void build_phase(uvm_phase phase);

super.build_phase(phase);

m_apb_predictor = uvm_reg_predictor#(bus_pkt)::type_id::create("m_apb_predictor”, this);

endfunction

3.将寄存器映射、适配器adapter和分析端口analysis ports连接到预测器predictor

virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// 1. Provide register map to the predictor
 m_apb_predictor.map = m_ral_model.default_map;
 // 2. Provide an adapter to help convert bus packet into register item
 m_apb_predictor.adapter = m_apb_adapter;
 // 3. Connect analysis port of target monitor to analysis implementation of predictor
m_apb_agent.ap.connect(m_apb_predictor.bus_in);
endfunction

1.5.4 register环境集成

让我们使用上述所有组件,并将它们集成到一个单独的寄存器环境中,以使其更具可重用性。

class reg_env extends uvm_env;
 `uvm_component_utils (reg_env)
 function new (string name="reg_env", uvm_component parent);
 super.new (name, parent);
 endfunction
 uvm_agent m_agent; // Agent handle
 ral_my_design m_ral_model; // Register Model
 reg2apb_adapter m_apb_adapter; // Convert Reg Tx <->Bus -type packets
 uvm_reg_predictor #(bus_pkt) m_apb_predictor; // Map APB tx to register in model
 virtual function void build_phase (uvm_phase phase);
 super.build_phase (phase);
 m_ral_model = ral_my_design::type_id::create ("m_ral_model", this);
 m_apb_adapter = m_apb_adapter :: type_id :: create ("m_apb_adapter”);
 m_apb_predictor = uvm_reg_predictor #(bus_pkt) :: type_id :: creat (“m_apb_predictor”,this)
 m_ral_model.build ();
 m_ral_model.lock_model ();
 uvm_config_db #(ral_my_design)::set (null, "uvm_test_top", "m_ral_model);
 endfunction
 virtual function void connect_phase (uvm_phase phase);
 super.connect_phase (phase);
 m_apb_predictor.map = m_ral_model.default_map;
 m_apb_predictor.adapter = m_apb_adapter;
 m_agent.ap.connect(m_apb_predictor.bus_in);
 endfunction
endclass  

我们必须在 build_phase() 中声明和创建三个组件。需要注意的是,必须通过调用其 lock() 函数来锁定寄存器模型,以防止任何其他测试台组件或部件修改结构或向其中添加寄存器。寄存器模型的 build()方法是自定义函数,不是标准UVM库的一部分,只是在模型中启动构建子块、映射和寄存器。最好将此模型放在配置数据库中的某个位置,以便其他组件可以访问它。

现在我们必须为predictor提供一个映射方案,以便它可以将地址值与模型中的寄存器的地址值匹配,并且还必须有一个predictor的句柄,以便它可以直接获取转换后的总线值。这最好在 connect_phase() 如上所示的方法中完成。

1.6 连接寄存器环境

直到上一个阶段,我们已经定义并创建了寄存器环境中所需的一切。然而,尚未定义哪个agent 负责驱动这些寄存器事务。

class my_env extends uvm_env;
 `uvm_component_utils (my_env)
 my_agent m_agent;
 reg_env m_reg_env;
 function new (string name = "my_env", uvm_component parent);
 super.new (name, parent);
 endfunction
 virtual function void build_phase (uvm_phase phase);
 super.build_phase (phase);
 m_agent = my_agent::type_id::create ("m_agent", this);
 m_reg_env = reg_env::type_id::create ("m_reg_env", this);
 endfunction
 virtual function void connect_phase (uvm_phase phase);
 super.connect_phase (phase);
 m_agent.m_mon.mon_ap.connect (m_reg_env.m_apb2reg_predictor.bus_in);
 m_reg_env.m_ral_model.default_map.set_sequencer (m_agent.m_seqr, m_r
 endfunction
endclass

  

标签:adapter,predictor,uvm,寄存器,phase,reg,UVM
From: https://www.cnblogs.com/lanlancky/p/17057506.html

相关文章

  • 河北稳控科技振弦采集模块配置工具VMTool生成寄存器值
    河北稳控科技振弦采集模块配置工具VMTool生成寄存器值 生成寄存器值VMXXX有很多按位使用的寄存器,使用VMTool工具可进行方便的设置,当需要知道寄存器的实际值时,可通......
  • 振弦采集模块配置工具VMTool生成寄存器值
    振弦采集模块​配置​工具VMTool生成寄存器值生成寄存器值VMXXX有很多按位使用的寄存器,使用VMTool工具可进行方便的设置,当需要知道寄存器的实际值时,可通过以下两种方......
  • 通用寄存器
     通用寄存器:这类寄存器在处理器中数量较多、使用频度较高,具有多种用途。例如它们可用来存放指令需要的操作数据,又可用来存放地址以便在主存或I/O接口中指定操作数据的位......
  • 逆向——EFLAGS寄存器
    EFLAGS寄存器:  查看DTDEBUG中的EFLAGS的值,然后转换成二进制的形式,并取出CF/PF/AF/ZF/SF/OF的值记住这几个寄存器的位置和名称1、进位标志CF(CarryFlag):如果运算结......
  • 一些汇编指令寄存器杂谈
    一些汇编指令寄存器杂谈汇编指令之寄存器寄存器学习记录8086寄存器分类:1.通用寄存器(8个):AX、BX、CX、DX和SI、DI、SP、BP2.指令指针(1个):IP3.标志寄存器(1个)......
  • 栈和寄存器虚拟机比较(以python和lua为例)
    指令长度pythonpython的指令定长,长度为16bit,其中8bit操作码,8bit操作数。///@file:Python-3.6.0\Include\code.htypedefuint16_t_Py_CODEUNIT;#ifdefWORDS_BIGEND......
  • 在KEIL中如何查看寄存器的值与地址
    正确连接硬件,编译程序后,按下工具栏的调试按钮进入调试模式。然后在View->SystemViwer下,可以按照分类来查看某个寄存器的值。例如RCC相关的寄存器:GPIO相关的寄存器。寄存......
  • HAL库教程11:定时器的缓冲功能与影子寄存器
      在STM32的定时器中,TIMx_PSC、TIM_ARR两个寄存器加上捕捉比较模块中TIMX_CCR寄存器,它们都可以动态修改。不过他们的修改和生效可能不在同一个时刻,或者说,修改过后立即生......
  • asm:8086寄存器概述(intel - reg16bits)
    asm:8086寄存器概述(intel-reg16bits)   一、 4个16位段地址寄存器  1、8086对存储器采用分段管理,4个段寄存器分别用于存放4个当前段的起始地址,又称为段基址寄存......
  • 逆向-通用寄存器
    32位通用寄存器的指定用途如下:      MOV的语法:r/m8,表示8位的寄存器或内存  MOV目标操作数,源操作数作用:拷贝源操作数到目标操作数1、源操作数可以是......