一、前言
很久没写技术博客了,有些懈怠,生活还得继续折腾。转眼工作一年多,时间越长越发觉得自己知之甚少,当然这跟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
+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
以新添加的中断测试为例。这个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