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 ...\n\n");
- 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("\nstatus: %t Testbench started\n\n", $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("\nERROR: 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("\nERROR: 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("\nERROR: Expected NACK, received ACK\n");
- 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("\n\nstatus: %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后通过下段代码完成等待中断等系列操作。
// drive slave address
u0.wb_write(1, TXR, {SADR,WR} ); // present slave address, set write-bit
u0.wb_write(0, CR, 8’h90 ); // set command (start, write)
$display(“status: %t generate ‘start’, write cmd %0h (slave address+write)”, $time, {SADR,WR} );
这部分对应的波形如下,可见中断输出信号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