目录
Introduction
UVM中的寄存器层类(Register Layer class)是用来创建高层次(high-level)基于对象(object-oriented)的模型,这些模型对应着DUT中的寄存器和存储单元。寄存器层定义了众多的基本类(base class)用来对DUT的寄存器进行抽象的读写操作(abstract read and write operation).在我们了解UVM的寄存器层之前,先来看看在DUT中的寄存器是如何定义以及实现其功能的。
许多设计中都可以通过外围总线(peripheral bus)来控制寄存器,这些寄存器可以被设置为不同的值从而使得硬件以不同的模式工作。例如上面的一个32bits的寄存器reg_ctl,在该寄存器中存在着多个变量(field),每一个变量都可以配置为不同的值用来配置不同的操作,如下表所示:
一个寄存器通常为32位,由多个变量构成,变量(field)通过uvm_reg_field来定义,如上面的speed、auto等;寄存器通过继承uvm_reg来定义,一个寄存器中至少要包含一个field;
下面来解释寄存器块(register block)的概念.寄存器块就是上述一系列类似的寄存器的集合 ,如下图:
每一个寄存器中都包含不同的变量(field),例如上面的reg_ctl存在于地址0x0的位置,由于它是4byte的寄存器,所以下一个寄存器的地址从0x4开始,如reg_stat;寄存器块通过继承uvm_reg_block来定义,每一个uvm_reg_block都至少包括一个uvm_reg,也可以加入其它的uvm_reg_block;
一个SOC系统通常包括一个或多个处理核(processor core)、DMA引擎(engine)、互连的内部总线和许多外围模块(peripheral module).每一个外围模块都有一个与之关联的寄存器块,这些寄存器块就存在于memory map中。memory map类似于一张很大的表(table),表中为处理器总线(processor bus)上的各种存储单元和寄存器定义了地址范围。处理器可以通过外围总线如APB总线来访问这些存储单元。每一个外围模块都在memory map中有一个存储单元,与之对应的寄存器块就存在于其中。memory map保存了所有寄存器的地址(一般为偏移地址,而不是绝对地址),通过继承uvm_reg_map来定义,当寄存器模型通过前门访问(frontdoor)方式来实现对寄存器的读或写操作时,map就会将地址转换为绝对地址,启动一个读或写的sequence,并将结果返回;系统一般定义好了一个default_map,只需要在寄存器块中将其实例化就可以了;一个uvm_reg_block通常至少有一个map!
假设上面的地址0xE78F_0000为模块timer design的寄存器块,为了访问寄存块中的reg_stat寄存器,则访问地址需要变为0xE78F_0000+0x4.
总结:
- uvm_reg_field是寄存器模型的最小单位,和DUT的每个register里的bit filed对应;
- uvm_reg 和dut中每个register对应,其宽度一般和总线位宽一致,里面可以包含多个uvm_reg_field;
- uvm_reg_block里包含uvm_reg,一般一个最底层模块级的DUT的所有寄存器,具有相同的基地址,会放在一个reg_block中;uvm_reg_block内也可包含其他低层次的reg_block;
- reg_block里有含有uvm_reg_map类的对象default_map,进行地址映射,以及用来完成寄存器前后门访问操作;
- 一个寄存器模型必须包含一个reg_block;
- 一个reg_block可以包含多个reg_map, 从而实现一个reg_block应用到不同总线,或不同地址段上;
- uvm_mem是对dut中memory进行建模使用的;
这些类均是继承自uvm_object类,一个完整的register model, 均有这些层次化的register元素构成,放到顶层的reg block中,一个reg block可以包含子block,register,register file和memories,如下图所示:
Register Model
现在我们可以用UVM RAL(register abstraction layer) 类来定义各种单独的变量、寄存器以及寄存器块。寄存器模型(register model)包括了寄存器以及各种变量,也就是说它描述了寄存器类的层次结构,我们可以通过寄存器模型的对象来对DUT进行读和写操作。在模型中,每一个寄存器都与实际DUT中的寄存器对应;模型中实际上存在两种类型的变量:
(1)Desired Value
desired value就是我们想让DUT中的寄存器所拥有的值,它一般保存在寄存器模型中,然后通过update()方法将该值赋给DUT中对应的寄存器,如下图:
(2)Mirrored Value
DUT中对应寄存器值的改变都会通过read()方法表现在寄存器模型的 Mirrored Value 中,因此每次DUT中寄存器值的更改我们都可以直接得知,如下图:
创建寄存器类(register)
寄存器类继承于uvm_reg,对于寄存器中的每一个变量都当做对象来处理,因此需要定义句柄和创建对象;寄存器中的变量(field)都属于uvm_reg_field类,可以直接在寄存器中定义并创建变量对象。我们以前面的reg_ctl寄存器为例,代码如下:
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;
`uvm_object_utils(reg_ctl)
function new (string name = "reg_ctl");
super.new (name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build ();
// 创建每一个变量的对象
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");
// 配置每一个变量
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
new()方法的参数有3个,第一个参数是寄存器的类名,第二个参数为寄存器的位宽,这个数字一般与系统总线的宽度一致,第三个参数为该寄存器是否要加入覆盖率的支持,可选的值如下:
每一个继承于uvm_reg的寄存器类都必须有一个build()方法,build()方法中需要创建变量对象,并且进行配置,该方法需要手动调用;
创建变量(field)对象的格式是和其他对象相同的,都是使用uvm_reg_field::type_id::create()方法;
创建完变量的对象后,还需要对变量对象进行配置,配置方法configure()的几个参数的定义如下:
function void configure(
uvm_reg parent,//变量所处的寄存器,一般为this
int unsigned size,//变量的位宽
int unsigned lsb_pos,//变量的最低位在寄存器中的序号,序号从0开始
string access,//该变量的获取方式,UVM共支持25种方式
bit volatile,//变量是否是易失的,一般设为0
uvm_reg_data_t reset,//复位后的默认值
bit has_reset,//是否有复位值,要配合上面的参数使用,一般设为1,表示带复位值
bit is_rand,//表示变量是否可以随机化,0表示不可以,则一直是复位值;1表示可以
bit individually_accessible//表示这个变量是否可以单独存取,1表示可以
);
类似的,我们可以定义其他寄存器类,如下:
class reg_stat extends uvm_reg;
...
endclass
class reg_dmactl extends uvm_reg;
...
endclass
...
创建寄存器块类(register block)
所有的寄存器都是属于一个寄存器块的,因此我们需要创建一个寄存器块类来包括上面所定义的寄存器.寄存器块通过继承uvm_reg_block来定义,每一个派生自uvm_reg_block都必须要有一个build()方法,该方法需要手动调用,并在它的build()方法中需要为每个寄存器创建对象,如下:
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);//创建默认的 memory map对象
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);
// 配置每一个寄存器
this.m_reg_ctl.configure (this, null, "");
this.m_reg_stat.configure (this, null, "");
this.m_reg_inten.configure (this, null, "");
// 调用每个寄存器的build()方法,为其中的变量创建对象
this.m_reg_ctl.build();
this.m_reg_stat.build();
this.m_reg_inten.build();
//将寄存器添加到默认的memory map
this.default_map.add_reg (this.m_reg_ctl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
this.default_map.add_reg (this.m_reg_stat, `UVM_REG_ADDR_WIDTH'h4, "RO", 0);
this.default_map.add_reg (this.m_reg_inten, `UVM_REG_ADDR_WIDTH'h8, "RW", 0);
endfunction
endclass
对寄存器进行实例化使用creat,包括三个参数,第一个参数为实例名,第二个参数为空,第三个参数一般为get_full_name;
对寄存器进行配置的configure()方法与上面对变量的configure方法不同,它的几个参数定义如下:
function void configure (
uvm_reg_block blk_parent,//寄存器所在的寄存器块名
uvm_reg_file regfile_parent = null,//reg_file指针,一般为null
string hdl_path = ""//hdl路径,一般为空""
)
一个uvm_reg_block一定要与一个uvm_reg_map对应,系统其实已经有了一个声明好的default_map,只需要在build()中将其实例化即可,注意这里map的实例化不是使用new,而是creat_map(),该方法有多个参数,如下:
virtual function uvm_reg_map create_map(
string name,//map的名字,使用default_map时为空
uvm_reg_addr_t base_addr,//基地址,所有的寄存器都会对该地址有一个偏移量
int unsigned n_bytes,//map所使用的系统总线的宽度,单位是byte
uvm_endianness_e endian,//大小端
bit byte_addressing = 1//是否可以按字节寻址,1表示可以
)
向default_map中添加寄存器用到add_reg()方法,该方法几个参数的含义如下:
virtual function void add_reg (
uvm_reg rg,//要加入的寄存器实例名
uvm_reg_addr_t offset,//寄存器地址相对于map基地址的偏移量
string rights = "RW",//寄存器的存取方式
bit unmapped = 0,//0表示在map中有地址;1表示不占用任何物理地址,此时下面的参数不能为null
uvm_reg_frontdoor frontdoor = null
)
寄存器块的对象就是一个寄存器模型,并且可以对寄存器模型中的所有寄存器进行读写操作;到目前为止,我们仅仅讨论了寄存器模型如何定义,并没有涉及到DUT中的寄存器如何进行读写操作。为了通过外部总线对DUT中的寄存器进行读写操作,我们需要向DUT 发送相关的数据类(transaction),这将在后面进行讨论。
Register Environment
前面我们介绍了如何创建寄存器模型与实际设计的寄存器对应,现在我们来看寄存器环境(register environment)的组件,以及如何对寄存器进行读写操作。
寄存器环境主要有4个重要组件:
(1)寄存器模型,用来反映DUT中寄存器的值;
(2)agent,基于总线协议将总线事务(bus transaction)驱动入DUT;
(3)adapter,将读写声明从模型转换为基于协议的总线事务,或者将总线事务转换为寄存器模型事务;
(4)predictor,了解总线活动并更新寄存器模型中的Mirrored Value以匹配DUT中寄存器的内容;
寄存器模型的前门访问(frontdoor)操作分为读和写两种。对于uvm_reg来说,存在内建的read()和write()方法来对DUT的寄存器进行读写操作,如下:
class reg_ctl extends uvm_reg;
...
endclass
reg_ctl.write (status, addr, wdata); // Write wdata to addr
reg_ctl.read (status, addr, rdata); // Read rdata from addr
无论读还是写,寄存器模型都会通过sequence创建一个uvm_reg_bus_op变量,此变量中存储着操作类型(读还是写)以及操作地址,如果是写操作还会有写入的数据。此变量中的信息要经过一个转换器(adapter)转换后交给bus_sequencer,随后交给bus_driver,最终实现前门访问的读写操作。
uvm_reg_bus_op是一个结构体(struct),其内容如下:
typedef struct {
uvm_access_e kind; // 操作的类型: UVM_READ/UVM_WRITE
uvm_reg_addr_t addr; // 总线的地址,默认64位
uvm_reg_data_t data; // 读或写的数据,默认64位
int n_bits; // 转换的位数
uvm_reg_byte_en byte_en; // 启用总线的字节使能
uvm_status_e status; // Result of transaction: UVM_IS_OK, UVM_HAS_X, UVM_NOT_OK
} uvm_reg_bus_op;
注意adpter是双向的,在其中需要定义好两个函数:一个是reg2bus,作用是将寄存器模型通过sequence发出的 uvm_reg_bus_op转换为bus_sequencer能够接受的形式;另一个是bus2reg,作用是当检测到总线上有操作时,将收集到的transaction转换为寄存器模型能够接受的形式,以便寄存器模型能够更新相应的寄存器值。
adapter通过继承uvm_reg_adapter 产生,uvm_reg_adapter中有内建的reg2bus和bus2reg两个方法,用户自定义的adapter可以覆盖这两个内建的方法。例子如下:
// 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;//如果总线协议支持字节启用,则设置为1
provides_responses = 0; //如果总线驱动程序提供了单独的响应项,则设置为1
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);//将寄存器模型 rw 转换为总线事务,也就是sequence item
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//将sequence item转换为寄存器模型 rw
`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
predictor
用来监视总线事务,连接到monitor,将从monitor获取的总线事务通过adpter转换为寄存器模型事务,并将寄存器的值映射到Mirrored Value;其定义如下:
// 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;//从此端口接收并处理BUSTYPE类型的总线事务,该分析端口连接monitor
uvm_reg_map map;//将总线地址转换为相应的寄存器或内存句柄
uvm_reg_adapter adapter;//将总线事务转换为寄存器模型事务 也就是uvm_reg_bus_op
function new (string name="uvm_reg_predictor",uvm_component parent=null)
super.new(name,parent);
endfunction
endclass
我们可以直接使用 uvm_reg_predictor作为predictor,而无需自己继承和定义派生的predictor,只需要改变所处理的总线事务类型BUSTYPE就可以了。在environment中加入predictor的步骤如下:
//1.定义一个参数化的predictor,这里处理的总线事务类型(sequence item)为bus_pkt
// Here "bus_pkt" is the sequence item sent by the target monitor to this predictor
uvm_reg_predictor #(bus_pkt) m_apb_predictor;
//2.在environment的build_phase中创建对象
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
//在connect_phase中将总线模型的uvm_reg_map连接到predictor的map、将agent的分析端口连接到predictor的分析端口,将adapter连接到predictor的adapter
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);//这里分析端口实际上连接的是monitor的
endfunction
一个完整的environment结构如下:
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; // 总线bus_agent
ral_my_design m_ral_model; // 寄存器模型
reg2apb_adapter m_apb_adapter; // adapter用于总线事务和寄存器模型事务的转换
uvm_reg_predictor #(bus_pkt) m_apb_predictor; // predictor用于将从monitor捕获的总线事务转换为寄存器模型事务
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 :: create ("m_apb_predictor", this);
m_ral_model.build ();//调用寄存器模型的build方法,用于创建寄存器对象
m_ral_model.lock_model ();
uvm_config_db #(ral_my_design)::set (null, "uvm_test_top", "m_ral_model", 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
上面调用了lock_model()方法来将寄存器模型锁定,以防止其他组件修改寄存器模型的结构;
Connecting register env
到目前为止,我们已经完成了寄存器环境的组件,但是在一个environment中会存在许多个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);//连接monitor和predictor
m_reg_env.m_ral_model.default_map.set_sequencer (m_agent.m_seqr, m_reg_env.m_reg2apb);//这个连接没弄明白?
endfunction
endclass
其中调用map中的set_sequencer()方法,是为了将map与sequencer以及adapter连接起来,其参数定义如下:
virtual function void set_sequencer (
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter = null
)
该方法必须在执行sequence之前调用!
对于寄存器,寄存器模型提供了两个基本的方法:write和read.若要在reference model中读取寄存器,则可以使用read方法.例子如下:
p_rm.invert.read(status, value, UVM_FRONTDOOR);//p_rm为寄存器块,invert为寄存器块中的寄存器
read方法的定义如下:
virtual task read(
output uvm_status_e status,
output uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0
)
通常只用前3个参数,第一个参数是 uvm_status_e型的变量,这是一个输出,用以表明读操作是否成功;第二个参数是读取的数值,也是一个输出,它是一个uvm_reg_data_t型的变量;第三个参数是读取的方式,分为前门访问(UVM_FRONTDOOR)和后门访问(UVM_BACKDOOR);
下面举一个write方法的例子:
p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);//p_rm为寄存器块,invert为寄存器
write方法的定义如下:
virtual task write(
output uvm_status_e status,
input uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0
)
通常只用前3个参数,第一个参数是 uvm_status_e型的变量,这是一个输出,用以表明读操作是否成功;第二个参数是写入的数值,是一个输入,它是一个uvm_reg_data_t型的变量;第三个参数是读取的方式,分为前门访问(UVM_FRONTDOOR)和后门访问(UVM_BACKDOOR);
UVM Register Model Example
下面的设计有以下4个寄存器:
下面的代码并不是全部的DUT,我们为了方便只写出了寄存器部分:
module traffic ( input pclk,
input presetn,
input [31:0] paddr,
input [31:0] pwdata,
input psel,
input pwrite,
input penable,
// Outputs
output [31:0] prdata);
reg [3:0] ctl_reg; // profile, blink_red, blink_yellow, mod_en RW
reg [1:0] stat_reg; // state[1:0]
reg [31:0] timer_0; // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
reg [31:0] timer_1; // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
reg [31:0] data_in;
reg [31:0] rdata_tmp;
// Set all registers to default values
always @ (posedge pclk) begin
if (!presetn) begin
data_in <= 0;
ctl_reg <= 0;
stat_reg <= 0;
timer_0 <= 32'hcafe_1234;
timer_1 <= 32'hface_5678;
end
end
// Capture write data
always @ (posedge pclk) begin
if (presetn & psel & penable)
if (pwrite)
case (paddr)
'h0 : ctl_reg <= pwdata;
'h4 : timer_0 <= pwdata;
'h8 : timer_1 <= pwdata;
'hc : stat_reg <= pwdata;
endcase
end
// Provide read data
always @ (penable) begin
if (psel & !pwrite)
case (paddr)
'h0 : rdata_tmp <= ctl_reg;
'h4 : rdata_tmp <= timer_0;
'h8 : rdata_tmp <= timer_1;
'hc : rdata_tmp <= stat_reg;
endcase
end
assign prdata = (psel & penable & !pwrite) ? rdata_tmp : 'hz;
endmodule
定义接口,接口的总线标准为APB,简单起见我们没有设置时钟块以及modport:
interface bus_if (input pclk);
logic [31:0] paddr;
logic [31:0] pwdata;
logic [31:0] prdata;
logic pwrite;
logic psel;
logic penable;
logic presetn;
endinterface
定义寄存器模型:首先定义寄存器,然后再定义寄存器块
// Register definition for the register called "ctl"
class ral_cfg_ctl extends uvm_reg;
rand uvm_reg_field mod_en; // Enables the module
rand uvm_reg_field bl_yellow; // Blinks yellow
rand uvm_reg_field bl_red; // Blinks red
rand uvm_reg_field profile; // 1 : Peak, 0 : Off-Peak
`uvm_object_utils(ral_cfg_ctl)
function new(string name = "traffic_cfg_ctrl");
super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
endfunction: new
// Build all register field objects
virtual function void build();
this.mod_en = uvm_reg_field::type_id::create("mod_en",, get_full_name());
this.bl_yellow = uvm_reg_field::type_id::create("bl_yellow",,get_full_name());
this.bl_red = uvm_reg_field::type_id::create("bl_red",, get_full_name());
this.profile = uvm_reg_field::type_id::create("profile",, get_full_name());
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
this.mod_en.configure(this, 1, 0, "RW", 0, 1'h0, 1, 0, 0);
this.bl_yellow.configure(this, 1, 1, "RW", 0, 3'h4, 1, 0, 0);
this.bl_red.configure(this, 1, 2, "RW", 0, 1'h0, 1, 0, 0);
this.profile.configure(this, 1, 3, "RW", 0, 1'h0, 1, 0, 0);
endfunction
endclass
// Register definition for the register called "stat"
class ral_cfg_stat extends uvm_reg;
uvm_reg_field state; // Current state of the design
`uvm_object_utils(ral_cfg_stat)
function new(string name = "ral_cfg_stat");
super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
this.state = uvm_reg_field::type_id::create("state",, get_full_name());
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
this.state.configure(this, 2, 0, "RO", 0, 1'h0, 0, 0, 0);
endfunction
endclass
// Register definition for the register called "timer"
class ral_cfg_timer extends uvm_reg;
uvm_reg_field timer; // Time for which it blinks
`uvm_object_utils(ral_cfg_timer)
function new(string name = "traffic_cfg_timer");
super.new(name, 32,build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
this.timer = uvm_reg_field::type_id::create("timer",,get_full_name());
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
this.timer.configure(this, 32, 0, "RW", 0, 32'hCAFE1234, 1, 0, 1);
this.timer.set_reset('h0, "SOFT");//congigure中设置的复位值为HARD复位,这里是soft复位
endfunction
endclass
将上面的寄存器整合到一个寄存器块中建立寄存器模型:
class ral_block_traffic_cfg extends uvm_reg_block;
rand ral_cfg_ctl ctrl; // RW
rand ral_cfg_timer timer[2]; // RW
ral_cfg_stat stat; // RO
`uvm_object_utils(ral_block_traffic_cfg)
function new(string name = "traffic_cfg");
super.new(name, build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
this.ctrl = ral_cfg_ctl::type_id::create("ctrl",,get_full_name());
this.ctrl.configure(this, null, "");
this.ctrl.build();
this.default_map.add_reg(this.ctrl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
this.timer[0] = ral_cfg_timer::type_id::create("timer[0]",,get_full_name());
this.timer[0].configure(this, null, "");
this.timer[0].build();
this.default_map.add_reg(this.timer[0], `UVM_REG_ADDR_WIDTH'h4, "RW", 0);
this.timer[1] = ral_cfg_timer::type_id::create("timer[1]",,get_full_name());
this.timer[1].configure(this, null, "");
this.timer[1].build();
this.default_map.add_reg(this.timer[1], `UVM_REG_ADDR_WIDTH'h8, "RW", 0);
this.stat = ral_cfg_stat::type_id::create("stat",,get_full_name());
this.stat.configure(this, null, "");
this.stat.build();
this.default_map.add_reg(this.stat, `UVM_REG_ADDR_WIDTH'hc, "RO", 0);
endfunction
endclass
// The register block is placed in the top level model class definition
class ral_sys_traffic extends uvm_reg_block;
rand ral_block_traffic_cfg cfg;
`uvm_object_utils(ral_sys_traffic)
function new(string name = "traffic");
super.new(name);
endfunction
function void build();
this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);//默认的map
this.cfg = ral_block_traffic_cfg::type_id::create("cfg",,get_full_name());
this.cfg.configure(this, "tb_top.pB0");//寄存器块的配置方法只有两个参数,第二个参数为HDL代码的层次路径
this.cfg.build();
this.default_map.add_submap(this.cfg.default_map, `UVM_REG_ADDR_WIDTH'h0);
endfunction
endclass
定义adapter转换器:
class reg2apb_adapter extends uvm_reg_adapter;
`uvm_object_utils (reg2apb_adapter)
function new (string name = "reg2apb_adapter");
super.new (name);
endfunction
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
virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
bus_pkt 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;
`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
定义agent,首先要定义sequence item、sequence、sequencer、driver和monitor
// Declare a sequence_item for the APB transaction
class bus_pkt extends uvm_sequence_item;//定义sequence item
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit write;
`uvm_object_utils_begin (bus_pkt)
`uvm_field_int (addr, UVM_ALL_ON)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (write, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name = "bus_pkt");
super.new (name);
endfunction
constraint c_addr { addr inside {0, 4, 8};}
endclass
// Drives a given apb transaction packet to the APB interface
class my_driver extends uvm_driver #(bus_pkt);//定义driver
`uvm_component_utils (my_driver)
bus_pkt pkt;
virtual bus_if vif;
function new (string name = "my_driver", uvm_component parent);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
if (! uvm_config_db#(virtual bus_if)::get (this, "*", "bus_if", vif))
`uvm_error ("DRVR", "Did not get bus if handle")
endfunction
virtual task run_phase (uvm_phase phase);
bit [31:0] data;
vif.psel <= 0;
vif.penable <= 0;
vif.pwrite <= 0;
vif.paddr <= 0;
vif.pwdata <= 0;
forever begin
seq_item_port.get_next_item (pkt);
if (pkt.write)
write (pkt.addr, pkt.data);//写
else begin
read (pkt.addr, data);//读
pkt.data = data;
end
seq_item_port.item_done ();
end
endtask
virtual task read ( input bit [31:0] addr,
output logic [31:0] data);
vif.paddr <= addr;
vif.pwrite <= 0;
vif.psel <= 1;
@(posedge vif.pclk);
vif.penable <= 1;
@(posedge vif.pclk);
data = vif.prdata;//将接口的数据读出
vif.psel <= 0;
vif.penable <= 0;
endtask
virtual task write ( input bit [31:0] addr,
input bit [31:0] data);
vif.paddr <= addr;
vif.pwdata <= data;//将数据写入到接口中
vif.pwrite <= 1;
vif.psel <= 1;
@(posedge vif.pclk);
vif.penable <= 1;
@(posedge vif.pclk);
vif.psel <= 0;
vif.penable <= 0;
endtask
endclass
// Monitors the APB interface for any activity and reports out
// through an analysis port
class my_monitor extends uvm_monitor;//定义monitor
`uvm_component_utils (my_monitor)
function new (string name="my_monitor", uvm_component parent);
super.new (name, parent);
endfunction
uvm_analysis_port #(bus_pkt) mon_ap;//定义分析端口TLM
virtual bus_if vif;
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
mon_ap = new ("mon_ap", this);
uvm_config_db #(virtual bus_if)::get (null, "uvm_test_top.*", "bus_if", vif);
endfunction
virtual task run_phase (uvm_phase phase);
fork
forever begin
@(posedge vif.pclk);
if (vif.psel & vif.penable & vif.presetn) begin
bus_pkt pkt = bus_pkt::type_id::create ("pkt");
pkt.addr = vif.paddr;
if (vif.pwrite)
pkt.data = vif.pwdata;
else
pkt.data = vif.prdata;
pkt.write = vif.pwrite;
mon_ap.write (pkt);//调用分析端口的write()方法向外数据
end
end
join_none
endtask
endclass
// The agent puts together the driver, sequencer and monitor
class my_agent extends uvm_agent;
`uvm_component_utils (my_agent)
function new (string name="my_agent", uvm_component parent);
super.new (name, parent);
endfunction
my_driver m_drvr;
my_monitor m_mon;
uvm_sequencer #(bus_pkt) m_seqr;//使用uvm_sequencer
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_drvr = my_driver::type_id::create ("m_drvr", this);
m_seqr = uvm_sequencer#(bus_pkt)::type_id::create ("m_seqr", this);
m_mon = my_monitor::type_id::create ("m_mon", this);
endfunction
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_drvr.seq_item_port.connect (m_seqr.seq_item_export);//连接driver和sequencer的分析端口
endfunction
endclass
定义寄存器模型的环境:
// Register environment class puts together the model, adapter and the predictor
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
ral_sys_traffic m_ral_model; // Register Model
reg2apb_adapter m_reg2apb; // Convert Reg Tx <-> Bus-type packets
uvm_reg_predictor #(bus_pkt) m_apb2reg_predictor; // Map APB tx to register in model
my_agent m_agent; // Agent to drive/monitor transactions
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_ral_model = ral_sys_traffic::type_id::create ("m_ral_model", this);
m_reg2apb = reg2apb_adapter :: type_id :: create ("m_reg2apb");
m_apb2reg_predictor = uvm_reg_predictor #(bus_pkt) :: type_id :: create ("m_apb2reg_predictor", this);
m_ral_model.build ();
m_ral_model.lock_model ();
uvm_config_db #(ral_sys_traffic)::set (null, "uvm_test_top", "m_ral_model", m_ral_model);
endfunction
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_apb2reg_predictor.map = m_ral_model.default_map;
m_apb2reg_predictor.adapter = m_reg2apb;
endfunction
endclass
现在我们已经有了一个寄存器环境和一个APB agent,我们在environment中将两者连接:
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);
uvm_reg::include_coverage ("*", UVM_CVR_ALL);
endfunction
// Connect analysis port of monitor with predictor, assign agent to register env
// and set default map of the register env
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_reg_env.m_agent = m_agent;
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_reg_env.m_reg2apb);
endfunction
endclass
除此之外,我们还需要一个sequence来进行复位操作,如下:
class reset_seq extends uvm_sequence;
`uvm_object_utils (reset_seq)
function new (string name = "reset_seq");
super.new (name);
endfunction
virtual bus_if vif;
task body ();
if (!uvm_config_db #(virtual bus_if) :: get (null, "uvm_test_top.*", "bus_if", vif))
`uvm_fatal ("VIF", "No vif")
`uvm_info ("RESET", "Running reset ...", UVM_MEDIUM);
vif.presetn <= 0;
@(posedge vif.pclk) vif.presetn <= 1;
@ (posedge vif.pclk);
endtask
endclass
前面建立了一个test library以用来保存一个base test,从中可以派生出子test,如下:
class base_test extends uvm_test;
`uvm_component_utils (base_test)
my_env m_env;
reset_seq m_reset_seq;
uvm_status_e status;
function new (string name = "base_test", uvm_component parent);
super.new (name, parent);
endfunction
// Build the testbench environment, and reset sequence
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_env = my_env::type_id::create ("m_env", this);
m_reset_seq = reset_seq::type_id::create ("m_reset_seq", this);
endfunction
// In the reset phase, apply reset
virtual task reset_phase (uvm_phase phase);
super.reset_phase (phase);
phase.raise_objection (this);
m_reset_seq.start (m_env.m_agent.m_seqr);
phase.drop_objection (this);
endtask
endclass
class reg_rw_test extends base_test;
`uvm_component_utils (reg_rw_test)
function new (string name="reg_rw_test", uvm_component parent);
super.new (name, parent);
endfunction
// Note that main_phase comes after reset_phase, and is performed when
// DUT is out of reset. "reset_phase" is already defined in base_test
// and is always called when this test is started
virtual task main_phase(uvm_phase phase);
ral_sys_traffic m_ral_model;
uvm_status_e status;
int rdata;
phase.raise_objection(this);
m_env.m_reg_env.set_report_verbosity_level (UVM_HIGH);
// Get register model from config_db
uvm_config_db#(ral_sys_traffic)::get(null, "uvm_test_top", "m_ral_model", m_ral_model);
// Write 0xcafe_feed to the timer[1] register, and read it back
m_ral_model.cfg.timer[1].write (status, 32'hcafe_feed);
m_ral_model.cfg.timer[1].read (status, rdata);
// Set 0xface as the desired value for timer[1] register
m_ral_model.cfg.timer[1].set(32'hface);
`uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)
// Predict that current value of timer[1] is 0xcafe_feed and check it is true
m_ral_model.cfg.timer[1].predict(32'hcafe_feed);
m_ral_model.cfg.timer[1].mirror(status, UVM_CHECK);
// Set desired value of the field "bl_yellow" in register ctrl to 1
// Then start bus transactions by calling "update" to update DUT with
// desired value
m_ral_model.cfg.ctrl.bl_yellow.set(1);
m_ral_model.cfg.update(status);
// Attempt to write into a RO register "stat" with some value
m_ral_model.cfg.stat.write(status, 32'h12345678);
phase.drop_objection(this);
endtask
// Before end of simulation, allow some time for unfinished transactions to
// be over
virtual task shutdown_phase(uvm_phase phase);
super.shutdown_phase(phase);
phase.raise_objection(this);
#100ns;
phase.drop_objection(this);
endtask
endclass
UVM Register Backdoor Access
前门访问和后面访问的区别
- 前门访问是通过物理总线向dut发起寄存器访问操作,消耗仿真时间
- 后面操作不通过物理总线,不消耗仿真时间
前门访问过程
以write为例:
- 当调用寄存器的write()任务后,产生uvm_reg_item类型的transaction:rw,之后调用uvm_reg::do_write()。
- 在uvm_reg_map中,调用reg_adapter.reg2bus将rw转换成bus driver对应的transaction。
- 把transaction交给sequencer,最终由bus driver驱动到对应的bus interface上。
- bus monitor在bus interface上检测到bus transaction 。
- reg_predictor会调用reg_adapter.bus2reg将该bus transaction转换成uvm_reg_item。
- 从driver中返回的req会转换成uvm_reg_item类型,如防止sequencer的response队列溢出,需要在adapter中设置provides_reponses.
- 寄存器模型根据返回的uvm_reg_item来更新寄存器的value,m_mirrored和m_desired三个值