• Verification of WISHBONE I2C Master Core(IRUN+Simvision)


    一、前言

      很久没写技术博客了,有些懈怠,生活还得继续折腾。转眼工作一年多,时间越长越发觉得自己知之甚少,当然这跟IC行业技术密集有关。用空余时间在opencores网站上下载些小的IP看看 验证下,让自己对EDA tool, design, testbench, bus protocol都能有更好的认识。这次接触的是WISHBONE I2C Master Core。仿真验证工具是IES(Irun)+Simvision。

    二、IP概述

      这一IP也是直接从Opencores网站上下载,对于FPGA平台来说是可以直接拿来用的,还带有spec 仿真脚本,真的是贴心。网络链接见参考节。

       对着图简单介绍下这个IP。内部有预分频寄存器、控制寄存器、状态寄存器、发送寄存器、接收寄存器还有命令寄存器。其中控制寄存器只负责使能,而命令寄存器则是I2C 协议中相关的指令操作。IP的核心逻辑在byte command controller和bit command controller两个模块中。

      byte command controller根据命令控制寄存器的指令来将单一的命令转换为bit级别的命令,bit command controller接受bit级命令后将每一比特划分更细的时间片操作SCL和SDA产生特定的时序。比如当读取一个字节时,bit command controller接收到8个读指令,而对于每一个比特分为5个时间片IDLE A B C D。这种分层设计方式具有很高的复用性和可读性。

     三IES(IRUN)+Simvision工具

      IES+Simvision是Cadence公司的仿真调试工具,Simvision的code schematic wave三者建立了映射关系,调试起来效率非常高。irun指令可以直接一起完成compilation elaboration simulation三个步骤,通过脚本观察它的使用方式。

     1 #!/bin/tcsh
     2 
     3 set i2c      = ../../..
     4 set bench    = $i2c/bench
     5 set wave_dir = $i2c/sim/rtl_sim/i2c_verilog/waves
     6 
     7 irun    -64bit                                            
     8                                                             
     9     +access+rwc                                            
    10     +define+WAVES                                          
    11                                                             
    12     +incdir+$bench/verilog                              
    13     +incdir+$i2c/rtl/verilog                          
    14                                                             
    15     $i2c/rtl/verilog/i2c_master_bit_ctrl.v        
    16     $i2c/rtl/verilog/i2c_master_byte_ctrl.v        
    17     $i2c/rtl/verilog/i2c_master_top.v                
    18                                                             
    19     $bench/verilog/i2c_slave_model.v                
    20     $bench/verilog/wb_master_model.v                
    21     $bench/verilog/tst_bench_top.v
    run.csh

    +access+rwc 设置编译结果的访问权限为读写执行

    +define+WAVES 在外部添加verilog宏定义 WAVES,相当于`define WAVES

    +incdir+xxx 添加路径,把design和testbench代码路径添加其中

    后边直接添加需要的.v文件

      现在来看看WAVES宏定义的作用:

      条件编译使能dump .sh波形的代码段。具体使用方式参考文末链接。

      ./run.csh启动仿真:

       仿真结束后启动Simvision的GUI。

    simvision -64bit WAVES/ &

       终于找到在公司debug的感觉了。

    四、testbench

      自带的testbench中例化了一个wb_master_model,两个DUT以及一个i2c_slave_model。作者特意例化两个I2C master意在验证I2C协议中多总线机制。我们可以从Simvision的schematic中直观地看到tb的整体结构。

       testbench中利用wb_master_model内部的task来实现总线读写Core寄存器,也就是充当MCU中CPU的角色。原有的testbench code存在些问题,解决后添加了测试中断信号的部分代码。源代码如下:

      1 `include "timescale.v"
      2 module tst_bench_top();
      3 
      4     //
      5     // wires && regs
      6     //
      7     reg  clk;
      8     reg  rstn;
      9 
     10     wire [31:0] adr;
     11   wire [2:0] adr_i;
     12     wire [ 7:0] dat_i, dat_o, dat0_i, dat1_i;
     13     wire we;
     14     wire stb;
     15     wire cyc;
     16     wire ack;
     17     wire inta0,inta1;
     18 
     19     reg [7:0] q, qq;
     20 
     21     wire scl, scl0_o, scl0_oen, scl1_o, scl1_oen;
     22     wire sda, sda0_o, sda0_oen, sda1_o, sda1_oen;
     23 
     24     parameter PRER_LO = 3'b000;
     25     parameter PRER_HI = 3'b001;
     26     parameter CTR     = 3'b010;
     27     parameter RXR     = 3'b011;
     28     parameter TXR     = 3'b011;
     29     parameter CR      = 3'b100;
     30     parameter SR      = 3'b100;
     31 
     32     parameter TXR_R   = 3'b101; // undocumented / reserved output
     33     parameter CR_R    = 3'b110; // undocumented / reserved output
     34 
     35     parameter RD      = 1'b1;
     36     parameter WR      = 1'b0;
     37     parameter SADR    = 7'b0010_000;
     38   parameter WAIT_TIME=50_000;
     39 
     40     //
     41     // Module body
     42     //
     43 
     44     // generate clock
     45     always #5 clk = ~clk;
     46 
     47     // hookup wishbone master model
     48     wb_master_model #(8, 32) u0 (
     49         .clk(clk),
     50         .rst(rstn),
     51         .adr(adr),
     52         .din(dat_i),
     53         .dout(dat_o),
     54         .cyc(cyc),
     55         .stb(stb),
     56         .we(we),
     57         .sel(),
     58         .ack(ack),
     59         .err(1'b0),
     60         .rty(1'b0)
     61     );
     62 
     63     wire stb0 = stb & ~adr[3];
     64     wire stb1 = stb &  adr[3];
     65   assign adr_i = adr[2:0];
     66 
     67     assign dat_i = ({{8'd8}{stb0}} & dat0_i) | ({{8'd8}{stb1}} & dat1_i);
     68 
     69     // hookup wishbone_i2c_master core
     70     i2c_master_top i2c_top (
     71 
     72         // wishbone interface
     73         .wb_clk_i(clk),
     74         .wb_rst_i(1'b0),
     75         .arst_i(rstn),
     76         .wb_adr_i(adr_i),
     77         .wb_dat_i(dat_o),
     78         .wb_dat_o(dat0_i),
     79         .wb_we_i(we),
     80         .wb_stb_i(stb0),
     81         .wb_cyc_i(cyc),
     82         .wb_ack_o(ack),
     83         .wb_inta_o(inta0),
     84 
     85         // i2c signals
     86         .scl_pad_i(scl),
     87         .scl_pad_o(scl0_o),
     88         .scl_padoen_o(scl0_oen),
     89         .sda_pad_i(sda),
     90         .sda_pad_o(sda0_o),
     91         .sda_padoen_o(sda0_oen)
     92     ),
     93     i2c_top2 (
     94 
     95         // wishbone interface
     96         .wb_clk_i(clk),
     97         .wb_rst_i(1'b0),
     98         .arst_i(rstn),
     99         .wb_adr_i(adr_i),
    100         .wb_dat_i(dat_o),
    101         .wb_dat_o(dat1_i),
    102         .wb_we_i(we),
    103         .wb_stb_i(stb1),
    104         .wb_cyc_i(cyc),
    105         .wb_ack_o(ack),
    106         .wb_inta_o(inta1),
    107 
    108         // i2c signals
    109         .scl_pad_i(scl),
    110         .scl_pad_o(scl1_o),
    111         .scl_padoen_o(scl1_oen),
    112         .sda_pad_i(sda),
    113         .sda_pad_o(sda1_o),
    114         .sda_padoen_o(sda1_oen)
    115     );
    116 
    117 
    118     // hookup i2c slave model
    119     i2c_slave_model #(SADR) i2c_slave (
    120         .scl(scl),
    121         .sda(sda)
    122     );
    123 
    124         // create i2c lines
    125     delay m0_scl (scl0_oen ? 1'bz : scl0_o, scl),
    126           m1_scl (scl1_oen ? 1'bz : scl1_o, scl),
    127           m0_sda (sda0_oen ? 1'bz : sda0_o, sda),
    128           m1_sda (sda1_oen ? 1'bz : sda1_o, sda);
    129 
    130     pullup p1(scl); // pullup scl line
    131     pullup p2(sda); // pullup sda line
    132 
    133     initial
    134       begin
    135           `ifdef WAVES
    136              $shm_open("waves");
    137              $shm_probe("AS",tst_bench_top,"AS");
    138              $display("INFO: Signal dump enabled ...
    
    ");
    139           `endif
    140 
    141           force i2c_slave.debug = 1'b1; // enable i2c_slave debug information
    142           //force i2c_slave.debug = 1'b0; // disable i2c_slave debug information
    143 
    144           $display("
    status: %t Testbench started
    
    ", $time);
    145 
    146 //          $dumpfile("bench.vcd");
    147 //          $dumpvars(1, tst_bench_top);
    148 //          $dumpvars(1, tst_bench_top.i2c_slave);
    149 
    150           // initially values
    151           clk = 0;
    152 
    153           // reset system
    154           rstn = 1'b1; // negate reset
    155           #2;
    156           rstn = 1'b0; // assert reset
    157           repeat(1) @(posedge clk);
    158           rstn = 1'b1; // negate reset
    159 
    160           $display("status: %t done reset", $time);
    161 
    162           @(posedge clk);
    163 
    164           //
    165           // program core
    166           //
    167 
    168           // program internal registers
    169           u0.wb_write(1, PRER_LO, 8'hfa); // load prescaler lo-byte
    170           u0.wb_write(1, PRER_LO, 8'hc8); // load prescaler lo-byte
    171           u0.wb_write(1, PRER_HI, 8'h00); // load prescaler hi-byte
    172           $display("status: %t programmed registers", $time);
    173 
    174           u0.wb_cmp(0, PRER_LO, 8'hc8); // verify prescaler lo-byte
    175           u0.wb_cmp(0, PRER_HI, 8'h00); // verify prescaler hi-byte
    176           $display("status: %t verified registers", $time);
    177 
    178           u0.wb_write(1, CTR,     8'h80); // enable core
    179           $display("status: %t core enabled", $time);
    180 
    181 
    182 
    183           $display("***************************");
    184           $display("test1: access slave (write)");
    185           $display("***************************");
    186 
    187           // drive slave address
    188           u0.wb_write(1, TXR, {SADR,WR} ); // present slave address, set write-bit
    189           u0.wb_write(0, CR,      8'h90 ); // set command (start, write)
    190           $display("status: %t generate 'start', write cmd %0h (slave address+write)", $time, {SADR,WR} );
    191 
    192           // check tip bit
    193           u0.wb_read(1, SR, q);
    194           while(q[1])
    195                u0.wb_read(0, SR, q); // poll it until it is zero
    196           $display("status: %t tip==0", $time);
    197 
    198           // send memory address
    199           u0.wb_write(1, TXR,     8'h01); // present slave's memory address
    200           u0.wb_write(0, CR,      8'h10); // set command (write)
    201           $display("status: %t write slave memory address 01", $time);
    202 
    203           // check tip bit
    204           u0.wb_read(1, SR, q);
    205           while(q[1])
    206                u0.wb_read(0, SR, q); // poll it until it is zero
    207           $display("status: %t tip==0", $time);
    208 
    209           // send memory contents
    210           u0.wb_write(1, TXR,     8'ha5); // present data
    211           u0.wb_write(0, CR,      8'h10); // set command (write)
    212           $display("status: %t write data a5", $time);
    213 
    214           // check tip bit
    215           u0.wb_read(1, SR, q);
    216           while(q[1])
    217                u0.wb_read(1, SR, q); // poll it until it is zero
    218           $display("status: %t tip==0", $time);
    219 
    220           // send memory contents for next memory address (auto_inc)
    221           u0.wb_write(1, TXR,     8'h5a); // present data
    222           u0.wb_write(0, CR,      8'h50); // set command (stop, write)
    223           $display("status: %t write next data 5a, generate 'stop'", $time);
    224 
    225           // check tip bit
    226           u0.wb_read(1, SR, q);
    227           while(q[1])
    228                u0.wb_read(1, SR, q); // poll it until it is zero
    229           $display("status: %t tip==0", $time);
    230         
    231         #WAIT_TIME;
    232         $display("***************************");
    233           $display("test2: access slave (read)");
    234           $display("***************************");
    235 
    236        // drive slave address
    237        u0.wb_write(1, TXR,{SADR,WR} ); // present slave address, set write-bit
    238        u0.wb_write(0, CR,     8'h90 ); // set command (start, write)
    239        $display("status: %t generate 'start', write cmd %0h (slave address+write)", $time, {SADR,WR} );
    240 
    241        // check tip bit
    242        u0.wb_read(1, SR, q);
    243        while(q[1])
    244             u0.wb_read(1, SR, q); // poll it until it is zero
    245        $display("status: %t tip==0", $time);
    246 
    247        // send memory address
    248        u0.wb_write(1, TXR,     8'h01); // present slave's memory address
    249        u0.wb_write(0, CR,      8'h10); // set command (write)
    250        $display("status: %t write slave address 01", $time);
    251 
    252        // check tip bit
    253        u0.wb_read(1, SR, q);
    254        while(q[1])
    255             u0.wb_read(1, SR, q); // poll it until it is zero
    256        $display("status: %t tip==0", $time);
    257 
    258        // drive slave address
    259        u0.wb_write(1, TXR, {SADR,RD} ); // present slave's address, set read-bit
    260        u0.wb_write(0, CR,      8'h90 ); // set command (start, write)
    261        $display("status: %t generate 'repeated start', write cmd %0h (slave address+read)", $time, {SADR,RD} );
    262 
    263        // check tip bit
    264        u0.wb_read(1, SR, q);
    265        while(q[1])
    266             u0.wb_read(1, SR, q); // poll it until it is zero
    267        $display("status: %t tip==0", $time);
    268 
    269        // read data from slave
    270        u0.wb_write(1, CR,      8'h20); // set command (read, ack_read)
    271        $display("status: %t read + ack", $time);
    272 
    273        // check tip bit
    274        u0.wb_read(1, SR, q);
    275        while(q[1])
    276             u0.wb_read(1, SR, q); // poll it until it is zero
    277        $display("status: %t tip==0", $time);
    278 
    279        // check data just received
    280        u0.wb_read(1, RXR, qq);
    281        if(qq !== 8'ha5)
    282          $display("
    ERROR: Expected a5, received %x at time %t", qq, $time);
    283        else
    284          $display("status: %t 1th received %x", $time, qq);
    285 
    286        // read data from slave
    287        u0.wb_write(1, CR,      8'h68); // set command (read, nack_read,stop)
    288        $display("status: %t read + ack", $time);
    289 
    290        // check tip bit
    291        u0.wb_read(1, SR, q);
    292        while(q[1])
    293             u0.wb_read(1, SR, q); // poll it until it is zero
    294        $display("status: %t tip==0", $time);
    295 
    296        // check data just received
    297        u0.wb_read(1, RXR, qq);
    298        if(qq !== 8'h5a)
    299          $display("
    ERROR: Expected 5a, received %x at time %t", qq, $time);
    300        else
    301          $display("status: %t 2th received %x", $time, qq);
    302 
    303         #WAIT_TIME;
    304         $display("********************************************************");
    305           $display("test3: access slave (check invalid slave memory address)");
    306           $display("********************************************************");
    307 
    308 
    309           // drive slave address
    310           u0.wb_write(1, TXR, {SADR,WR} ); // present slave address, set write-bit
    311           u0.wb_write(0, CR,      8'h90 ); // set command (start, write)
    312           $display("status: %t generate 'start', write cmd %0h (slave address+write). Check invalid address", $time, {SADR,WR} );
    313 
    314           // check tip bit
    315           u0.wb_read(1, SR, q);
    316           while(q[1])
    317                u0.wb_read(1, SR, q); // poll it until it is zero
    318           $display("status: %t tip==0", $time);
    319 
    320           // send memory address
    321           u0.wb_write(1, TXR,     8'h10); // present slave's memory address
    322           u0.wb_write(0, CR,      8'h10); // set command (write)
    323           $display("status: %t write slave memory address 10", $time);
    324 
    325           // check tip bit
    326           u0.wb_read(1, SR, q);
    327           while(q[1])
    328                u0.wb_read(1, SR, q); // poll it until it is zero
    329           $display("status: %t tip==0", $time);
    330 
    331           // slave should have send NACK
    332           $display("status: %t Check for nack", $time);
    333           if(!q[7])
    334             $display("
    ERROR: Expected NACK, received ACK
    ");
    335 
    336           // stop
    337           u0.wb_write(1, CR,      8'h40); // set command (stop)
    338           $display("status: %t generate 'stop'", $time);
    339 
    340           // check tip bit
    341           u0.wb_read(1, SR, q);
    342           while(q[1])
    343           u0.wb_read(1, SR, q); // poll it until it is zero
    344           $display("status: %t tip==0", $time);
    345         
    346         #WAIT_TIME;
    347         $display("********************************************************");
    348           $display("test4: access slave (write and interrupt acknowledge)");
    349           $display("********************************************************");
    350 
    351         u0.wb_write(1, CTR,     8'hC0); // enable core and interrupt
    352         u0.wb_write(1,CR,8'h01);
    353           $display("status: %t core enabled", $time);
    354 
    355         //TODO
    356         // drive slave address
    357           u0.wb_write(1, TXR, {SADR,WR} ); // present slave address, set write-bit
    358           u0.wb_write(0, CR,      8'h90 ); // set command (start, write)
    359           $display("status: %t generate 'start', write cmd %0h (slave address+write)", $time, {SADR,WR} );
    360 
    361          
    362         //wait interrupt
    363         wait(inta0 == 1'b1);
    364         $display("status: %t interrupt assert",$time);
    365         u0.wb_read(1,SR,q);
    366         if(q[1])
    367           $display("status: %t transfer complete",$time);
    368         u0.wb_write(0, CR,      8'h01); // set command (IACK)
    369 
    370 
    371           // send memory address
    372           u0.wb_write(1, TXR,     8'h01); // present slave's memory address
    373           u0.wb_write(0, CR,      8'h10); // set command (write)
    374           $display("status: %t write slave memory address 01", $time);
    375 
    376 
    377         //wait interrupt
    378         wait(inta0 == 1'b1);
    379         $display("status: %t interrupt assert",$time);
    380         u0.wb_read(1,SR,q);
    381         if(q[1])
    382           $display("status: %t transfer complete",$time);
    383           u0.wb_write(0, CR,      8'h01); // set command (IACK)
    384 
    385           // send memory contents
    386           u0.wb_write(1, TXR,     8'ha5); // present data
    387           u0.wb_write(0, CR,      8'h10); // set command (write)
    388           $display("status: %t write data a5", $time);
    389 
    390           //wait interrupt
    391         wait(inta0 == 1'b1);
    392         $display("status: %t interrupt assert",$time);
    393         u0.wb_read(1,SR,q);
    394         if(q[1])
    395           $display("status: %t transfer complete",$time);
    396         u0.wb_write(0, CR,      8'h01); // set command (IACK)
    397 
    398          
    399           // send memory contents for next memory address (auto_inc)
    400           u0.wb_write(1, TXR,     8'h5a); // present data
    401           u0.wb_write(0, CR,      8'h50); // set command (stop, write)
    402           $display("status: %t write next data 5a, generate 'stop'", $time);
    403 
    404           //wait interrupt
    405         wait(inta0 == 1'b1);
    406         $display("status: %t interrupt assert",$time);
    407         u0.wb_read(1,SR,q);
    408         if(q[1])
    409           $display("status: %t transfer complete",$time);
    410         u0.wb_write(0, CR,      8'h01); // set command (IACK)
    411 
    412           #250000; // wait 250us
    413           $display("
    
    status: %t Testbench done", $time);
    414           $finish;
    415       end
    416 
    417 endmodule
    418 
    419 module delay (in, out);
    420   input  in;
    421   output out;
    422 
    423   assign out = in;
    424 
    425   specify
    426     (in => out) = (600,600);
    427   endspecify
    428 endmodule
    tst_bench_top.v

      以新添加的中断测试为例。这个case是根据test1改动而来的,区别就是将不断读取寄存器来判断上一指令是否响应完成改为等待中断+读取状态寄存器方式。后者不会过多占用CPU的资源,从软件从面来讲也适用于带有调度算法的操作系统应用。在case开始前启动中断使能并写IACK比特位清除之前的中断标志位。之后在每次写CR后通过下段代码完成等待中断等系列操作。

    //wait interrupt
    wait(inta0 == 1'b1);
    $display("status: %t interrupt assert",$time);
    u0.wb_read(1,SR,q);
    if(q[1])
    $display("status: %t transfer complete",$time);
    u0.wb_write(0, CR, 8'h01); // set command (IACK)

      这部分对应的波形如下,可见中断输出信号inta0被拉高多次。2字节写操作完成。

     五、总结

      折腾折腾还是有帮助的。之后有打算在此基础上进一步深入,比如搭建基于UVM的验证环境来重新验证这个IP、添加更多的case覆盖所有的features、将interface改成APB bus。

    六、参考

    1  WISHBONE I2C Master Core下载地址: https://opencores.org/projects/i2c

    2 Candence $shm_open $shm_probe 函数_Holden_Liu的博客-CSDN博客

    https://blog.csdn.net/holden_liu/article/details/91376709

  • 相关阅读:
    C# 运用StreamReader类和StreamWriter类实现文件的读写操作
    C# 理解FileInfo类的Open()方法
    C# 运用FileInfo类创建、删除文件
    C# 创建子目录
    C# 目录下的文件操作
    C# 运用DirectoryInfo类和FileInfo类
    C# 文件操作概述
    LINUX介绍
    linux iso 下载地址
    ADO.NET梳理
  • 原文地址:https://www.cnblogs.com/moluoqishi/p/13906339.html
Copyright © 2020-2023  润新知