Testbench仿真串口自收发通信
0赞以前仿真用的都是ISE自带的Test Bench Waveform。图形化界面,只要点点添加把信号拉高或者拉低进行激励添加,可谓方便快捷。但是所有的仿真激励都用TBW里做恐怕在大一些的项目模块仿真时就会遇到困难了,之前的IIC收发通信用TBW里添加仿真激励就显得有些为难了,所以在对RTL级的verilog设计有了一定程度的掌握后,下一步就该加强自己编写verilog testbench的能力了。
今天就以一个简单的串口自收发通信开始吧,这个RTL级代码在前面的日志里。
Verilog test fixture:
`timescale 1ns / 1ps
module testbench;
// DATE: Sat Aug 11 10:03:33 2007
// TITLE:
// MODULE: my_uart_top
// DESIGN: my_uart_top
// FILENAME: my_uart_top
// PROJECT: test
// VERSION: Version
// Inputs
reg clk;
reg rst_n;
reg rs232_rx;
// Outputs
wire rs232_tx;
// Bidirs
// Instantiate the UUT
my_uart_top d (
.clk(clk),
.rst_n(rst_n),
.rs232_rx(rs232_rx),
.rs232_tx(rs232_tx)
);
// Initialize Inputs
parameter RESET_TIME = 1000;
initial begin
rst_n = 1'b0; // RESET pulse
# RESET_TIME rst_n = 1'b1; //after 1us,RESET over
end
parameter CLOCK_20NS = 20;
initial begin
clk = 1'b0;
forever
# (CLOCK_20NS/2) clk = ~clk; // 20ns CLOCK
end
parameter BPS_9600 = 104140,
SEND_DATA = 8'b1010_0011;
integer i;
initial begin
rs232_rx = 1'b1; //RESET rs232_rx
# BPS_9600 rs232_rx = 1'b0;// transmit START bit
for (i=0;i<8;i=i+1) //transmit DATA byte
# BPS_9600 rs232_rx = SEND_DATA[i];
# BPS_9600 rs232_rx = 1'b0;// transmit STOP bit
# BPS_9600 rs232_rx = 1'b1;// bus IDLE
end
//
reg[7:0] tran_data; // receive transmit data byte
always @ (negedge rs232_tx) begin
# (BPS_9600/2)
for (i=0;i<8;i=i+1)
# BPS_9600 tran_data[i] = rs232_tx; //receive data byte
# BPS_9600
# (BPS_9600/2);
end
endmodule
仿真后的波形如下:(明显比单纯用TBW来得方便)
rs232_rx信号为仿真PC发送数据到FPGA里,上图可以看到rs232_rx线的变化。
rs232_tx为FPGA发送数据给PC,数据正是PC发送过来的原数据,tran_data是模拟PC接收到来自FPGA的数据,可以看到最终PC接收FPGA发送回来的数据和PC发送给FPGA的数据是一致的。自此,这个仿真说明了源代码设计是正确的。
前面的testbench只是比较简单的,因为就做了一次的数据收发。然而在实际中要验证一个设计的正确与否,往往应该是要经受住无数次的考验才算满足要求。基于此,下面的testbench的任务就是从0开始一直发到255一共就是256此验证。这么多此的测试光看波形可不成,尤其定位错误的时候非累死你不可,所以就应该采用$display这个命令,在modulsim的命令窗口里观察就可以了:
上面这个截图会将写入的数据和读出的数据实时的显示出来,而且监视着当前是否错误。这样的仿真验证就比较容易差错了。
Testbench代码如下:
module vtf_uart_top_v;
// DATE: Sat Aug 11 10:03:33 2007
// TITLE:
// MODULE: my_uart_top
// DESIGN: my_uart_top
// FILENAME: my_uart_top
// PROJECT: test
// VERSION: Version
// Inputs
reg clk;
reg rst_n;
reg rs232_rx;
// Outputs
wire rs232_tx;
// Bidirs
// Instantiate the UUT
my_uart_top d (
.clk(clk),
.rst_n(rst_n),
.rs232_rx(rs232_rx),
.rs232_tx(rs232_tx)
);
// Initialize Inputs
parameter RESET_TIME = 1000;
initial begin
rst_n = 1'b0; // RESET pulse
# RESET_TIME rst_n = 1'b1; //after 1us,RESET over
end
parameter CLOCK_20NS = 20;
initial begin
clk = 1'b0;
forever
# (CLOCK_20NS/2) clk = ~clk; // 20ns CLOCK
end
parameter BPS_9600 = 104140;
integer i;
reg[7:0] test_data;
initial begin
rs232_rx = 1'b1;
test_data = 0;
repeat (256) begin
transmit_data(test_data);
# (BPS_9600*15); //delay 1 unit transmit time
test_data = test_data+1;
end
end
task transmit_data;
input[7:0] SEND_DATA;
begin
rs232_rx = 1'b1; //RESET rs232_rx
# BPS_9600 rs232_rx = 1'b0;// transmit START bit
$display ("write data: %d\n",SEND_DATA);
for (i=0;i<8;i=i+1) //transmit DATA byte
# BPS_9600 rs232_rx = SEND_DATA[i];
# BPS_9600 rs232_rx = 1'b0;// transmit STOP bit
# BPS_9600 rs232_rx = 1'b1;// bus IDLE
end
endtask
//
reg[7:0] tran_data; // receive transmit data byte
integer error_counter; // error times per 256 times transmit
initial begin
error_counter = 0;
end
always @ (negedge rs232_tx) begin
# (BPS_9600/2)
for (i=0;i<8;i=i+1)
# BPS_9600 tran_data[i] = rs232_tx; //receive data byte
# BPS_9600
$display ("read data: %d\n",tran_data);
if(tran_data != test_data) error_counter = error_counter + 1;
# (BPS_9600/2);
$display ("%d errors occur current transmit\n",error_counter);
end
endmodule