IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> UVM:寄存器模型 集成 -> 正文阅读

[游戏开发]UVM:寄存器模型 集成

构建了寄存器模型之后,那如何将寄存器模型集成到UVM的验证框架中去呢?


1. 总线适配器 adapter

就是说,有了寄存器块reg_block类还不行,还要写一个适配器adapter类。

因为对寄存器模型操作的transaction是uvm_reg_bus_op类的

该类与driver驱动dut的总线bus_trans不一样,uvm_reg_bus_op具有更高的可读性,所以需要一个adapter来作trans类型转换。

1.1. uvm_reg_adapter

例子

class my_adapter extends uvm_reg_adapter;
    `uvm_object_utils(my_adapter)
   function new(string name="my_adapter");
      super.new(name);
      provides_responses = 1;
   endfunction

   function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
      bus_trans tr;
      tr = bus_trans::type_id::create("tr"); 
      tr.addr = rw.addr;
      tr.bus_op = (rw.kind == UVM_READ) ? BUS_RD: BUS_WR;
      if (tr.bus_op == BUS_WR)
         tr.wr_data = rw.data; 
      return tr;
   endfunction

   function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
      bus_trans tr;
      void'($cast(tr, bus_item));
      rw.kind = (tr.bus_op == BUS_RD) ? UVM_READ : UVM_WRITE;
      rw.addr = tr.addr;
      rw.byte_en = 'h3;
      rw.data = (tr.bus_op == BUS_RD) ? tr.rd_data : tr.wr_data;
      rw.status = UVM_IS_OK;
   endfunction
endclass

有一些重要方法和属性

uvm_reg_adapter::reg2bus() 与 uvm_reg_adapter::bus2reg()

这两个方法是必须要实现,因为adapter要实现两种transaction格式的相互转换。

//...\questasim64_2020.1\verilog_src\uvm-1.2\src\reg\uvm_reg_adapter.svh
  pure virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
  pure virtual function void bus2reg(uvm_sequence_item bus_item,
                                     ref uvm_reg_bus_op rw);

uvm_reg_bus_op

该类是寄存器模型的访问transaction格式,从例子中可以看到需要访问这里面的很多成员以转换为bus_trans

//...\questasim64_10.6c\verilog_src\uvm-1.2\src\reg\uvm_reg_item.svh
typedef enum{UVM_READ, UVM_WRITE} uvm_access_e;
typedef enum{UVM_IS_OK, UVM_IS_X,UVM_NOT_OK} uvm_status_e;

typedef struct{
	uvm_reg_addr_t	addr;			//寄存器地址,默认64bit
	uvm_reg_data_t	data;			//寄存器数据,默认64bit
	uvm_access_e	kind;			//存取种类
	int n_bits;						//传输的bit
	uvm_reg_byte_en_t byte_en;		//byte操作使能
	uvm_status_e	status;			//
} uvm_reg_bus_op;

2. 寄存器模型 集成

需要说明的是,寄存器模型并不是用来去改变reference model的,而是用来使用可读性更高的方式对dut的寄存器模块进行控制的。

比起以往将sequence直接产生bus_trans的方式送至sequencer,接下来我们将使用reg_block与reg_adapter向sequencer发送bus_trans。

先介绍如何将reg_block与reg_adapter进行连接。

uvm_reg_map::set_sequencer(sqr,adapter)

reg_block.default_map不仅可以实现寄存器块、寄存器的地址和读写控制,还可将reg_block与reg_adapter进行连接,并挂载sequencer。

看源码

//...\questasim64_10.6c\verilog_src\uvm-1.2\src\reg\uvm_reg_map.svh
function void uvm_reg_map::set_sequencer(uvm_sequencer_base sequencer,
                                         uvm_reg_adapter adapter=null);
	//...
   m_sequencer = sequencer;
   m_adapter = adapter;
endfunction

好了,现在将reg_block和reg_adapter绑在一起了,接下来的问题就是将这俩东西放在验证平台的哪个地方。

2.1. 集成于reg_sequence(个人思路)

reg_block与adapter是绑定的,这俩是例化在env内呢?还是test内呢?还是其他什么地方?

笔者个人的思路是放在reg_sequence内

原因很简单:尽量将reg_block与reg_adapter在sequence的封装下,这样来套用原来virtual sequence的思路。

而且反正最后都要给sequencer发一个bus_trans,而且二者还都是object类型的,容易理解。

uvm_reg_sequence

uvm_reg_sequence有针对reg和mem独有的读、写等方法,可以从uvm_reg_sequence继承一个新的类reg_sequence

注意读的数据不是寄存器模型的数据!!!是dut中寄存器的数据!!!所以根据不同的总线会产生不同的耗时!!!

本质上是driver在生成rsp时的耗时,因为reg_sequence是通过接受driver的反馈trans,即rsp判断读写是否成功的!!!
所以如果driver的run_phase写错了,可能会给reg_sequence带来错误!

那么有个问题:如何判断寄存器模型中的数据和dut寄存器的数据是一致的?

//...\questasim64_10.6c\verilog_src\uvm-1.2\src\reg\uvm_reg_sequence.svh
virtual task write_reg(		input  uvm_reg           rg,						//寄存器句柄
                          output uvm_status_e      status,						//访问状态
                          input  uvm_reg_data_t    value,						//结果
                          input  uvm_path_e        path = UVM_DEFAULT_PATH,		//UVM_FRONTDOOR还是UVMEE UVM_BACKDOOR
                          input  uvm_reg_map       map = null,
                          input  int               prior = -1,
                          input  uvm_object        extension = null,
                          input  string            fname = "",
                          input  int               lineno = 0);
      if (rg == null)
        `uvm_error("NO_REG","Register argument is null")
      else
        rg.write(status,value,path,map,this,prior,extension,fname,lineno);		//其实还是调用的uvm_reg::write()
endtask
                          

   virtual task read_reg(input  uvm_reg           rg,
                         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  int               prior = -1,
                         input  uvm_object        extension = null,
                         input  string            fname = "",
                         input  int               lineno = 0);
  
   virtual task poke_reg(input  uvm_reg           rg,
                         output uvm_status_e      status,
                         input  uvm_reg_data_t    value,
                         input  string            kind = "",
                         input  uvm_object        extension = null,
                         input  string            fname = "",
                         input  int               lineno = 0);
   virtual task peek_reg(input  uvm_reg           rg,
                         output uvm_status_e      status,
                         output uvm_reg_data_t    value,
                         input  string            kind = "",
                         input  uvm_object        extension = null,
                         input  string            fname = "",
                         input  int               lineno = 0);
   virtual task update_reg(input  uvm_reg           rg,
                           output uvm_status_e      status,
                           input  uvm_path_e        path = UVM_DEFAULT_PATH,
                           input  uvm_reg_map       map = null,
                           input  int               prior = -1,
                           input  uvm_object        extension = null,
                           input  string            fname = "",
                           input  int               lineno = 0);
   virtual task mirror_reg(input  uvm_reg       rg,
                           output uvm_status_e  status,
                           input  uvm_check_e   check  = UVM_NO_CHECK,
                           input  uvm_path_e    path = UVM_DEFAULT_PATH,
                           input  uvm_reg_map   map = null,
                           input  int           prior = -1,
                           input  uvm_object    extension = null,
                           input  string        fname = "",
                           input  int           lineno = 0);

例码

具体实现方式见下面例码,注意reg_seq中reg_block和reg_adapter的例化和挂载都在reg_seq.body()中实现了,所以使用使只需调用一次`uvm_do_on即可。

在这里插入图片描述

class reg_sequence extends uvm_reg_sequence;	
	`uvm_object_utils(reg_sequence)
	my_rgm rgm;
	my_adapter adapter;
	task body();
		rgm = my_rgm::type_id::create("rgm");
		rgm.build();
		adapter = my_adapter::type_id::create("adapter");
		rgm.default_map.set_auto_predict(1);				//自动预测,后文讲
		rgm.default_map.set_sequencer(m_sequencer,adapter);			//连接sequencer与adapter
		rgm.reset();										//复位
	endtask
	
endclass
class base_virtual_sequence extends uvm_sequence;
	//...
	`uvm_declare_p_sequencer(virtual_sequencer)
	reg_sequence reg_seq;
	task body();
		do_reg();
		//...
	endtask
	virtual task do_reg();
	endtask
	
endclass
class consistence_basic_virtual_sequence extends base_virtual_sequence;
	//...
	task do_reg();
	  bit[31:0] wr_val, rd_val;
      uvm_status_e status;
      // slv0 with len=8,  prio=0, en=1
      wr_val = (1<<3)+(0<<1)+1;
	  `uvm_do_on(reg_seq,p_sequencer.reg_sqr)									//例化seq + 例化rgm + adapter + 挂载
      reg_seq.write_reg(reg_seq.rgm.chnl0_ctrl_reg,status,wr_val,UVM_FRONTDOOR);		
      reg_seq.read_reg(reg_seq.rgm.chnl0_ctrl_reg,status,rd_val,UVM_FRONTDOOR);
      void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
      //...
	endtask
endclass

注意个人非常想在consistence_basic_virtual_sequence::do_reg();里将读写寄存器用一句话uvm_do_on_with(req_seq,p_sequencer.reg_sqr,{kind == UVM_READ; reg == reg_seq.rgm.ctrl_reg;})等等
但是不行,因为uvm_do是含有uvm_create的,所以每次调用都回重新创建以一个新的reg_sequence,进而reg_block每次都是新创建的。

2.2. 集成于test,再向底层配置

来源于参考资料的思路:在test的build_phase中例化并通过config机制配置到env.rgm和env.adapter中,之后在env的build_phase中接收,

之后在connect_phase执行reg_block.default_map.set_sequencer()方法,并配置到virtual_sequencer.rgm句柄中。

访问的话,在virtual_sequence中通过p_sequencer.rgm执行访问。

笔者认为这个过程略微复杂,不易理解。

3. 寄存器模型 访问

将reg_block、reg_adapter和sequencer蓝上了,那么如何对寄存器模型进行访问以实现对dut的硬件寄存器进行访问呢?

3.1. 前门访问

前门访问是指通过总线(APB协议、OPB协议、I2C协议等)上的通过时序对dut的寄存器进行读写访问。

实际上在2.1.节中的consistence_basic_virtual_sequence ::do_reg();中就已经实现了前门访问,是借助uvm_reg_sequence::write_reg()uvm_reg_sequence::read_reg()实现的。

访问流程

可以从源码中查看整个访问流程,这里简单说一下:本质上还是调用的uvm_reg::write()uvm_reg::read()实现的

内部产生一个uvm_reg_item rw的变量,记载着访问的属性等内容,然后将它传给reg_sequence.rgm.default_map

别忘了default_map已经与adapterreg_sequencer建立了连接,然后default_map调用

//...\questasim64_2020.1\verilog_src\uvm-1.2\src\reg\uvm_reg_map.svh
task uvm_reg_map::do_write(uvm_reg_item rw);
	//...
	do_bus_write(rw, sequencer, adapter);
	//...
endtask

task uvm_reg_map::do_bus_write (uvm_reg_item rw,
                                uvm_sequencer_base sequencer,
                                uvm_reg_adapter adapter);
	//... 
	  uvm_reg_bus_op rw_access=accesses[i];  
      uvm_sequence_item bus_req;
      
      bus_req = adapter.reg2bus(rw_access);
	  //...
      rw.parent.start_item(bus_req,rw.prior);				//rw.parent就是reg_sequence
      rw.parent.finish_item(bus_req);
      bus_req.end_event.wait_on();

      if (adapter.provides_responses) begin
        uvm_sequence_item bus_rsp;
        rw.parent.get_base_response(bus_rsp);
        adapter.bus2reg(bus_rsp,rw_access);
      end
      //...
endtask

adapter.reg2bus()reg_sequence.start_item()reg_sequence.finish_item()adapter.bus2reg()以实现trans的发送反馈和格式转化。

显然trans反馈需要根据dut中总线协议来的,所以一定会耗时。

3.2. 后门访问

后门访问是指直接访问dut内部的寄存器变量。其实就是把dut里的reg变量扒开看数值。

不太正规,但是不耗仿真时间,通过UVM DPI完成。

使用后门访问的时候,只需将UVM_FRONTDOOR改成UVM_BACKDOOR即可,例如

reg_seq.write_reg(reg_seq.rgm.chnl0_ctrl_reg,status,wr_val,UVM_BACKDOOR);
reg_seq.read_reg(reg_seq.rgm.chnl0_ctrl_reg,status,rd_val,UVM_BACKDOOR);

建立映射

需要将reg_block内的寄存器与dut中的reg型变量作映射。

reg_block::build()内完成,例子如下

class reg_block extends uvm_reg_block;
	//...
	virtual function build();
		//...
		add_hdl_path("tb.dut.ctrl_regs_inst");					//从tb开始找reg型变量所在的module
		chnls_ctrl_reg[0].add_hdl_path_slice($sformats("mem[%0d]",`SLV0_RW_REG),1,16); //即映射到mem[`SLV0_RW_REG][16:1]
		//...
		lock_model();
	endfunction
endclass

而在tb.dut.ctrl_regs_inst的module块中是这么定义的

 module ctrl_regs(	input clk_i,
					input rstn_i,
					//...
					);
reg [`CMD_DATA_WIDTH-1:0] mem [5:0];
//...
endmodule

使用了以下方法。

注意一般来说dut中一个寄存器都单独一个module,进而对应于reg_sequence中的一个reg_block,所以reg_block类内一般只有一句add_hdl_path

//...\questasim64_2020.1\verilog_src\uvm-1.2\src\reg\uvm_reg_block.svh
function void uvm_reg_block::add_hdl_path(string path, string kind = "RTL");

//...\questasim64_2020.1\verilog_src\uvm-1.2\src\reg\uvm_reg.svh
function void uvm_reg::add_hdl_path_slice(string name,			//变量名
                                          int offset,			//起始下标
                                          int size,				//以offset下标开始size个bit
                                          bit first = 0,
                                          string kind = "RTL");

3.3. 比较与应用

前门访问和后门访问最大的区别就在于协议时序。后门仿真不参照协议,所以读写数据不占用仿真时间、立即进行的。而前门访问是按照总线协议的时序来的,所以可能会阻塞住占用仿真时间。

如下表

项目前门访问后门访问
访问方式总线协议UVM DPI
时序依赖协议时序,消耗仿真时间直接读取,不消耗仿真时间
功能按字(32bit)读写,不可读写寄存器域可对域读写
预测检测总线作预测auto prediction

下面讲混合应用

物理通路

就是说使用前门能否按照协议说的那样读写数据,例如只写一次的寄存器,我写一次写进去了,再写发现不能写了,OK

而且,通过前门写进去的数据对不对啊。就可以先前门写、再后门看,最后前门读,以防止寄存器模型和dut寄存器模块地址不匹配的情况

随机化场景

想让dut中的寄存器为随机值的情况下观察响应,但随机值毕竟是随机的所以不能通过前门作配置,用后门。

方法就是先把软件一侧的寄存器模型随机化,然后将这些随机了的数值通过后门配置给硬件一侧的寄存器,然后再使用软件的寄存器模型对dut寄存器进行配置,观察是否有边界情况的产生。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:53:59  更:2022-03-12 17:55:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年9日历 -2024/9/20 13:33:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码