查看: 74|回复: 0

SystemVerilog搭建APB_I2C IP 层次化验证平台

[复制链接]
发表于 2020-2-16 06:55:01 | 显示全部楼层 |阅读模式
一、前言
  近期疫情严重,身为社畜的我只能在家中继续钻研技能了。之前写过一篇关于搭建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  -o  -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脚本文件:
  1. 1 #quit -sim 2  3 set filename testbench 4  5 vlog *.v *.sv 6  7 vopt -debugdb +acc work.$filename -o top_opt1 8 vsim -debugdb top_opt1 9 10 #vsim -vopt -debugdb +acc work.$filename11 12 # change WildcardFilter variables13 set WildcardFilter "Variable Constant Generic Parameter SpecParam Memory Assertion Cover Endpoint ScVariable ImmediateAssert VHDLFile"14 15 add wave /$filename/*16 add log -r /*17 18 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中。
  上代码:
  1.   1 package components;  2     `include "defines.sv"  3       4     apb_bus_t apb_bus;  5     logic event_tx_i2c_vld,event_tx_vld;  6     data_t data_tx_i2c;  7     logic data_tx_i2c_vld;  8       9     //Driver 10     class Initiator; 11          12         function void init_en(); 13             apb_bus.sel = 0; 14             apb_bus.wdata = 0; 15             apb_bus.addr = 0; 16             apb_bus.write = 0; 17             apb_bus.enable = 0; 18         endfunction 19          20         task write_oper(address_t address,data_t data_w); 21             @(posedge apb_bus.clk); 22             #1; 23             apb_bus.sel = 1; 24             apb_bus.write = 1; 25             apb_bus.wdata = data_w; 26             apb_bus.addr = address; 27             #T; 28             apb_bus.enable = 1; 29             #T; 30             init_en(); 31         endtask 32          33         task read_oper(address_t address,output data_t data_r); 34             @(posedge apb_bus.clk); 35             #1; 36             apb_bus.sel = 1; 37             apb_bus.write = 0; 38             apb_bus.addr = address; 39             #T; 40             apb_bus.enable = 1; 41             #T; 42             data_r = apb_bus.rdata; 43             init_en(); 44         endtask 45     endclass 46  47     typedef class Config; 48     //Generator 49     class Request;  50         data_t data_w; 51         data_t data_r; 52         Initiator initiator; 53          54         function new(); 55             data_w = 32'h1234_5678;//32'b0001_0010_0011_0100_0101_0110_0111_1000 56             initiator = new(); 57             clear_req(); 58         endfunction 59          60         function void clear_req(); 61             initiator.init_en(); 62         endfunction 63          64         task configure_reg(data_t data_reg_config,data_t data_reg_timeout); 65             initiator.write_oper(ADDR_REG_CONFIG,data_reg_config); 66             #(T*10); 67             initiator.write_oper(ADD_REG_TIMEOUT,data_reg_timeout); 68         endtask 69          70         task write_data(data_t data_w); 71             initiator.write_oper(ADDR_TX_FIFO,data_w); 72         endtask 73          74         task read_data(output data_t data_r); 75             initiator.read_oper(ADDR_RX_FIFO,data_r); 76         endtask 77          78         task req_run(Config req_config); 79             if(req_config.config_type == CONFIG_WR_DATA)begin 80                 configure_reg(data_t'({30'd10,WRI_EN}),data_t'(32'd10000)); 81                 write_data(data_w); 82             end 83             else if(req_config.config_type == CONFIG_RD_DATA)begin 84                 configure_reg(data_t'({30'd10,RD_EN}),data_t'(32'd10000)); 85                 read_data(data_r); 86             end 87         endtask 88      89     endclass:Request 90      91     class Config; 92         config_type_t config_type; 93          94         function new(config_type_t config_type=CONFIG_RD_DATA); 95             this.config_type = config_type; 96         endfunction 97          98     endclass:Config 99     100     class Monitor;101 102         mailbox #(data_t) mb_data_i2c_tx;103         mailbox #(data_t) mb_data_tx;104         105         function new(mailbox mb1,mailbox mb2);106             this.mb_data_i2c_tx = mb1;107             this.mb_data_tx = mb2;108         endfunction109         110         task store_res_tx();111             wait(event_tx_i2c_vld);112             #(T/2.0);113             mb_data_i2c_tx.put(data_tx_i2c);114             $display("store_res_tx:MAILBOX PUT:'h%h",data_tx_i2c);115         endtask116         117         task store_source_tx();118             wait(event_tx_vld);119             #(T/2.0);120             mb_data_tx.put(apb_bus.wdata);121             $display("store_source_tx:MAILBOX PUT:'h%h",apb_bus.wdata);122         endtask123         124         task mon_run();125             fork 126                 store_res_tx();127                 store_source_tx();128             join129         endtask130     131     endclass:Monitor132     133     class Checker;134         uint cmp_cnt;135         uint err_cnt;136         data_t data_A,data_B;137         mailbox #(data_t) mb_data_A,mb_data_B;138         sim_res_t check_res;139         140         function new(mailbox mb_A,mailbox mb_B);141             cmp_cnt = 0;142             err_cnt = 0;143             this.mb_data_A = mb_A;144             this.mb_data_B = mb_B;145         endfunction146         147         task collect_res();148             mb_data_A.get(this.data_A);149             mb_data_B.get(this.data_B);150             $display("MAILBOX GET:'h%h, 'h%h",this.data_A,this.data_B);151         endtask152         153         function sim_res_t compare(data_t dataA,data_t dataB);154             if(dataA == dataB)begin155                 check_res = TRUE;156             end157             else begin158                 err_cnt ++;159                 check_res = false;160             end161             return check_res;162         endfunction163         164         task check_run();165             sim_res_t check_res;166             collect_res();167             check_res = compare(data_A,data_B);168             if(check_res == TRUE)169                 $display("RUN PASS");170             else171                 $display("RUN FAIL");172         endtask173     174     endclass:Checker175     176     class Environment;177         mailbox #(data_t) mb[2];178         Checker chk;179         Request req;180         Monitor monitor;181         Config req_config;182         183         function new();184             uint i;185             req_config = new();186             req = new();187             foreach(mb[i])188                 mb[i] = new();189             monitor = new(mb[0],mb[1]);190             chk = new(mb[0],mb[1]);191         endfunction192         193         task env_run();194             fork195                 req.req_run(req_config);196                 monitor.mon_run();197             join198             chk.check_run();199         endtask200         201     endclass:Environment202     203 endpackage
复制代码
components.sv
  1. 1     parameter T = 200; 2     parameter DATA_W = 32; 3      4     parameter bit [2-1:0] WRI_EN = 2'B01, 5                         RD_EN = 2'B10; 6      7     typedef int unsigned uint; 8     //ADDR_REG_CONFIG = 'd8,//configure register 9     //ADD_REG_TIMEOUT = 'd12;//time before starting10     typedef enum uint {ADDR_TX_FIFO = 'd0,ADDR_RX_FIFO = 'd4,ADDR_REG_CONFIG = 'd8,ADD_REG_TIMEOUT = 'd12} address_t;11     typedef enum uint {TRUE,FALSE} sim_res_t;12     typedef logic [DATA_W-1:0] data_t;13     typedef struct {14     logic clk;15     logic write;16     logic sel;17     logic enable;18     data_t wdata;19     data_t rdata;20     data_t addr;21     logic ready;22     logic slverr;23     } apb_bus_t;24     25     typedef enum {WR,RD} gen_t;26     typedef enum {CONFIG_WR_DATA,CONFIG_RD_DATA} config_type_t;
复制代码
defines.sv
[code] 1 `timescale 1ns/1ps 2  3 module i2c_slave 4 #(parameter DATA_WIDTH=32) 5 ( 6 input clk, 7 input scl, 8 inout sda, 9 input sda_master_en,10 11 output logic [DATA_WIDTH-1:0] data_r,//master --> slave12 output logic data_r_vld,13 input [DATA_WIDTH-1:0] data_w,14 input data_w_vld15 );16 17 logic sda_r;18 logic sda_neg,sda_pos;19 logic cond_end,cond_start;20 21 assign sda = sda_master_en ? 1'bz : 1'b0;22 23 [email protected](posedge clk)begin24     sda_r

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?用户注册

x

相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表