目录
- 寄存器:硬件模块之间交谈的窗口,读出reg状态获取硬件当前的状态,配置reg使其工作在一定模式下
- 通过硬件的reg模型和总线UVC建立一个验证环境
- reg有关的设计流程
- reg model相关类
- 将reg model集成到现有环境,与总线UVC桥接,与dut模型绑定
- reg model常用方法和预定义的seq
- reg测试和功能覆盖率
- reg的域:WO,RO,RW,RC读后擦除clean_on_read,W1S只写一次write_one_to_set
- reg按照地址索引的关系是按字对齐
- 寄存器列表(块):将寄存器按照地址排列
- 寄存器的中心化管理:保证通过软件建立reg模型与硬件reg的内容属性保持一致
uvm_reg
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
uvm_reg_field reserved;
rand uvm_reg_field pkt_len;//声明域,且可以随机化
rand uvm_reg_field prio_level;
rand uvm_reg_field chnl_en;
function new(string name ="ctrl_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function build();
reserved = uvm_reg_field::type_id::create("");
pkt_len = uvm_reg_field::type_id::create("");
prio_level = uvm_reg_field::type_id::create("");
chnl_en = uvm_reg_field::type_id::create("");//例化
reserved.configure(this, 26, 6, "RO", 0, 26'h0, 1, 0 , 0);//配置
pkt_len.configure(this,3,3,"RW",0,3'h0,1,1,0);
prio_level.configure(this,2,1,"RW",0,2'h3,1,1,0);
chnl_en.configure(this,1,0,"RW",0,1'h0,1,1,0);
endfunction
endclass
class stat_reg extends uvm_reg;
`uvm_object_utils()
uvm_reg_field reserved;//保留位的域
rand uvm_reg_field fifo_avail;
function new(string name="stat_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function build();
reserved = uvm_reg_field::type_id::create("");
fifo_avail= uvm_reg_field::type_id::create("");
reserved.configure(this,24,8,"RO",0,24'h0,1,0,0);
fifo_avail.configure(this,8,0,"RO",0,8'h0,1,1,0);
endfunction
endclass
class mcdf_rgm extends uvm_reg_block;
`uvm_object_utils(mcdf_rgm)
rand ctrl_reg chnl0_ctrl_reg;
rand ctrl_reg chnl1_ctrl_reg;
rand ctrl_reg chnl2_ctrl_reg;
rand stat_reg chnl0_stat_reg;
rand stat_reg chnl1_stat_reg;
rand stat_reg chnl2_stat_reg;
uvm_reg_map map;
function new(string name ="")
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function build();
chnl0_ctrl_reg=ctrl_reg::type_id::create("");
chnl0_ctrl_reg.configure(this);//创建,配置,
chnl0_ctrl_reg.build();
chnl1_ctrl_reg=ctrl_reg::type_id::create("");
chnl1_ctrl_reg.configure(this);
chnl1_ctrl_reg.build();
chnl2_ctrl_reg=ctrl_reg::type_id::create("");
chnl2_ctrl_reg.configure(this);
chnl2_ctrl_reg.build();
chnl0_stat_reg=stat_reg::type_id::create("");
chnl0_stat_reg.configure(this);
chnl0_stat_reg.build();
chnl1_stat_reg=stat_reg::type_id::create("");
chnl1_stat_reg.configure(this);
chnl1_stat_reg.build();
chnl2_stat_reg=stat_reg::type_id::create("");
chnl2_stat_reg.configure(this);
chnl2_stat_reg.build();
map=create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);//map名字,基地址,位宽,endianess
map.add_reg(chnl0_ctrl_reg, 32'h00000000, "RW");//偏移地址offset addr
map.add_reg(chnl1_ctrl_reg, 32'h00000004, "RW");//完整reg地址addr=base addr+offset addr
map.add_reg(chnl2_ctrl_reg, 32'h00000008, "RW");
map.add_reg(chnl0_stat_reg, 32'h00000010, "RO");//添加每个reg的偏移地址和访问属性(模式)
map.add_reg(chnl1_stat_reg, 32'h00000014, "RO");
map.add_reg(chnl2_stat_reg, 32'h00000018, "RO");
lock_model();//reg构建后锁住这个模型,不允许外部访问寄存器内部,只可以用预测组件来改变
endfunction
endclass
- 寄存器模型可以用来写一些激励
寄存器模型集成
总线UVC的实现
- 控制reg接口在每一个clk解析cmd:写,读
- 以下是8位地址线和32位数据线的总线UVC
class mcdf_bus_trans extends uvm_sequence_item;
rand bit[1:0] cmd;
rand bit[7:0] addr;
rand bit[31:0] data;
bit[31:0] rdata;//从总线读回来的,不可随机化
`uvm_object_utils_begin()...//注册以及域的自动化
...endclass
class mcdf_bus_sequencer extends uvm_sequencer;
virtual mcdf_if vif;
`uvm_component_utils()
...//注册,例化
function void build_phase(u p);
if(!uvm_config_db#(virtual mcdf_if)::get(this, "", "vif", vif))begin
`uvm_error("", "") end
endfunction
endclass
class mcdf_bus_monitor extends uvm_monitor;//等下连接到uvm_reg_predictor
virtual mcdf_if vif;
uvm_analysis_port #(mcdf_bus_trans) ap;//monitor要广播
`uvm_component_utils()
...
function void build_phase(u p);
if(!uvm_config_db#(virtual mcdf_if)::get(this, "", "vif", vif))begin
`uvm_error("", "") end
ap =new("", this);//不能用create,因为port不是object
endfunction
task run_phase(u p);
forever begin
mon_trnas() end
endtask
task mon_trans();
mcdf_bus_trans t;
@(posedge vif.clk);
if(vif.cmd==`WRITE) begin
t=new();
t.cmd=`WRITE;
t.addr=vif.addr;
t.wdata=vif.wdata;
ap.write(t);
end
else if(vif.cmd==`READ) begin
t=new();
t.cmd=`READ;
t.addr=vif.addr;
fork begin//等下一个周期
@(posedge vif.clk);
#10ps;
t.rdata=vif.rdata;
ap.write(t); end
join_none//的同时不错过下一拍
end
endtask
endclass
class mcdf_bus_driver extends uvm_driver;
virtual mcdf_if vif;
...//注册+例化
function void build_phase(u p);
if(!uvm_config_db#(virtual mcdf_if)::get(this, "", "vif", vif))begin
`uvm_error("", "") end
endfunction
task run_phase(u p);
REQ tmp;
mcdf_bus_trans req, rsp;
reset_listener();
forever begin
seq_item_port.get_next_item(tmp);
void'($cast(req,tmp));
`uvm_info("got")
void'($cast(rsp, req.clone()));
rsp.set_sequence_id(req.get_sequence_id());
rsp.set_transaction_id(req.get_transaction_id());
driver_bus(rsp);
set_item_port.item_done(rsp);
`uvm_info("sent")
end
endtask
task reset_listener();
fork
forever begin
@(negdege vif.rstn) drive_idle();
end
join_none
endtask
task drive_bus(mcdf_bus_trans t);
case (t.cmd)
`WRITE:drive_write(t);
`READ :drive_read(t);
`IDLE :drive_idle();
default: `uvm_error()
endcase
endtask
task drive_write(mcdf_bus_trans t);
@(posedge vif.clk);
vif.cmd<=t.cmd;
vif.addr<=t.addr;
vif.data<=t.wdata;
endtask
task drive_read(mcdf_bus_trans t);
@(posedge vif.clk);
vif.cmd<=t.cmd;
vif.addr<=t.addr;
@(posedge vif.clk);
#10ps;
t.rdata=vif.rdata;
endtask
task drive_idle(bit is_sync=0);
if(is_sync) @(posedge vif.clk);
vif.cmd<='h0;
vif.addr<='h0;
vif.data<='h0;
endtask
endclass
class mcdf_bus_agent extends uvm_agent;
mcdf_bus_driver driver;
mcdf_bus_sequencer sequencer;
mcdf_bus_monitor monitor;
...
function void build_phase(u p);
driver =mcdf_bus_driver::type_id::create("", this);
sequencer=mcdf_bus_sequencer::type_id::create("", this);
monitor =mcdf_bus_monitor::type_id::create("", this);
endfunction
function void connect_phase(u p);
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
endclass
硬件reg
module ctrl_regs();
还差个adapter和predictor
class ref2mcdf_adapter extends uvm_reg_adapter;
`uvm_object_utils()
function new(string name ="");
super.new(name);
provides_responses=1;//enable了provides_responses,总线可以返回rsp的数据
endfunction
//预定义的,也是必须实现的,名字一个字都不能差
function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);
mcdf_bus_trans t=mcdf_bus_trnas::type_id::create("t");//创建子类对象
t.cmd=(rw.kind == UVM_WRITE)? `WRITE : `READ;
t.addr=rw.addr;
t.wdata=rw.data;
return t;//做了隐式转换
endfunction//完成了寄存器级别操作rw到bus上的桥接,下面的func反之
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
//从driver将数据写会sequencer,adapter从sqr拿到rsp(t)后自动调用
mcdf_bus_trans t;
if(!$cast(t, bus_item)) begin//父类句柄,转换成子类mcdf_bus_trans句柄
`uvm_fatal("", "")
return; end
rw.kind=(t.cmd==`WRITE)? UVM_WRITE : UVM_READ;//转换后才可以访问子类对象
rw.addr=t.addr;
rw.data=(t.cmd==`WRITE)? t.wdata : t.rdata;
rw.status=UVM_IS_OK;
endfunction
endclass
uvm_reg_bus_op包含这些成员变量,要转换成mcdf_bus_trans
如何将adapter集成到环境中?
- 从test层传入寄存器模型rgm句柄,在顶层例化后通过uvm_config_db进行配置
- rgm在创建后要调用build()函数,因为uvm_reg_block是object类型,其预定义的build()不会自动执行
- 这里由于还没集成predictor,所以调用set_auto_predict(),采用auto prediction方式
- 顶层连接时,需要将rgm的map组件和bus sequencer和adapter连接(reg信息-总线侧激励驱动-reg级别和硬件总线级别的桥接),adapter的桥接功能才可以工作
class mcdf_bus_env extends uvm_env; mcdf_bus_agent agent; mcdf_rgm rgm; reg2mcdf_adapter reg2mcdf; ...//注册+例化 function void build_phase(u p); agent=mcdf_bus_agent::type_id::create("", this); if(!uvm_config_db#(mcdf_rgm)::get(this, "", "rgm", rgm)) begin `uvm_info() rgm=mcdf_rgm::type_id::create("", this); end rgm.build();//创建,配置reg,调用各个reg的build,以及它们的field rgm.map.set_auto_predict(); reg2mcdf=reg2mcdf_adapter::type_id::create(""); endfunction function void connect_phase(u p); rgm.map.set_sequencer(agent.sequencer, reg2mcdf);//!连接 endfunction endclass class test1 extends uvm_test; mcdf_rgm rgm; mcdf_bus_env env; ...//注册+例化 function void build_phase(u p); rgm=mcdf_rgm::type_id::create("", this); uvm_config_db#(mcdf_rgm)::set(this, "env*", "rgm", rgm) env=mcdf_bus_env::type_id::create("", this); endfunction task run_phase(u p); ... endclass
寄存器访问方式
前门访问
- 在rgm上做读写操作,最终会通过总线UVC来实现总线上的物理时序访问
class mcdf_example_seq extends uvm_reg_sequence;
mcdf_rgm rgm;
`uvm_object_utils()
`uvm_declare_p_sequencer(mcdf_bus_sequencer)
...
task body();
uvm_status_e status;
uvm_reg_data_t data;
if(!uvm_config_db#(mcdf_rgm)::get(null, get_full_name(), "rgm", rgm)) begin
`uvm_error("", "") end
//方式1:uvm_reg::read()/write()
rgm.chnl0_ctrl_reg.read (status, data, UVM_FRONTDOOR, .parent(this));
rgm.chnl0_ctrl_reg.write(status, 'h11, UVM_FRONTDOOR, .parent(this));
rgm.chnl0_ctrl_reg.read (status, data, UVM_FRONTDOOR, .parent(this));
//方式2:uvm_reg_sequence::read_reg()/write_reg()
read_reg (rgm.chnl1_ctrl_reg.read, status, data, UVM_FRONTDOOR);
write_reg (rgm.chnl1_ctrl_reg.read, status, 'h22, UVM_FRONTDOOR);
read_reg (rgm.chnl1_ctrl_reg.read, status, data, UVM_FRONTDOOR);
endtask
endclass
后门访问
- 利用UVM DPI (uvm_hdl_read()和uvm_hdl_deposit()),将reg的操作直接作用到DUT内的寄存器变量,不通过物理总线访问,不是真实的硬件行为,速度快不消耗时间
- 首先需要确保rgm在建立时是否将各个reg映射到DUT一侧的HDL路径,如下
class mcdf_rgm extends uvm_reg_block;
...//reg成员,map声明
virtual function build();
...//创建
add_hdl_path("reg_backdoor_access,dut");
chnl0_ctrl_reg.add_hdl_path_slice($sformat("regs[%0d]", `SLV0_RW_REG), 0,32);
chnl1_ctrl_reg.add_hdl_path_slice($sformat("regs[%0d]", `SLV1_RW_REG), 0,32);
chnl2_ctrl_reg.add_hdl_path_slice($sformat("regs[%0d]", `SLV2_RW_REG), 0,32);
chnl0_stat_reg.add_hdl_path_slice($sformat("regs[%0d]", `SLV0_R_REG), 0,32);
chnl1_stat_reg.add_hdl_path_slice($sformat("regs[%0d]", `SLV1_R_REG), 0,32);
chnl2_stat_reg.add_hdl_path_slice($sformat("regs[%0d]", `SLV2_R_REG), 0,32);
lock_model();//结束地址映射关系,保证model不会被其他用户修改
endfunction
endclass
class mcdf_example_seq extends uvm_reg_sequence;
mcdf_rgm rgm;
`uvm_object_utils()
`uvm_declare_p_sequencer(mcdf_bus_sequencer)
...
task body();
uvm_status_e status;
uvm_reg_data_t data;
if(!uvm_config_db#(mcdf_rgm)::get(null, get_full_name(), "rgm", rgm)) begin
`uvm_error("", "") end
//read()/write()
rgm.chnl0_ctrl_reg.read (status, data, UVM_BACKDOOR, .parent(this));
rgm.chnl0_ctrl_reg.write(status, 'h11, UVM_BACKDOOR, .parent(this));
rgm.chnl0_ctrl_reg.read (status, data, UVM_BACKDOOR, .parent(this));
rgm.chnl1_ctrl_reg.peek (status, data, .parent(this));
rgm.chnl1_ctrl_reg.poke (status, 'h22, .parent(this));
rgm.chnl1_ctrl_reg.peek (status, data, .parent(this));
//peek()/poke()
read_reg (rgm.chnl2_ctrl_reg.read, status, data, UVM_BACKDOOR);
write_reg (rgm.chnl2_ctrl_reg.read, status, 'h22, UVM_BACKDOOR);
read_reg (rgm.chnl2_ctrl_reg.read, status, data, UVM_BACKDOOR);
peek_reg (rgm.chnl2_ctrl_reg, status, data);//peek读取reg
poke_reg (rgm.chnl2_ctrl_reg, status, 'h33);//poke修改reg
peek_reg (rgm.chnl2_ctrl_reg, status, data);
endtask
endclass
二者比较如下