一、前言

  很久没写技术博客了,有些懈怠,生活还得继续折腾。转眼工作一年多,时间越长越发觉得自己知之甚少,当然这跟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

最新文章

  1. Android Studio的配置问题——Intel HAXM is required to run this AVD,VT-x is disabled in BIOS;
  2. poj 1195 - Mobile phones(树状数组)
  3. 深入理解javascript---如何编写高质量的代码?
  4. js css 构建滚动边框
  5. php checkbox复选框值的获取与checkbox默认值输出方法
  6. C语言知识总结
  7. 利用 html的锚点(元素a)功能实现ajax单页面应用的浏览器后退前进功能
  8. Oracle 树操作
  9. ZOJ 3696 Alien's Organ 概率论 泊松分布
  10. python小白之路
  11. freeSSHD在windows环境下搭建SFTP服务器
  12. PHP常用设计模式讲解
  13. ASP.NET Core 应用发布与部署指南
  14. css 别人找的css特效
  15. (贪心 线段不相交问题)codeVs 1214 线段覆盖
  16. CentOS7利用systemctl添加自定义系统服务
  17. windows 批处理语言学习
  18. Fiddlercore Demo - Fiddler
  19. 上下栏固定, 中间滚动的HTML模板
  20. App Transfer:苹果允许iOS App从一个开发者帐号转至另一个开发者账号

热门文章

  1. GZip 压缩解压 工具类 [ GZipUtil ]
  2. 动态生成简约MVC请求接口|抛弃一切注解减少重复劳动吧
  3. 深入理解Callable接口
  4. Web开发初探之JavaScript 快速入门
  5. 查杀进程小工具——WPF和MVVM初体验
  6. LiteOS-任务篇
  7. Varnish 不重启使之配置生效的方法
  8. 收集的照片信息都是Excel超链接?批量命名很困难?来试试这个自制的下载器吧!
  9. lua 源码阅读 5.3.5 笔记
  10. 2014年 实验一  C2C个人拍卖