SystemVerilog搭建APB_I2C IP 层次化验证平台
一、前言
近期疫情严重,身为社畜的我只能在家中继续钻研技术了。之前写过一篇关于搭建FIFO验证平台的博文,利用SV的OOP特性对FIFO进行初步验证,但有很多不足之处,比如结构不够规范、验证组件类不独立于DUT等问题。此次尝试验证更复杂的IP,并利用SV的更多高级特性来搭建层次化验证平台。
二、APB_I2C IP概述
实践出真知,于是在opencores网站上下载了个APB_I2C的IP核,便着手展开验证工作。第一步是理清楚这个IP的整体功能、引脚作用以及顶层结构。整体功能从模块名称便可得知是带有APB总线接口的I2C_master。要了解引脚作用与时序,直接截取SPEC上的示意图查看:
APB_WRITE:
APB_READ:
I2C_PROTOCOL:
接口和协议这里就不细说了,感兴趣的朋友查找相关的资料。至于顶层结构这方面,最好还是交给工具方便点。无奈回家没有带回我的虚拟机硬盘,只能下载个WINDOW版本的EDA工具了。本文使用QuestaSim,原理图如下:
很容易看出该模块顶层包含APB接口模块APB、分别用于缓存发送和接收数据的FIFO_TX和FIFO_RX,以及I2C协议转换模块I2X_INTERNAL_RX_TX。master通过APB总线访问该IP核内部的数据缓存区和配置寄存器,无需关注内部实现。
除了这几个方面,配置寄存器的访问也非常重要。IP核必须做出正确的配置和使能才可以按照需要正常工作。配置寄存器见下表:
三、QuestaSim常用指令
QuestaSim工具的WINDOWS/LINUX版本很容易下载到,和Modelsim的主要区别是对SV UVM的支持性较好,这一点非常符合本文的意愿。但仿真过程中一次次点击鼠标很麻烦,只好学习学习操作命令了,写个脚本配合SV实现自动化仿真。以下是在官方文档user manual和tutorial中截取的常用指令及解释。
1 Compile the source files.
vlog gates.v and2.v cache.v memory.v proc.v set.v top.v
2 Use the vopt command to optimize the design with full visibility into all design units
vopt +acc <design_name> -o <optimized_design_name> -debugdb
The +acc argument enables full visibility into the design for debugging purposes. The -oargument is required for naming the optimized design object. The -debugdb argument collects combinatorial and sequential logic data into the work library.
3 Use the optimized design name to load the design with the vsim command:
vsim testcounter_opt -debugdb
4 set WildcardFilter "Variable Constant Generic Parameter SpecParam Memory
Assertion Endpoint ImmediateAssert"
With this command, you remove “CellInternal” from the default list of Wildcard filters.
This allows all signals in cells to be logged by the simulator so they will be visible in the
debug environment.
5 Add Wave *
6 add log /*
This will provide the historic values of the events of interest plus its drivers
7 run 500
一并给出我的do脚本文件:
- #quit -sim
- set filename testbench
- vlog *.v *.sv
- vopt -debugdb +acc work.$filename -o top_opt1
- vsim -debugdb top_opt1
- #vsim -vopt -debugdb +acc work.$filename
- # change WildcardFilter variables
- set WildcardFilter "Variable Constant Generic Parameter SpecParam Memory Assertion Cover Endpoint ScVariable ImmediateAssert VHDLFile"
- add wave /$filename/*
- add log -r /*
- run 1000ns
sim.do
四、搭建验证环境
这一节是本文的核心内容了。通用的验证环境的结构和组件如图:
Stimulus将测试激励送入待测试模块DUT,Monitor观察响应并发送给检Checker。遇到复杂的设计还需要设计Reference model,进而对比实际响应与黄金参考的响应区别。并且当Monitor无法简单直接地收集DUT响应时,还需要设计VIP来解析复杂的响应信号时序。这几天参照工具书和网上的教程视频,根据APB_I2C模块的特性构思出基本的验证环境。
APB_I2C模块并不复杂,所以没必要设计reference model。若想利用Monitor组件获取DUT响应需要解析I2C协议时序,这里编写个VIP来帮助它解析出有效数据,进而与Stimulus数据对比。Monitor因VIP的存在得到了很大程度上的简化,主要的功能为将等待触发事件发生后,将数据通过MAILBOX传输给Checker进行比较。
另外,为了让Stimulus脱离具体接口信号操作,建立Generator和Initiator类分别用于产生读写访问和将读写访问转换成读写操作对应的具体信号逻辑。为了实现OOP特性中的“细节隐藏”,建立配置类Config来配置验证环境,这里主要是配置Generator发送特定场景的读写请求。想要测试不同的功能特性,只需改动传入Config的参数即可。到此验证环境包含了Generator Initiator Monitor Checker Config五个验证组件,这里再建立Environment类将这些组件包在一起,方便调用方法。还是上图更直观些(有点丑,凑活看吧)
除了验证环境结构,好的代码结构也能极大提高平台的重用性。这里将所有类及对应的属性方法封装到Package components中,方便被import到testbench中。验证过程中用到的所有变量类型、参数放置在defines.sv中。
上代码:
- package components;
- `include "defines.sv"
- apb_bus_t apb_bus;
- logic event_tx_i2c_vld,event_tx_vld;
- data_t data_tx_i2c;
- logic data_tx_i2c_vld;
- //Driver
- class Initiator;
- function void init_en();
- apb_bus.sel = ;
- apb_bus.wdata = ;
- apb_bus.addr = ;
- apb_bus.write = ;
- apb_bus.enable = ;
- endfunction
- task write_oper(address_t address,data_t data_w);
- @(posedge apb_bus.clk);
- #;
- apb_bus.sel = ;
- apb_bus.write = ;
- apb_bus.wdata = data_w;
- apb_bus.addr = address;
- #T;
- apb_bus.enable = ;
- #T;
- init_en();
- endtask
- task read_oper(address_t address,output data_t data_r);
- @(posedge apb_bus.clk);
- #;
- apb_bus.sel = ;
- apb_bus.write = ;
- apb_bus.addr = address;
- #T;
- apb_bus.enable = ;
- #T;
- data_r = apb_bus.rdata;
- init_en();
- endtask
- endclass
- typedef class Config;
- //Generator
- class Request;
- data_t data_w;
- data_t data_r;
- Initiator initiator;
- function new();
- data_w = 'h1234_5678;//32'b0001_0010_0011_0100_0101_0110_0111_1000
- initiator = new();
- clear_req();
- endfunction
- function void clear_req();
- initiator.init_en();
- endfunction
- task configure_reg(data_t data_reg_config,data_t data_reg_timeout);
- initiator.write_oper(ADDR_REG_CONFIG,data_reg_config);
- #(T*);
- initiator.write_oper(ADD_REG_TIMEOUT,data_reg_timeout);
- endtask
- task write_data(data_t data_w);
- initiator.write_oper(ADDR_TX_FIFO,data_w);
- endtask
- task read_data(output data_t data_r);
- initiator.read_oper(ADDR_RX_FIFO,data_r);
- endtask
- task req_run(Config req_config);
- if(req_config.config_type == CONFIG_WR_DATA)begin
- configure_reg(data_t'({30'd10,WRI_EN}),data_t'(32'd10000));
- write_data(data_w);
- end
- else if(req_config.config_type == CONFIG_RD_DATA)begin
- configure_reg(data_t'({30'd10,RD_EN}),data_t'(32'd10000));
- read_data(data_r);
- end
- endtask
- endclass:Request
- class Config;
- config_type_t config_type;
- function new(config_type_t config_type=CONFIG_RD_DATA);
- this.config_type = config_type;
- endfunction
- endclass:Config
- class Monitor;
- mailbox #(data_t) mb_data_i2c_tx;
- mailbox #(data_t) mb_data_tx;
- function new(mailbox mb1,mailbox mb2);
- this.mb_data_i2c_tx = mb1;
- this.mb_data_tx = mb2;
- endfunction
- task store_res_tx();
- wait(event_tx_i2c_vld);
- #(T/2.0);
- mb_data_i2c_tx.put(data_tx_i2c);
- $display("store_res_tx:MAILBOX PUT:'h%h",data_tx_i2c);
- endtask
- task store_source_tx();
- wait(event_tx_vld);
- #(T/2.0);
- mb_data_tx.put(apb_bus.wdata);
- $display("store_source_tx:MAILBOX PUT:'h%h",apb_bus.wdata);
- endtask
- task mon_run();
- fork
- store_res_tx();
- store_source_tx();
- join
- endtask
- endclass:Monitor
- class Checker;
- uint cmp_cnt;
- uint err_cnt;
- data_t data_A,data_B;
- mailbox #(data_t) mb_data_A,mb_data_B;
- sim_res_t check_res;
- function new(mailbox mb_A,mailbox mb_B);
- cmp_cnt = ;
- err_cnt = ;
- this.mb_data_A = mb_A;
- this.mb_data_B = mb_B;
- endfunction
- task collect_res();
- mb_data_A.get(this.data_A);
- mb_data_B.get(this.data_B);
- $display("MAILBOX GET:'h%h, 'h%h",this.data_A,this.data_B);
- endtask
- function sim_res_t compare(data_t dataA,data_t dataB);
- if(dataA == dataB)begin
- check_res = TRUE;
- end
- else begin
- err_cnt ++;
- check_res = FALSE;
- end
- return check_res;
- endfunction
- task check_run();
- sim_res_t check_res;
- collect_res();
- check_res = compare(data_A,data_B);
- if(check_res == TRUE)
- $display("RUN PASS");
- else
- $display("RUN FAIL");
- endtask
- endclass:Checker
- class Environment;
- mailbox #(data_t) mb[];
- Checker chk;
- Request req;
- Monitor monitor;
- Config req_config;
- function new();
- uint i;
- req_config = new();
- req = new();
- foreach(mb[i])
- mb[i] = new();
- monitor = new(mb[],mb[]);
- chk = new(mb[],mb[]);
- endfunction
- task env_run();
- fork
- req.req_run(req_config);
- monitor.mon_run();
- join
- chk.check_run();
- endtask
- endclass:Environment
- endpackage
components.sv
- parameter T = ;
- parameter DATA_W = ;
- parameter bit [-:] WRI_EN = 'B01,
- RD_EN = 'B10;
- typedef int unsigned uint;
- //ADDR_REG_CONFIG = 'd8,//configure register
- //ADD_REG_TIMEOUT = 'd12;//time before starting
- typedef enum uint {ADDR_TX_FIFO = 'd0,ADDR_RX_FIFO = 'd4,ADDR_REG_CONFIG = 'd8,ADD_REG_TIMEOUT = 'd12} address_t;
- typedef enum uint {TRUE,FALSE} sim_res_t;
- typedef logic [DATA_W-:] data_t;
- typedef struct {
- logic clk;
- logic write;
- logic sel;
- logic enable;
- data_t wdata;
- data_t rdata;
- data_t addr;
- logic ready;
- logic slverr;
- } apb_bus_t;
- typedef enum {WR,RD} gen_t;
- typedef enum {CONFIG_WR_DATA,CONFIG_RD_DATA} config_type_t;
defines.sv
- `timescale 1ns/1ps
- module i2c_slave
- #(parameter DATA_WIDTH=)
- (
- input clk,
- input scl,
- inout sda,
- input sda_master_en,
- output logic [DATA_WIDTH-:] data_r,//master --> slave
- output logic data_r_vld,
- input [DATA_WIDTH-:] data_w,
- input data_w_vld
- );
- logic sda_r;
- logic sda_neg,sda_pos;
- logic cond_end,cond_start;
- assign sda = sda_master_en ? 'bz : 1'b0;
- always@(posedge clk)begin
- sda_r <= sda;
- end
- assign sda_neg = sda_r & ~sda;
- assign sda_pos = ~sda_r & sda;
- assign cond_start = sda_neg & scl;
- assign cond_end = sda_pos & scl;
- integer bit_index=;
- always
- begin
- data_r_vld = ;
- wait(cond_start);
- $display("TRANSMISSION START");
- @(posedge scl);
- while(bit_index < DATA_WIDTH)begin
- @(negedge scl);
- if (sda_master_en)begin
- @(posedge clk);
- data_r = {sda,data_r[DATA_WIDTH- -:DATA_WIDTH-]};
- bit_index = bit_index+;
- $display("Get bit%d:%d",bit_index,sda);
- end
- end
- data_r_vld = ;
- repeat()
- @(posedge clk);
- data_r_vld = ;
- $display("TRANSMISSION END");
- bit_index = ;
- end
- endmodule
i2c_slave.sv
- `timescale 1ns/1ps
- import components::*;
- module testbench;
- logic pclk,presetn;
- logic [DATA_W-:] paddr,pwdata,prdata;
- logic pwrite,pselx,penable;
- logic req_tx_vld;
- wire pready,pslverr;
- wire int_rx,int_tx;
- wire sda_enable,scl_enable;
- wire scl;
- wire sda;
- wire [DATA_W-:] data_r;
- wire data_r_vld;
- //apb_bus_t apb_bus;
- assign pwrite = apb_bus.write;
- assign pselx = apb_bus.sel;
- assign penable = apb_bus.enable;
- assign pwdata = apb_bus.wdata;
- assign paddr = apb_bus.addr;
- assign apb_bus.rdata = prdata;
- assign apb_bus.ready = pready;
- assign apb_bus.slverr = pslverr;
- assign apb_bus.clk = pclk;
- //logic event_tx_i2c_vld,event_tx_vld;
- assign event_tx_vld = req_tx_vld == 'b1;
- assign event_tx_i2c_vld = data_r_vld == 'b1;
- //data_t data_tx_i2c;
- //logic data_tx_i2c_vld;
- assign data_tx_i2c = data_r;
- assign data_tx_i2c_vld = data_r_vld;
- initial begin
- pclk = ;
- forever begin
- #(T/2.0) pclk = ~pclk;
- end
- end
- initial begin
- presetn = ;
- #;
- presetn = ;
- #(T*);
- presetn = ;
- end
- assign req_tx_vld = pselx & pwrite & penable & pready & ~pslverr & (paddr == ADDR_TX_FIFO || paddr == ADDR_RX_FIFO);
- Environment env;
- Config req_config;
- initial begin
- env = new();
- //req_config = new(CONFIG_WR_DATA);
- req_config = new(CONFIG_RD_DATA);
- env.req_config = req_config;
- #;
- #(T*);
- env.env_run();
- end
- ///////////////////////////
- i2c_slave
- #(.DATA_WIDTH(DATA_W))
- i2c_slave_vip(
- .clk (pclk),
- .scl (scl),
- .sda (sda),
- .sda_master_en (sda_enable),
- .data_r (data_r),
- .data_r_vld (data_r_vld),
- .data_w (),
- .data_w_vld ()
- );
- i2c DUT(
- //APB PORTS
- .PCLK (pclk),
- .PRESETn (presetn),
- .PADDR (paddr),
- .PWDATA (pwdata),
- .PWRITE (pwrite),
- .PSELx (pselx),
- .PENABLE (penable),
- . PREADY (pready),
- . PSLVERR (pslverr),
- . INT_RX (int_rx),
- . INT_TX (int_tx),
- . PRDATA (prdata),
- . SDA_ENABLE (sda_enable),
- . SCL_ENABLE (scl_enable),
- .SDA (sda),
- .SCL (scl)
- );
- endmodule
testbench.sv
五、仿真分析
当Config类对象的配置参数为CONFIG_WR_DATA时,generator发起写请求。波形如下:
观察打印的Log可以看出每个SCL时钟周期采集到一个bit,MAILBOX正确传输,checker对比正确,故而仿真PASS。
验证过程中发现该模块有很多BUG!!这里举两个例子。
1 SDA为双向端口,但当sda_enable为0时,并没有赋值为高阻态,即释放信号线控制权给slave。做出如下修改并让VIP在ACK阶段拉低SDA。
2 SCL在读操作状态机中没有被toggle,因此config的配置参数为CONFIG_RD_DATA时SCL没有翻转。在读操作状态机中添加翻转逻辑,使BR_CLK_RX_O信号在counter_receive_data == clk_t_1_4时拉高,counter_receive_data==clk_t_3_4时拉低。
波形显示在读操作时SCL正常翻转。
该模块的读操作很多地方不正确还有待修改,就不一一赘述了。总的来说就是根本不能用o(╥﹏╥)o 不抱希望了,之后我还是自己写一个吧。
六、总结
本文利用APB_I2C模块为例搭建了层次化验证平台,但还有待改善。这里列出几点:
1 没有完全做到测试用例与环境分离
2 没有构建场景层给予丰富的pattern
七、参考
1 《SystemVerilog验证——测试平台编写指南》
2 《QuestaSim Tutorial》
3 《QuestaSim User Manual》
4 《apbi2c_spec》
5 Overview :: APB to I2C :: OpenCores https://opencores.org/projects/apbi2c
SystemVerilog搭建APB_I2C IP 层次化验证平台的更多相关文章
- ( 转)UVM验证方法学之一验证平台
在现代IC设计流程中,当设计人员根据设计规格说明书完成RTL代码之后,验证人员开始验证这些代码(通常称其为DUT,Design Under Test).验证工作主要保证从设计规格说明书到RTL转变的正 ...
- SystemVerilog搭建验证平台使用DPI时遇到的问题及解决方案
本文目的在于分享一下把DPI稿能用了的过程,主要说一下平台其他部分搭建好之后,在完成DPI相关工作阶段遇到的问题,以及解决的办法. 工作环境:win10 64bit, Questasim 10.1b ...
- UART UVM验证平台平台搭建总结
tb_top是整个UVM验证平台的最顶层:tb_top中例化dut,提供时钟和复位信号,定义接口以及设置driver和monitor的virual interface,在intial中调用run_te ...
- UART IP和UVM的验证平台
UART是工程师在开发调试时最常用的工具的,其通信协议简单.opencores 网站提供了兼容16550a的UART IP其基本特性如下: uart16550 is a 16550 compatibl ...
- 基于简单DUT的UVM验证平台的搭建(一)
最近一个月在实习公司做回归测试,对公司的UVM平台用的比较熟练,就想着自己做一个DUT,然后搭建一个UVM验证平台. 首先,DUT是一个简单的32位的加法器,代码如下:alu.v module add ...
- 干货 | 手把手教你搭建一套OpenStack云平台
1 前言 今天我们为一位朋友搭建一套OpenStack云平台. 我们使用Kolla部署stein版本的OpenStack云平台. kolla是用于自动化部署OpenStack的一个项目,它基于dock ...
- 从0到1搭建移动App功能自动化测试平台(2):操作iOS应用的控件
转自:http://debugtalk.com/post/build-app-automated-test-platform-from-0-to-1-Appium-interrogate-iOS-UI ...
- centos6.4搭建knowlededgeroot-1.0.4知识库平台
知识库平台选择 http://www.oschina.net/project/tag/320/pkm 最近接到一个任务,要求搭建一个用于部门内部业务知识规范管理和共享的平台,目的是把部门内的FAQ知识 ...
- 从0到1搭建移动App功能自动化测试平台(0):背景介绍和平台规划
本文作者: 伯乐在线 - debugtalk .未经作者许可,禁止转载!欢迎加入伯乐在线 专栏作者. 转载地址:http://blog.jobbole.com/101221/ 背景 最近新加入DJI的 ...
随机推荐
- 洛谷p-1522又是Floyd
挺简单一个题,可惜当时没想到,有点巧妙丫! #include<cstdio> #include<iostream> #include<cstring> #inclu ...
- Java正则表达式学习与记录
转载自:http://www.runoob.com/java/java-regular-expressions.html 正则表达式定义了字符串的模式,用于搜索.编辑或处理文本. 1.正则表达式中字符 ...
- 【题解】P2521 [HAOI2011]防线修建(动态凸包)
[题解]P2521 [HAOI2011]防线修建(动态凸包) 凸包是易插入不好删除的东西,按照剧情所以我们时光倒流 然后问题就是维护凸包的周长,支持加入 本来很简单,但是计算几何就是一些小地方经验不足 ...
- $UVA10559\ Blocks\ $区间$dp$
\(Des\) • 有一排数量为N的方块,每次可以把连续的相同颜色的区间消除,得到分数为 区间长度的平方,然后左右两边连在一起,问最大分数为多少. • n<=1 \(Sol\) 正解状态设得奇奇 ...
- Python数据分析:手把手教你用Pandas生成可视化图表
大家都知道,Matplotlib 是众多 Python 可视化包的鼻祖,也是Python最常用的标准可视化库,其功能非常强大,同时也非常复杂,想要搞明白并非易事.但自从Python进入3.0时代以后, ...
- UML类图基础
UML( Unified Modeling Language) 统一建模语言, 它是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支持,包括由需求分析到规格,到构造和 ...
- 个性化重排--Personalized Re-ranking for Recommendation
推荐中的个性化重排--Personalized Re-ranking for Recommendation 这篇文章是阿里在ResSys'19发表的,主要贡献是在重排序阶段,引入了用户的相关信息,很符 ...
- jenkins +git+ssh 构建 .net项目
jenkins +git+ssh 构建 .net项目 安装jenkins jdk 和插件就不一一介绍了. Multiple SCMs 插件介绍:可以获取多个项目(如果你的项目中有依赖其他项目的) So ...
- Queue and deque
Queue : 队列 队列(Queue)是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另一端取出(poll)元素. 队列遵循先 ...
- C++生成dll以及调用(函数)和类
C++新手,方法可能有很多,此方法仅仅是自己实验并可行,详细步骤如下: 生成dll文件和lib文件: (1) 新建项目-windows桌面向导,选择动态链接.dll以及空项目: (2)复制代码(头文件 ...