写过 verilog 硬件代码的同学应该都知道 DUT 会包含很多寄存器,它们是模块间交互的接口,其用途大致可以分为两类:
a. 通过读出寄存器当前的值获取 DUT 当前的状态,该类寄存器称为状态寄存器;
b. 通过对寄存器进行配置,可以使得 DUT 工作在一定模式下,该类寄存器称为配置寄存器。
在验证过程中,寄存器的验证是最新开始的,只有保证寄存器的配置正确,才能使得硬件之间的“交互”正确。在验证寄存器配置是否正确的过程中,我们需要频繁的对 DUT 内部的寄存器进行读写操作,如 reference model 需要获取指定 reg 的参数值,在验证平台中我们获取 DUT 内部寄存器的值的方式主要有两种:
前门访问(FRONTDOOR):启动 sequence 产生待操作寄存器的读写控制和地址,在 driver 中通过总线(Bus)驱动至 DUT,并在 monitor 中采集 Bus 输出数据,该方式需要消耗仿真时间 ;
后门访问(BACKDOOR):在仿真环境中通过 DUT 实例名进行点操作,直接访问 DUT 内部的寄存器,该方式的缺点是,点操作需要通过绝对路径操作,如果寄存器数量庞大,会导致验证环境臃肿繁杂,容易出错。
因为上述操作的不利因素,才导致寄存器模型 ( RAL Model ) 产生。
1. 什么是寄存器模型
RAL Model 对应于 DUT 中的寄存器,RAL Model 中有 DUT 每一个 寄存器的副本,它是真实硬件寄存器在软件环境中的行为模型;硬件寄存器的一个或多个 bit 的拼接称为一个域 ( field );多一个 field 形成一个 reg;多个 reg 构成一个块 ( block )。uvm library 已经为我们定义好了上述几个概念,我们在使用时只需继承即可。
uvm_reg_field:这是寄存器模型中的最小单位。
uvm_reg:它比 uvm_reg_field 高一个级别,但是依然是比较小的单位。一个寄存器中至少包含一个 uvm_reg_field。
uvm_reg_block:它是一个比较大的单位,在其中可以加入许多的 uvm_reg,也可以加入其他的 uvm_reg_block。一个寄存器模型中至少包含一个 uvm_reg_block。
uvm_reg_map:每个寄存器在加入寄存器模型时都有其地址,uvm_reg_map 就是存储这些地址,并将其转换成可以访问的物理地址(因为加入寄存器模型中的寄存器地址一般都是偏移地址,而不是绝对地址)。当寄存器模型使用前门访问方式来实现读或写操作时,uvm_reg_map 就会将地址转换成绝对地址,启动一个读或写的 sequence,并将读或写的结果返回。在每个 reg_block 内部,至少有一个(通常也只有一个)uvm_reg_map。
如下图所示,RAL Model 中包含 MEM 和 block,它们分别用于对 DUT 中的寄存器和 memory 进行建模,其行为和硬件行为保持一致(其实是尽可能保持一致),ADDR MAP 用于实现访问寄存器的相对地址和绝对地址的转换。
寄存器模型注意有以下优势:
a.方便对 DUT 中寄存器进行读写;
b.在软件仿真时,可以不耗时的获取寄存器的值(直接从 RAL Model 中获取);
c.可以很方便的正对寄存器的 coverage 验证点的收集。
如果有了寄存器模型,那么寄存器访问过程就可以简化为:
RAL Model
task my_model::main_phase(uvm_phase phase);
…
reg_model.version.read(status, value, UVM_FRONTDOOR);
reg_model.version.write(status, value, UVM_FRONTDOOR);
…
endtask
只要一条语句就可以实现上述复杂的过程。像启动 sequence 及将读取结果返回这些事情,都会由寄存器模型来自动完成。在没有寄存器模型之前,只能启动 sequence 通过前门(FRONTDOOR)访问的方式来读取寄存器,局限较大,在 scoreboard(或者其他 component )中难以控制。而有了寄存器模型之后,scoreboard 只与寄存器模型打交道,无论是发送读的指令还是获取读操作的返回值,都可以由寄存器模型完成。有了寄存器模型后,可以在任何耗费时间的phase中使用寄存器模型以前门访问或后门(BACKDOOR)访问的方式来读取寄存器的值,同时还能在某些不耗费时间的 phase(如 check_phase)中使用后门访问的方式来读取寄存器的值。
2. 寄存器模型实例
假设有如下的 DUT: