今天在看黑金AX309FPGA开发板自带教程中的EEPROM那一章,考虑如何写其中iic_com模块的TestBench,难点在于1. 该模块存在一个inout型的端口信号;2. 时序较为复杂,不可能在TestBench中完全计算出准确的延时;3. 存在应答信号。具体的模块设计内容见附件。
对于该模块,首先需要对iic_com模块做一定的修改,在信号列表中,加入如下内容:
1 ////////////////////////////////////////////// 2 output is_out_w; //只是为了TestBench用 3 output [4:0] i_w; 4 /////////////////////////////////////////////
1 //////////////////////////////////////////////// 2 assign is_out_w = is_out; //只是为了TestBench用 3 assign i_w = i; 4 ////////////////////////////////////////////////
即把is_out信号和i信号作为输出信号引出来,这样在写TestBench的时候就可以根据这两个信号的变化来作出具体的驱动变化。
然后,因为原模块中存在inout型的端口信号,而对于这种信号一般的处理方法是利用一个寄存器变量来控制器方向,该变量在iic_com模块就是is_out,如下所示:
1 assign sda = is_out ? r_sda : 1'bz;
若is_out为1,则sda作为输出,输出值为r_sda;若is_out为0,则sda为高阻,即作为输入使用。而在TestBench中,同样需要考虑其输入输出问题,而且我们知道TestBench相当于是给原模块一个激励信号,所以必须要设计好不能使得两个模块输入输出冲突,所以我在TestBench中同样采用一个寄存器变量来控制器方向,但该寄存器和原模块中is_out的值正好相反,如下所示:
1 assign sda = (!is_out) ? sda_r : 1'bz;
注意is_out前面有一个“!”。
接下来,需要考虑其时序复杂问题,其实时序复杂并不是问题,但是如果因为时序复杂而一点一点计算出用了多少个clock,然后在TestBench中用#延时的方法就有点不够灵活了。我在处理中根据引入的i信号来做处理,这个i在原模块中是用来存储状态值的,所以我在TestBench中可以根据i的值来判断当前进入到了哪个状态,然后根据状态值来作出相应的处理。
最后一点是应答信号,这里我是把应答信号给屏蔽掉了,也是为了简化TestBench的书写复杂度,当然也可以设计更为复杂的TestBench来一并测试应答信号,屏蔽的方式如下:
1 16: begin 2 if (is_ack != 0) //don't receive acknowledge signal 3 //i <= 5'd0; 4 i <= go; /////////////////测试///////////////// 5 else 6 i <= go; 7 end
即不论is_ack的值是多少,都会继续进行状态转移。
我设计的TestBench模块内容如下:
1 `timescale 1ns / 1ps 2 module iic_com_tb; 3 reg clk; //系统时钟 4 reg rst_n; //复位信号 5 reg [1:0] start_sig; //写读开始信号 6 reg [7:0] addr_sig; //写读地址信号 7 reg [7:0] wr_data; //写数据输入 8 wire [7:0] rd_data; //读数据输出 9 wire done_sig; //写读完成标志 10 wire scl; //IIC SCL 11 wire sda; //IIC SDA 12 wire is_out; //控制SDA输入输出方向的信号 13 wire [4:0] i; //存储状态机状态值 14 15 reg sda_r; 16 assign sda = (!is_out) ? sda_r : 1'bz; //is_out控制sda的方向,注意is_out前面的!,即控制其方向与原模块中不同 17 18 //生成时钟,周期为20ns 19 initial 20 begin 21 clk = 0; 22 forever 23 #10 24 clk = ~clk; 25 end 26 27 //生成复位信号 28 initial 29 begin 30 rst_n = 0; 31 #30 32 rst_n =1; 33 end 34 35 //写读地址和写数据位固定值 36 initial 37 begin 38 addr_sig = 8'b1010_1010; 39 wr_data = 8'b0101_0101; 40 end 41 42 //根据状态值,产生不同的驱动信号 43 reg c; //c信号没有实际作用,只是用于后面的延时 44 initial 45 begin 46 c = 0; 47 start_sig = 2'b00; 48 sda_r = 0; 49 #50 50 start_sig = 2'b01; //01代表写 51 while (i != 6) //状态6是写过程中的最后一个状态 52 begin 53 c = #1 c + 1; //用该方式延时1个时间单位 54 end 55 #20 //这个延时是必须的,延时一个时钟周期,不能多也不能少 56 start_sig = 2'b10; //10代表读 57 while (i >= 0 && i <= 27 && i != 8) //状态8是读过程中的最后一个状态 58 begin 59 if (i >= 19 && i <= 26) //19到26状态时用于读取sda信号线上的值 60 begin 61 case (i) 62 19: begin sda_r = 1'b1; c = #1 c + 1; end 63 20: begin sda_r = 1'b1; c = #1 c + 1; end 64 21: begin sda_r = 1'b1; c = #1 c + 1; end 65 22: begin sda_r = 1'b1; c = #1 c + 1; end 66 23: begin sda_r = 1'b0; c = #1 c + 1; end 67 24: begin sda_r = 1'b0; c = #1 c + 1; end 68 25: begin sda_r = 1'b0; c = #1 c + 1; end 69 26: begin sda_r = 1'b0; c = #1 c + 1; end 70 endcase 71 end 72 else 73 begin 74 c = #1 c + 1; 75 end 76 end 77 $stop(); 78 end 79 80 iic_com inst( 81 .clk (clk), 82 .rst_n (rst_n), 83 .start_sig (start_sig), 84 .addr_sig (addr_sig), 85 .wr_data (wr_data), 86 .rd_data (rd_data), 87 .done_sig (done_sig), 88 .scl (scl), 89 .sda (sda), 90 .is_out_w (is_out), 91 .i_w (i) 92 ); 93 94 endmodule
仿真波形如下:
我的邮箱地址:zhsj1993@126.com。欢迎大家来信交流讨论。另外,附上两个文件iic_com.v和iic_com_tb.v:http://files.cnblogs.com/files/zhsj/iic_com.zip
声明:源文件中的iic_com.v是来自黑金教程中的代码,为了写TestBench本人做了一定的修改,如果存在版权问题,请通知我,我会第一时间删帖!