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