基于ZX-2型FPGA开发板的串口示波器(五)
系统仿真验证及testbench的编写
仿真验证:
以上分部分介绍了系统的各个关键模块的设计。接下来,我们来对该设计进行仿真验证。因为该实验是基于串口的,为了实现仿真验证,这里小梅哥分别编写了一个串口发送的仿真模型(Uart_Tx_Model)和一个串口接收的仿真模型(Uart_Rx_Model),两个仿真模型的设计都较为简单,但是我们却可以通过该模型模拟对我们的设计进行串口数据的发送和接收,并实时打印仿真模型发送的数据与接收到的数据。关于仿真模型的代码,这里只贴上代码,不做具体解释。(此贴回复超过100条我就专门开文讲解testbench的编写技巧)
以下为串口接收仿真模型的代码:
- 001 `timescale 1ns/1ps
- 002
- 003 module Uart_RX_Model(Baud_Set,uart_rx);
- 004
- 005 input [2:0]Baud_Set;/*波特率选择信号*/
- 006 input uart_rx;/*仿真模型串口接收引脚*/
- 007
- 008 reg Clk;/*仿真模型内部时钟,50M*/
- 009 reg Rst_n;/*仿真模型内部复位信号*/
- 010
- 011 wire Mid_Flag_Receive;/*数据中点(采样点)标志信号*/
- 012
- 013 reg Receive_Baud_Start;/*接收波特率生成使能信号*/
- 014 reg [7:0]rx_data;/*接收数据移位寄存器*/
- 015
- 016 reg [7:0]Rx_Byte;/*最终接收结果*/
- 017
- 018 initial Clk = 1;
- 019 always#10 Clk = ~Clk;
- 020
- 021 /*例化波特率设置模块*/
- 022 baud_select baud_select_Receive(
- 023 .Clk(Clk),
- 024 .Rst_n(Rst_n),
- 025 .Baud_Set(Baud_Set),
- 026 .Baud_Start(Receive_Baud_Start),
- 027 .Mid_Flag(Mid_Flag_Receive)
- 028 );
- 029
- 030 initial begin
- 031 Rst_n = 0;
- 032 Rx_Byte = 0;
- 033 rx_data = 0;
- 034 #100 Rst_n = 1;
- 035 end
- 036
- 037 /*接收一个字节的数据*/
- 038 initial begin
- 039 forever begin
- 040 @(negedge uart_rx)
- 041 begin
- 042 Receive_Baud_Start = 1;
- 043 @(posedge Mid_Flag_Receive);
- 044 @(posedge Mid_Flag_Receive)rx_data[0] = uart_rx;
- 045 @(posedge Mid_Flag_Receive)rx_data[1] = uart_rx;
- 046 @(posedge Mid_Flag_Receive)rx_data[2] = uart_rx;
- 047 @(posedge Mid_Flag_Receive)rx_data[3] = uart_rx;
- 048 @(posedge Mid_Flag_Receive)rx_data[4] = uart_rx;
- 049 @(posedge Mid_Flag_Receive)rx_data[5] = uart_rx;
- 050 @(posedge Mid_Flag_Receive)rx_data[6] = uart_rx;
- 051 @(posedge Mid_Flag_Receive)rx_data[7] = uart_rx;
- 052 @(posedge Mid_Flag_Receive)begin Receive_Baud_Start = 0;Rx_Byte = rx_data;end
- 053 $display("Master_receive Data = %0h",Rx_Byte);
- 054 end
- 055 end
- 056 end
- 057
- 058 endmodule
复制代码
以下为串口发送仿真模型的设计代码
- 001 `timescale 1ns/1ps
- 002
- 003 module Uart_Tx_Model(Baud_Set,Tx_Data,Tx_En,uart_tx,Tx_Done);
- 004
- 005 input [2:0]Baud_Set; /*波特率选择信号*/
- 006 input [7:0]Tx_Data; /*待发送数据字节*/
- 007 input Tx_En; /*数据字节发送使能信号*/
- 008 output reg uart_tx; /*仿真串口发送模型发送信号*/
- 009 output reg Tx_Done; /*发送完成信号*/
- 010
- 011 reg Clk; /*仿真模型内部工作时钟*/
- 012 reg Rst_n; /*仿真模型内部复位信号*/
- 013
- 014 wire Bps_Clk; /*发送波特率时钟波特率*/
- 015 reg Bps_En; /*发送波特率使能信号*/
- 016
- 017 initial Clk = 1;
- 018 always#10 Clk = ~Clk;
- 019
- 020 /*----例化发送波特率时钟生成模块-----*/
- 021 TxModel_Bps_Gen TxModel_Bps_Gen_send(
- 022 .Clk(Clk),
- 023 .Rst_n(Rst_n),
- 024 .Baud_Set(Baud_Set),
- 025 .Tx_Done(Tx_Done),
- 026 .Bps_Clk(Bps_Clk),
- 027 .Byte_En(Bps_En)
- 028 );
- 029
- 030 initial begin
- 031 Tx_Done = 0;
- 032 uart_tx = 1;
- 033 Rst_n = 0;
- 034 Bps_En = 0;
- 035 #100;
- 036 Rst_n = 1;
- 037 forever@(posedge Tx_En)/*每来一个发送使能信号即执行一次发送过程*/
- 038 Uart_Send(Tx_Data);
- 039 end
- 040
- 041 /*执行一次字节数据的发送*/
- 042 task Uart_Send;
- 043 input [7:0]Data;
- 044 begin
- 045 Bps_En = 1;
- 046 Tx_Done = 0;
- 047 $display("Uart_Send Data = %0h",Data);/*打印发送的数据*/
- 048 @(posedge Bps_Clk) #0.1 uart_tx = 0;
- 049 @(posedge Bps_Clk) #0.1 uart_tx = Data[0];
- 050 @(posedge Bps_Clk) #0.1 uart_tx = Data[1];
- 051 @(posedge Bps_Clk) #0.1 uart_tx = Data[2];
- 052 @(posedge Bps_Clk) #0.1 uart_tx = Data[3];
- 053 @(posedge Bps_Clk) #0.1 uart_tx = Data[4];
- 054 @(posedge Bps_Clk) #0.1 uart_tx = Data[5];
- 055 @(posedge Bps_Clk) #0.1 uart_tx = Data[6];
- 056 @(posedge Bps_Clk) #0.1 uart_tx = Data[7];
- 057 @(posedge Bps_Clk) #0.1 uart_tx = 1;
- 058 @(posedge Bps_Clk) #0.1 ;
- 059 Tx_Done = 1;
- 060 Bps_En = 0;
- 061 #20 Tx_Done = 0;
- 062 end
- 063 endtask
- 064
- 065 endmodule
复制代码
以下为仿真顶层模块的设计
- 001 `timescale 1ns/1ns
- 002 `include "../rtl/header.v"
- 003 module uart_scope_tb;
- 004 localparam KEY_WIDTH = 3;
- 005
- 006 reg Clk;
- 007 reg Rst_n;
- 008 reg [KEY_WIDTH - 1:0]Key_in;
- 009
- 010 reg ADC_Din;
- 011 wire ADC_Clk;
- 012 wire ADC_Cs_n;
- 013
- 014 /*波特率设置总线,此处默认为9600bps,仿真不做波特率修改测试*/
- 015 wire [2:0]Baud_Set;
- 016 reg [7:0]Tx_Data;/*串口发送仿真模型待发送数据字节*/
- 017 reg Tx_En; /*串口发送仿真模型发送使能信号*/
- 018 wire Rs232_MTSR; /*串口“主机(PC)发送-从机(FPGA)接收”信号*/
- 019 wire Rs232_MRST; /*串口“主机(PC)接收-从机(FPGA)发送”信号*/
- 020 wire Tx_Done; /*串口字节发送完成信号*/
- 021
- 022 assign Baud_Set = 3'd0;/*设置波特率为固定的9600bps*/
- 023
- 024 localparam
- 025 Header = 8'hAA, /*帧头*/
- 026 Length = 8'd3, /*帧长*/
- 027 Tail = 8'h88; /*帧尾*/
- 028
- 029 /*------例化串口示波器顶层模块------*/
- 030 uart_scope uart_scope(
- 031 .Clk(Clk),
- 032 .Rst_n(Rst_n),
- 033 .Rs232_Rx(Rs232_MTSR),
- 034 .Rs232_Tx(Rs232_MRST),
- 035 .Key_in(Key_in),
- 036 .ADC_Din(ADC_Din),
- 037 .ADC_Clk(ADC_Clk),
- 038 .ADC_Cs_n(ADC_Cs_n)
- 039 );
- 040
- 041 /*------例化串口发送仿真模型------*/
- 042 Uart_Tx_Model Uart_Tx_Model(
- 043 .Baud_Set(Baud_Set),
- 044 .Tx_Data(Tx_Data),
- 045 .Tx_En(Tx_En),
- 046 .uart_tx(Rs232_MTSR),
- 047 .Tx_Done(Tx_Done)
- 048 );
- 049
- 050 /*------例化串口接收仿真模型------*/
- 051 //该模型接收FPGA发送出来的数据并打印在modelsim的transcript窗口中
- 052 Uart_RX_Model Uart_RX_Model(
- 053 .Baud_Set(Baud_Set),
- 054 .uart_rx(Rs232_MRST)
- 055 );
- 056
- 057 /*-------生成50M时钟信号--------*/
- 058 initial Clk = 0;
- 059 always #10 Clk = ~Clk;
- 060
- 061 /*-------生成ADC_Din数据-------*/
- 062 /*此处不对ADC的采样结果多做计较,只要求保
- 063 证ADC_Din上有数据即可,有兴趣者可自己编写仿真模型*/
- 064 initial ADC_Din = 1;
- 065 always #1315 ADC_Din = ~ADC_Din;
- 066
- 067 initial begin
- 068 Rst_n = 1'b0;
- 069 Tx_En = 1'b0;
- 070 Tx_Data = 8'd0;
- 071 Key_in = 4'b1111;
- 072 #200;
- 073 Rst_n = 1'b1; /*释放复位信号,系统即进入正常工作状态*/
- 074 #1000;
- 075 En_DDS_Run; /*使能DDS信号发生器生成信号数据*/
- 076 #10000;
- 077 En_S_DDS; /*使能采样ADC数据*/
- 078 En_S_ADC; /*使能采样DDS数据*/
- 079 #10000;
- 080 En_UART_Send;/*使能串口发送,此时串口猎人软件上将会开始持续接收到数据*/
- 081 end
- 082
- 083 initial begin
- 084 #200_000_000;press_key(0);
- 085 #200_000_000;press_key(1);
- 086 #200_000_000;
- 087 $stop;
- 088 end
- 089
- 090
- 091
- 092 /*---发送命令帧数据任务-----*/
- 093 task Send_CMD;
- 094 input [7:0]DATAA,DATAB,DATAC;/*用户数据(地址、数据高字节,数据低字节)*/
- 095 begin
- 096 Tx_Data = Header;/*需发送数据为帧头*/
- 097 Tx_En = 1; /*启动发送*/
- 098 #20 Tx_En = 0; /*一个时钟周期后,清零发送启动信号*/
- 099 @(posedge Tx_Done)/*等待发送完成信号*/
- 100 #1000;
- 101
- 102 Tx_Data = Length;/*需发送数据为帧长,此处帧长只是数据内容的长度*/
- 103 Tx_En = 1; /*启动发送*/
- 104 #20 Tx_En = 0; /*一个时钟周期后,清零发送启动信号*/
- 105 @(posedge Tx_Done)/*等待发送完成信号*/
- 106 #1000;
- 107
- 108 Tx_Data = DATAA;/*需发送数据第一个字节,此数据代表外设寄存器的地址*/
- 109 Tx_En = 1; /*启动发送*/
- 110 #20 Tx_En = 0; /*一个时钟周期后,清零发送启动信号*/
- 111 @(posedge Tx_Done)/*等待发送完成信号*/
- 112 #1000;
- 113
- 114 Tx_Data = DATAB;/*需发送数据第二个字节,此数据代表写入外设寄存器的内容高8位*/
- 115 Tx_En = 1; /*启动发送*/
- 116 #20 Tx_En = 0; /*一个时钟周期后,清零发送启动信号*/
- 117 @(posedge Tx_Done)/*等待发送完成信号*/
- 118 #1000;
- 119
- 120 Tx_Data = DATAC;/*需发送数据第三个字节,此数据代表写入外设寄存器的内容低8位*/
- 121 Tx_En = 1; /*启动发送*/
- 122 #20 Tx_En = 0; /*一个时钟周期后,清零发送启动信号*/
- 123 @(posedge Tx_Done)/*等待发送完成信号*/
- 124 #1000;
- 125
- 126 Tx_Data = Tail;/*需发送数据为帧尾*/
- 127 Tx_En = 1; /*启动发送*/
- 128 #20 Tx_En = 0; /*一个时钟周期后,清零发送启动信号*/
- 129 @(posedge Tx_Done)/*等待发送完成信号*/
- 130 #1000;
- 131 #10000;
- 132 end
- 133 endtask
- 134
- 135 task En_DDS_Run;/*使能DDS生成数据*/
- 136 begin
- 137 Send_CMD(`DDS_En, 8'h00, 8'h01);
- 138 $display("En DDS Run");
- 139 end
- 140 endtask
- 141
- 142 task Stop_DDS_Run;/*停止DDS生成数据*/
- 143 begin
- 144 Send_CMD(`DDS_En, 8'h00, 8'h00);
- 145 $display("Stop DDS Run");
- 146 end
- 147 endtask
- 148
- 149 task En_S_DDS;/*使能采样DDS数据*/
- 150 begin
- 151 Send_CMD(`DDS_Sample_En, 8'h00, 8'h01);
- 152 $display("En Sample DDS data");
- 153 end
- 154 endtask
- 155
- 156 task Stop_S_DDS;/*停止采样DDS数据*/
- 157 begin
- 158 Send_CMD(`DDS_Sample_En, 8'h00, 8'h00);
- 159 $display("Stop Sample DDS data");
- 160 end
- 161 endtask
- 162
- 163 task En_UART_Send;/*使能串口发送*/
- 164 begin
- 165 Send_CMD(`UART_En_Tx, 8'h00, 8'h01);
- 166 $display("En UART Send");
- 167 end
- 168 endtask
- 169
- 170 task Stop_UART_Send;/*停止串口发送*/
- 171 begin
- 172 Send_CMD(`UART_En_Tx, 8'h00, 8'h00);
- 173 $display("Stop UART Send");
- 174 end
- 175 endtask
- 176
- 177 task En_S_ADC;/*使能采集ADC数据*/
- 178 begin
- 179 Send_CMD(`ADC_Sample_En, 8'h00, 8'h01);
- 180 $display("En Sample ADC data");
- 181 end
- 182 endtask
- 183
- 184 task Stop_S_ADC;/*停止采集ADC数据*/
- 185 begin
- 186 Send_CMD(`ADC_Sample_En, 8'h00, 8'h00);
- 187 $display("Stop Sample ADC data");
- 188 end
- 189 endtask
- 190
- 191 task Set_ADC_Sample_Speed;/*设置ADC采样率*/
- 192 input[25:0] Fs;/*采样率实际频率*/
- 193 reg [31:0] S_cnt_top;/*分频计数器计数最大值*/
- 194 begin
- 195 /*由采样实际频率值换算出采样分频计数器计数最大值*/
- 196 S_cnt_top = 50000000/Fs - 1;
- 197 /*写采样分频计数器计数最大值低16位*/
- 198 Send_CMD(`ADC_S_Cnt_Max_L,S_cnt_top[15:8],S_cnt_top[7:0]);
- 199 /*写采样分频计数器计数最大值高16位*/
- 200 Send_CMD(`ADC_S_Cnt_Max_H,S_cnt_top[31:24],S_cnt_top[23:16]);
- 201 $display("Set ADC Sample Speed as = %0d" ,Fs);
- 202 end
- 203 endtask
- 204
- 205 task Set_DDS_Sample_Speed;/*设置DDS数据的采样率*/
- 206 input[25:0] Fs;/*采样率实际频率*/
- 207 reg [31:0] S_cnt_top;/*分频计数器计数最大值*/
- 208 begin
- 209 /*由采样实际频率值换算出采样分频计数器计数最大值*/
- 210 S_cnt_top = 50000000/Fs - 1;
- 211 /*写采样分频计数器计数最大值低16位*/
- 212 Send_CMD(`DDS_S_Cnt_Max_L,S_cnt_top[15:8],S_cnt_top[7:0]);
- 213 /*写采样分频计数器计数最大值高16位*/
- 214 Send_CMD(`DDS_S_Cnt_Max_H,S_cnt_top[31:24],S_cnt_top[23:16]);
- 215 $display("Set DDS Sample Speed as = %0d" ,Fs);
- 216 end
- 217 endtask
- 218
- 219 task Set_DDS_Fout_Speed;/*设置DDS输出信号频率*/
- 220 input[25:0] Fs;/*输出信号实际频率*/
- 221 reg [31:0] r_fword;/*DDS频率控制字*/
- 222 begin
- 223 /*由实际要求输出频率数据换算出频率控制字*/
- 224 r_fword = Fs*65536*65536/50000000;
- 225 Send_CMD(`DDS_Fword_L,r_fword[15:8],r_fword[7:0]);
- 226 Send_CMD(`DDS_Fword_H,r_fword[31:24],r_fword[23:16]);
- 227 $display("Set DDS Fout as = %0d" ,Fs);
- 228 end
- 229 endtask
- 230
- 231
- 232 task press_key;
- 233 input [KEY_WIDTH/2:0]Key;
- 234 reg [15:0]myrand;
- 235 begin
- 236 Key_in = {KEY_WIDTH{1'b1}};
- 237 /*按下抖动*/
- 238 repeat(20)begin
- 239 myrand = {$random} % 65536;
- 240 #myrand Key_in[Key] = ~Key_in[Key];
- 241 end
- 242 Key_in[Key] = 1'b0;
- 243
- 244 #22000000;/*稳定期*/
- 245
- 246 /*释放抖动*/
- 247 repeat(20)begin
- 248 myrand = {$random} % 65536;
- 249 #myrand Key_in[Key] = ~Key_in[Key];
- 250 end
- 251 Key_in[Key] = 1'b1;
- 252 #22000000;/*稳定期*/
- 253 end
- 254 endtask
- 255
- 256 endmodule
复制代码
下图为系统仿真架构图:
这里,在我们提供的工程中,已经设置好了Nativelink,用户只需要在Quartus II中点击tools—run rtl simulation tool—rtl simulation即可自动调用modelsim-altera并执行仿真,因为这里完全模拟真实时序进行仿真,因此运行完整个仿真大约需要5—10分钟。
仿真完成后,结果如图所示:
其中,Rx_Byte为串口接收仿真模型接收到的数据,这里以波形的方式展示。ADC_Data为ADC采样结果,DDS_Data为DDS输出的数据最下方为按键标志和按键结果,当按下按键1时,数据通道切换为ADC的采样结果,当按下按键2时,数据通道切换为DDS的输出数据。
(如果用户在进行仿真的过程中发现仿真无法运行,在modelsim中提示错误的话,请删除simulation—>modelsim文件夹下除wave.do和mydo.do文件外的其他所有文件,然后在quartus 中重新启动仿真)
小梅哥
2015年4月8日 于至芯科技
|