1、前言緩存
近期疫情嚴重,身爲社畜的我只能在家中繼續鑽研技術了。以前寫過一篇關於搭建FIFO驗證平臺的博文,利用SV的OOP特性對FIFO進行初步驗證,但有不少不足之處,好比結構不夠規範、驗證組件類不獨立於DUT等問題。這次嘗試驗證更復雜的IP,並利用SV的更多高級特性來搭建層次化驗證平臺。併發
2、APB_I2C IP概述ide
實踐出真知,因而在opencores網站上下載了個APB_I2C的IP核,便着手展開驗證工做。第一步是理清楚這個IP的總體功能、引腳做用以及頂層結構。總體功能從模塊名稱即可得知是帶有APB總線接口的I2C_master。要了解引腳做用與時序,直接截取SPEC上的示意圖查看:工具
APB_WRITE:學習
APB_READ:測試
I2C_PROTOCOL:網站
接口和協議這裏就不細說了,感興趣的朋友查找相關的資料。至於頂層結構這方面,最好仍是交給工具方便點。無奈回家沒有帶回個人虛擬機硬盤,只能下載個WINDOW版本的EDA工具了。本文使用QuestaSim,原理圖以下:ui
很容易看出該模塊頂層包含APB接口模塊APB、分別用於緩存發送和接收數據的FIFO_TX和FIFO_RX,以及I2C協議轉換模塊I2X_INTERNAL_RX_TX。master經過APB總線訪問該IP核內部的數據緩存區和配置寄存器,無需關注內部實現。this
除了這幾個方面,配置寄存器的訪問也很是重要。IP核必須作出正確的配置和使能才能夠按照須要正常工做。配置寄存器見下表:spa
3、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腳本文件:
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.$filename 11 12 # change WildcardFilter variables 13 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
4、搭建驗證環境
這一節是本文的核心內容了。通用的驗證環境的結構和組件如圖:
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 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 endfunction 109 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 endtask 116 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 endtask 123 124 task mon_run(); 125 fork 126 store_res_tx(); 127 store_source_tx(); 128 join 129 endtask 130 131 endclass:Monitor 132 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 endfunction 146 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 endtask 152 153 function sim_res_t compare(data_t dataA,data_t dataB); 154 if(dataA == dataB)begin 155 check_res = TRUE; 156 end 157 else begin 158 err_cnt ++; 159 check_res = FALSE; 160 end 161 return check_res; 162 endfunction 163 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 else 171 $display("RUN FAIL"); 172 endtask 173 174 endclass:Checker 175 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 endfunction 192 193 task env_run(); 194 fork 195 req.req_run(req_config); 196 monitor.mon_run(); 197 join 198 chk.check_run(); 199 endtask 200 201 endclass:Environment 202 203 endpackage
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 starting 10 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;
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 --> slave 12 output logic data_r_vld, 13 input [DATA_WIDTH-1:0] data_w, 14 input data_w_vld 15 ); 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 always@(posedge clk)begin 24 sda_r <= sda; 25 end 26 assign sda_neg = sda_r & ~sda; 27 assign sda_pos = ~sda_r & sda; 28 29 assign cond_start = sda_neg & scl; 30 assign cond_end = sda_pos & scl; 31 32 integer bit_index=0; 33 34 always 35 begin 36 data_r_vld = 0; 37 wait(cond_start); 38 $display("TRANSMISSION START"); 39 @(posedge scl); 40 while(bit_index < DATA_WIDTH)begin 41 @(negedge scl); 42 if (sda_master_en)begin 43 @(posedge clk); 44 data_r = {sda,data_r[DATA_WIDTH-1 -:DATA_WIDTH-1]}; 45 bit_index = bit_index+1; 46 $display("Get bit%d:%d",bit_index,sda); 47 end 48 end 49 data_r_vld = 1; 50 repeat(10) 51 @(posedge clk); 52 data_r_vld = 0; 53 $display("TRANSMISSION END"); 54 bit_index = 0; 55 end 56 57 endmodule
1 `timescale 1ns/1ps 2 3 import components::*; 4 5 6 module testbench; 7 8 logic pclk,presetn; 9 logic [DATA_W-1:0] paddr,pwdata,prdata; 10 logic pwrite,pselx,penable; 11 logic req_tx_vld; 12 13 wire pready,pslverr; 14 wire int_rx,int_tx; 15 wire sda_enable,scl_enable; 16 wire scl; 17 wire sda; 18 19 wire [DATA_W-1:0] data_r; 20 wire data_r_vld; 21 //apb_bus_t apb_bus; 22 23 assign pwrite = apb_bus.write; 24 assign pselx = apb_bus.sel; 25 assign penable = apb_bus.enable; 26 assign pwdata = apb_bus.wdata; 27 assign paddr = apb_bus.addr; 28 29 assign apb_bus.rdata = prdata; 30 assign apb_bus.ready = pready; 31 assign apb_bus.slverr = pslverr; 32 assign apb_bus.clk = pclk; 33 34 //logic event_tx_i2c_vld,event_tx_vld; 35 assign event_tx_vld = req_tx_vld == 1'b1; 36 assign event_tx_i2c_vld = data_r_vld == 1'b1; 37 //data_t data_tx_i2c; 38 //logic data_tx_i2c_vld; 39 assign data_tx_i2c = data_r; 40 assign data_tx_i2c_vld = data_r_vld; 41 42 43 initial begin 44 pclk = 1; 45 forever begin 46 #(T/2.0) pclk = ~pclk; 47 end 48 end 49 50 initial begin 51 presetn = 1; 52 #1; 53 presetn = 0; 54 #(T*2); 55 presetn = 1; 56 end 57 58 59 assign req_tx_vld = pselx & pwrite & penable & pready & ~pslverr & (paddr == ADDR_TX_FIFO || paddr == ADDR_RX_FIFO); 60 61 Environment env; 62 Config req_config; 63 initial begin 64 65 env = new(); 66 //req_config = new(CONFIG_WR_DATA); 67 req_config = new(CONFIG_RD_DATA); 68 env.req_config = req_config; 69 70 #1; 71 #(T*2); 72 73 env.env_run(); 74 end 75 /////////////////////////// 76 i2c_slave 77 #(.DATA_WIDTH(DATA_W)) 78 i2c_slave_vip( 79 .clk (pclk), 80 .scl (scl), 81 .sda (sda), 82 .sda_master_en (sda_enable), 83 .data_r (data_r), 84 .data_r_vld (data_r_vld), 85 .data_w (), 86 .data_w_vld () 87 ); 88 89 90 i2c DUT( 91 //APB PORTS 92 .PCLK (pclk), 93 .PRESETn (presetn), 94 .PADDR (paddr), 95 .PWDATA (pwdata), 96 .PWRITE (pwrite), 97 .PSELx (pselx), 98 .PENABLE (penable), 99 . PREADY (pready), 100 . PSLVERR (pslverr), 101 . INT_RX (int_rx), 102 . INT_TX (int_tx), 103 . PRDATA (prdata), 104 . SDA_ENABLE (sda_enable), 105 . SCL_ENABLE (scl_enable), 106 .SDA (sda), 107 .SCL (scl) 108 109 ); 110 111 endmodule
5、仿真分析
當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 不抱但願了,以後我仍是本身寫一個吧。
6、總結
本文利用APB_I2C模塊爲例搭建了層次化驗證平臺,但還有待改善。這裏列出幾點:
1 沒有徹底作到測試用例與環境分離
2 沒有構建場景層給予豐富的pattern
7、參考
1 《SystemVerilog驗證——測試平臺編寫指南》
2 《QuestaSim Tutorial》
3 《QuestaSim User Manual》
4 《apbi2c_spec》
5 Overview :: APB to I2C :: OpenCores https://opencores.org/projects/apbi2c