参考文档链接:https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/
前言 sv实验五到uvm实验四之间所有的验证结构、组件、环境、运行、怎么开始结束,都是映射的,uvm所有的核心已经sv过度到uvm
今天我们学的寄存器结构,sv里面虽然也有但比较简陋,在uvm我们需要用相关语法去合成一个寄存器模型,利用寄存器模型我们可以做成很多东西
理论对应内容(uvm入门进阶):
寄存器模型在不同公司情况:有的可能是doc文档/excel,设计根据这些进行手动实现。不同公司方法不一样
对于这个模型,我们怎么理解、实现、集成
实战对应内容(uvm实战):
深入内容
如果公司内部模型是自动化生成的话,我们通过(第三个例子)简单脚本帮助我们生成寄存器模型
1 寄存器模型概览 1.1 概览 1) 概览 对于硬件有了解的读者,都知道寄存器是模块之间互相交谈的窗口
寄存器的作用:
一方面可以通过读出寄存器的状态,获取硬件当前的状况
另外一方面也可以通过配置寄存器,使得寄存器工作在一定的模式下
在验证的过程中,寄存器的验证也排在了验证清单的前列 ,因为只有首先保证寄存器的功能正确,才会使得硬件与硬件之间的交谈是“语义一致”的
如果寄存器配置结果与寄存器配置内容不同:
那么硬件无法工作在想要的模式下
可能无法正确反映硬件的状态
本节内容: 本节我们关于UVM寄存器模型的介绍中,会将MCDF寄存器模块简化,通过硬件的寄存器模型和总线UVC建立一个小的验证环境:
寄存器有关的设计流程
UVM寄存器模型的相关类
如何将寄存器模型集成到现有环境 ,与总线UVC桥接,与DUT模型绑定
寄存器模型的常用方法(预定义sequence更可读) 和预定义的sequence
寄存器测试/寄存器覆盖率,和功能覆盖率的实际用例
寄存器内部实现:
硬件中的各个功能模块可以由处理器来配置功能以及访问状态,而与处理器的对话即是通过寄存器的读写来实现的。
寄存器的硬件实现是通过触发器 ,而每一个比特位的触发器都对应着寄存器的功能描述 (function specification)。
一个寄存器一般由32个比特位构成 ,将单个寄存器拆分之后,又可以分为多个域(field) ,不同的域往往代表着某一项独立的功能
单个的域可能由多个比特位构成,也可能由单一比特位构成,这取决于该域的功能模式可配置的数量
而不同的域,对于外部的读写而言,又大致可以分为WO(write-only,只写),RO(read-only,只读)和RW(read and write,读写),除过这些常见的操作属性以外,还有一些特殊行为(quirky)的寄存器,例如读后擦除模式(clean-on-read,RC),只写一次模式(write-one-to-set,W1S)
MCDF的寄存器模块的描述:
MCDF的寄存器模块描述,将0×00功能寄存器 和0×10状态寄存器 位用图来表示
通常来讲,一个寄存器有32位宽,寄存器按照地址索引的关系是按字对齐的(word-align) ,上图中的寄存器有多个域,每个域的属性也可以不相同,reserved域表示的是该域所包含的比特位暂时保留以作日后功能的扩展使用,而对保留域的读写不起任何作用,即无法写入而且读出值也是它的复位值
寄存器块(register block) :上面的这些寄存器按照地址排列,即可构成寄存器列表,我们称之为
实际上,寄存器块除了包含寄存器,也可以包含存储器 ,因为它们的属性都近乎于读写功能,以及表示为同外界通信的接口
MCDF的寄存器功能模块可由这样一个register block来表示,寄存器快中有多个寄存器有机结合:
硬件与软件中的寄存器:
2)寄存器中心化管理方式
通过软件建立寄存器模型的方法如何保证与硬件寄存器的内容属性保持一致呢?
这离不开一份中心化管理的寄存器描述文件
很多公司目前在使用XML格式的寄存器描述文件 ,也有一些公司在使用Excel(CSV)或者DOC等格式来保存寄存器的描述
为什么寄存器描述应该被中心化管理呢?
因为采用单一源的管理方式可以尽量降低出现分歧和错误的可能
寄存器描述文档使用了结构化的文档描述方式 ,这也是为什么可以通过XML或者Excel(CSV)等数据结构化 的方式来实现寄存器的功能描述
在寄存器中心化中不同工程师的工作(通过数据结构化的存储方式,可以在硬件和软件开发过程中以不后方式来使用寄存器描述文档):
系统工程师 会撰写并维护寄存器描述文件,而后归置到中心化存储路径供其他工程师开发使用
硬件工程师 会利用寄存器描述文件生成寄存器硬件模块(包含各个寄存器的硬件实现和总线访问模块)
验证工程师 会利用寄存器描述文件来生成UVM寄存器模型,以供验证过程中的激励使用、寄存器测试和功能覆盖率收集
软件工程师 会利用该文件生成用于软件开发的寄存器配置的头文件(header file),从而提高软件开发的可维护性
寄存器描述文件也可以用来生成文档 ,实现更好的可读性
推荐自动化的生成方式 ,原因还包括:
手动转换会有潜在的错误 ,而且寄存器越多出现错误的可能性越大,这样会使得后期调试的难度更大
一个广义的寄存器生成器(register generator),应该依据统一格式的寄存器描述文件,来生成UVM寄存器模型(为验证),或者硬件寄存器模块(被集成到设计中)
一个稳定的寄存器不但可以保证从文本信息到寄存器模型的无错误转换,还可以在转换过程中通过语义检查发现寄存器描述文件违规的情况帮助修正寄存器描述文件内容
如果寄存器描述文件内容有更新,寄存器生成器可以再次生成需要的相关文件格式,这对于流程化作业非常方便
对于验证所需的寄存器模型而言,一个更有效的做法是可以通过封装已有的寄存器生成器,使得可以通过指定多个寄存器模块和其对应基地址 ,继而生成一个层次化的寄存器模型,从而将系统级的寄存器模型归纳在一起,便于操作管理
1.2 UVM寄存器 进入编写代码阶段,学会如何使用这些类构建模型、自动生成模型
1)uvm_reg相关概念 不需要理解每一个方法,重点在集成
uvm_reg_field
uvm_reg对应每一个寄存器,内部包含uvm_reg_field
uvm_mem包含多个uvm_reg
uvm_reg_block包含多个uvm_mem,uvm_reg_map,uvm_reg
2)代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ctrl_reg extends uvm_reg; `uvm_object_utils (ctrl_reg) //注册为uvm_object 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 ("reserved" ); pkt_len = uvm_reg_field::type_id::create ("pkt_len" ); prio_level = uvm_reg_field::type_id::create ("prio_level" ); chnl_en = uvm_reg_field::type_id::create ("chnl_en" ); 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 (stat_reg) 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 ("reserved" ); fifo avail = uvm reg field::type id::create ("fifo avail" ); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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 = "mcdf rgm" ); super .new (name, UVM NO_COVERAGE) ; endfunction virtual function build (); chnl0_ctrl_reg = ctrl_reg::type_id::create ("chnl0_ctrl reg" ); chnl0_ctrl_reg.configure (this ); chnl0_ctrl_reg.build (); chnl1_ctrl_reg = ctrl_reg::type_id::create ("chn11_ctrl_reg" ); chnl1_ctrl_reg.configure (this ); chnl1_ctrl_reg.build (); chnl2_ctrl_reg = ctrl_reg::type_id::create ("chn12_ctrl_reg" ); chnl2_ctrl_reg.configure (this ); chnl2_ctrl_reg.build (); chnl0_stat_reg = stat_reg::type_id::create ("chn10_stat_reg" ); chnl0_stat_reg.configure (this ); chnl0_stat reg .build (); chnl1_stat_reg = stat_reg::type_id::create ("chnl1_stat_reg" ); chnl1_stat_reg.configure (this ); chnl1 stat_reg.build (); chnl2 _ stat_reg = stat_reg::type_id::create ("chn12_stat_reg" ); chnl2_stat_reg.configure (this ); chnl2_stat_reg.build (); map = create_map ("map" , 'h0 , 4 , UVM LITTLE ENDIAN); map.add_reg (chnl0_ctrl_reg, 32'h00000000 , "RW" ); map.add_reg (chnl1_ctrl_reg, 32'h00000004 , "RW" ); map.add_reg (chnl2_ctrl_reg, 32'h00000008 , "RW" ); map.add_reg (chn10_stat_reg, 32'h00000010 , "RO" ); map.add_reg (chn11_stat_reg, 32'h00000014 , "RO" ); map.add_reg (chn12_stat_reg, 32'h00000018 , "RO" ); lock model (); endfunction endclass : mcdf_rgm
3)基本要点总结
在定义单个寄存器时 :需要将寄存器的各个域整理出来 ,在创建之后还应当通过uvm_reg_field::configure() 函数来进一步配置各自属性
在定义uvm_reg_block时 :读者需要注意reg_block与uvm_mem、uvm_reg以及uvm_reg_map的包含关系 。首先uvm_reg和uvm_mem分别对应着硬件中独立的寄存器或者存储,而一个uvm_reg_block可以用来模拟一个功能模块的寄存器模型,其中可以容纳多个uvm_reg和uvm_mem实例 ;其次map 的作用一方面用来表示寄存器和存储对应的偏移地址,同时由于一个reg_block可以包含多个map,各个map可以分别对应不同总线或者不同地址段。在reg_block中创建了各个uvm_reg之后,需要调用uvm_reg::configure()去配置各个uvm_reg实例的属性
uvm_reg_map 也会在uvm_reg_block中例化,在例化之后需要通过uvm_reg_map::add_reg()函数来添加各个uvm_reg对应的偏移地址和访问属性等。只有规定了这些属性,才可以在稍后的前门访问(frontdoor )中给出正确的地址
uvm_reg_block 可以对更大的系统做寄存器建模,这意味着uvm_reg_block之间也可以存在层次关系,上层uvm_reg_block的uvm_reg_map可以添加子一级uvm_reg_block的uvm_reg_map,用来构建更全局的“版图”,继而通过uvm_reg_block与uvm_reg_map之间的层次关系来构建更系统的寄存器模型
为什么这里需要手动调用Build:
不同于comp,obj没有phase机制的自动化,必须手动调用build
4)模型使用流程
那么当拥有一个寄存器模型之后它接下来的使用步骤是什么呢?实际上对于不同的角色,他们对寄存器模型也有着不同的关注,譬如VIP开发者主要关注实现总线适配器,TB开发者关心如何将总线适配器与寄存器模型连接
不管对于什么角色,寄存器模型从一开始的寄存器描述文档到最后的功能检查,都需要贯穿寄存器模型的生命周期
对于验证中的不同角色,他们也会参与上述的部分流程
系统工程师需要提供寄存器描述文件
模块验证人员需要生成寄存器模型
VIP开发人员需要提供总线适配器
TB构建人员(与模块验证人员有时候不是同一个人)需要集成寄存器模型
模块验证人员还需要完成后续的寄存器模型检查和功能覆盖率收集
2 寄存器模型集成 2.1 总线UVC的实现 1)概述
MCDF访问寄存器的总线接口时序较为简单。控制寄存器接口首先需要在每一个时钟解析cmd。
当cmd为写指令时,即需要把数据cmd_data_in写入到cmd_addr对应的寄存器中。
当cmd为读指令时,即需要从cmd_addr对应的寄存器中读取数据,在下一个周期,cmd_addr对应的寄存器数据被输送至cmd_data_out接口。
我们给出一段8位地址线,32位数据线的总线UVC实现代码
2)总线UVC代码示例 class mcdf_bus_trans extends uvm_sequence_item; rand bit [1 :0 ]cmd; rand bit [7 :0 ] addr; rand bit [31 :0 ]wdata; bit [31 :0 ]rdata; `uvm_object_utils_begin(mcdf _bus_trans) ... `uvm_object_utils_end ...endclass
class mcdf bus sequencer extends uvm sequencer; virtual mcdf if vif; `uvm_component_utils(mcdf bus sequencer) ... function void build phase(uvm phase phase); if (!uvm config db#(virtual mcdf_if)::get(this,"","vif",vif)) begin `uvm_error("GETVIE","no virtual interface is assigned") end endfunction endclass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class mcdf bus monitor extends uvm monitor; virtual mcdf if vif; uvm_analysis_port #(mcdf bus trans) ap; `uvm_component_utils(mcdf bus monitor) ... function void build phase(uvm phase phase); if (!uvm config db#(virtual mcdf if)::get(this,"","vif",vif)) begin `uvmerror("GETVIF","no virtual interface is assigned") end ap=new ("ap" ,this ); endfunction task run_phase(uvm phase phase); forever begin mon_trans(); 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 e(posedge vif. clk); #10 ps; t.rdata =vif.rdata ; ap.write (t); end join_none end endtask endclass :mcdf_bus_monitor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class mcdf bus driver extends uvm driver; virtual mcdf_if vif; `uvm_component_utils(mcdf bus driver) function void build phase(uvm phase phase); if (! uvm config db#(virtual mcdf if):: get(this,"","vif", vif)) begin `uvm_error("GETVIE","no virtual interface is assigned") end endfunction task run phase(uvm phase phase); REQ tmp; mcdf_bus_trans req, rsp; reset_listener(); forever begin seg_item_port.get_next_item (tmp); void '($cast (rea, tmp)); `uvm_info("DRV",$sformatf("got a item \n %s", req. sprint()), UVM LOW) void '($cast (rsp, req. clone())); rsp. set_sequence_id(req. get_sequence_id()); rsp. set_transaction_id(req. get_transaction_id()); drive_bus(rsp); seg_item_port. item_done(rsp); `uvm_info("DRV",$sformatf("sent a item \n %s", rsp.sprint()), UVM LOW) end endtask task reset listener(); forkforkforever beginforever begin e(negedge vif. rstn) drive idle(); endendjoin nonejoin none endtaskendtask task drive bus(mcdf bus trans t);case (t. cmd) WRITE: drive write(t); READ: drive read(t); IDLE: drive idle(1 ); default : uvm error("DRIVE" ,"invalid mcdf command type received!" )endcase endtask4 task drive write(mcdf bus_trans t); e(posedge vif. clk); vif. cmd<=t. cmd; vif. addr <=t. addr; xvif. addr <=t. addr;x vif. wdata<=t. wdata; endtask
+