一、前言

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

版权声明:本文为moluoqishi原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/moluoqishi/p/13906339.html