新闻中心

EEPW首页 > 手机与无线通信 > 牛人业话 > 小梅哥和你一起深入学习FPGA之串口调试(一)(上)

小梅哥和你一起深入学习FPGA之串口调试(一)(上)

作者: 时间:2015-11-19 来源:网络 收藏

  原始代码验证

本文引用地址:https://www.eepw.com.cn/article/283059.htm

  前面,通对设计代码的一个简单分析,弄清楚了特权同学设计代码的基本架构和思路。那么看过特权同学教学视频的都知道,该代码能够实现一个字节的数据收发测试。那么这里,小梅哥就先对该设计进行一个仿真,通过仿真来分析此设计的性能。

  仿真的思路很简单,就是通过模拟串口发送过程,给该设计模块发送数据,由前面分析可知,该设计模块接收到数据后,会立即将数据发送出去,因此我们还需要对串口发送出来的数据进行分析,这里,熟悉Uart协议的,我们可以直接观察发送波形。当然,为了更加直观,我们也可以设计一个模拟串口接收数据的仿真模型,通过该模块来读取串口发送出来的数据。考虑到看这篇文章的大多是初学者,为了让大家能够更好的查看我们的仿真结果,同时教大家进行仿真模型的设计,小梅哥还是自己编写了一个虚拟的串口仿真模型。验证时,只需要将该仿真模型挂接到串口模块上,则该模型便能够自动的给串口模块发送数据,同时接收串口发送过来的数据。并会实时的将发送的数据和接收的数据打印出来,实际在观察仿真结果时,我们便只需要观看打印的结果就可以了。该串口仿真模型的代码如下所示:

  以下是代码片段:

  1 `timescale 1ns/1ps

  2

  3 module Uart_module ( uart_rx , uart_tx , send_state );

  4

  5 input uart_rx ;

  6 output reg uart_tx ;

  7 output reg send_state ;

  8

  9 reg Clk;

  10 reg Rst_n ;

  11

  12 wire Mid_Flag_send ;

  13 wire Mid_Flag_Receive ;

  14

  15 reg Receive_Baud_Start ;

  16 reg [ 7 : 0] rx_data ;

  17

  18 initial Clk = 1;

  19 always #10 Clk = ~Clk;

  20

  21 speed_select speed_select_Send (

  22 . clk( Clk),

  23 . rst_n ( Rst_n ),

  24 . bps_start ( 1'b1 ),

  25 . clk_bps ( Mid_Flag_send )

  26 );

  27

  28 speed_select speed_select_receive (

  29 . clk( Clk),

  30 . rst_n ( Rst_n ),

  31 . bps_start ( Receive_Baud_Start ),

  32 . clk_bps ( Mid_Flag_Receive )

  33 );

  34

  35 initial begin

  36 Rst_n = 0;

  37 uart_tx = 1;

  38 send_state = 0;

  39 #300 Rst_n = 1;

  40

  41 $display ( "Set Baud As 9600bps" );

  42 #200 ; Uart_Send ( 8'hb6 );

  43 #20 ; Uart_Send ( 8'he7 );

  44 #80 ; Uart_Send ( 8'hf0 );

  45 #500 ; Uart_Send ( 8'h02 );

  46 #300 ; Uart_Send ( 8'hb4 );

  47 #40 ; Uart_Send ( 8'he5 );

  48 #90 ; Uart_Send ( 8'hb0 );

  49 #1000 ; Uart_Send ( 8'h32 );

  50 #2000000 ;

  51 $stop ;

  52 end

  53

  54 task Uart_Send ;

  55 input [ 7: 0] Data ;

  56 begin

  57 send_state = 1;

  58 @( posedge Mid_Flag_send) #0.1 uart_tx = 0;

  59 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [0];

  60 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [1];

  61 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [2];

  62 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [3];

  63 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [4];

  64 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [5];

  65 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [6];

  66 @( posedge Mid_Flag_send) #0.1 uart_tx = Data [7];

  67 @( posedge Mid_Flag_send) #0.1 uart_tx = 1;

  68 $display ( "Uart_Send Data = %0h" , Data );

  69 send_state = 0;

  70 end

  71 endtask

  72

  73 initial begin

  74 forever begin

  75 @( negedge uart_rx )

  76 begin

  77 Receive_Baud_Start = 1;

  78 @( posedge Mid_Flag_Receive);

  79 @( posedge Mid_Flag_Receive) rx_data [0] = uart_rx ;

  80 @( posedge Mid_Flag_Receive) rx_data [1] = uart_rx ;

  81 @( posedge Mid_Flag_Receive) rx_data [2] = uart_rx ;

  82 @( posedge Mid_Flag_Receive) rx_data [3] = uart_rx ;

  83 @( posedge Mid_Flag_Receive) rx_data [4] = uart_rx ;

  84 @( posedge Mid_Flag_Receive) rx_data [5] = uart_rx ;

  85 @( posedge Mid_Flag_Receive) rx_data [6] = uart_rx ;

  86 @( posedge Mid_Flag_Receive) rx_data [7] = uart_rx ;

  87 @( posedge Mid_Flag_Receive) Receive_Baud_Start = 0;

  88 $display ( "Uart_receive Data = %0h" , rx_data );

  89 end

  90 end

  91 end

  92

  93 endmodule

  94

  因为在将代码复制到word的过程中,会有一定的格式兼容问题,所以文中部分格式不是太规范,望各位理解,另外,完整的代码,小梅哥也以pdf的形式提供了,感兴趣的朋友可以下载学习。

  本仿真模型的第一句话“`timescale 1ns/1ps”为仿真精度及时间的说明,定义时间精度为1ps,时间单位为1ns,那么我们在代码编写的过程中,如果写成“#200”则表示延时200ns,因为时间精度为1ps,因此我们还可以进一步提高延时精度,如“#200.1”表示延时200.1ns。一般的测试文件(testbench)中,这句话作为第一句话,必写,当然,时间精度和单位我们可以根据自己的需求更改,如写成“`timescale 1us/1ns”或者“`timescale 1ns/1ns”等都是可以的。

  该模块作为一个仿真模型,就是虚拟了一个串口收发仪器,既然是一个串口收发仪器,则必然有串口发送端口和串口接收端口,因此在模块名后面定义了三个端口。这与一般的testbench不同,一般的testbench作为仿真时的顶层,不需要端口,因此模块名后就直接以“;”结束。该模块的三个端口“uart_rx , uart_tx , send_state”分别为串口接收端口、串口发送端口、串口发送状态信号。串口收发端口不用说,大家也已经知道了,串口发送状态信号主要作为指示信号,指示当前仿真模型正在进行数据的发送过程。

  第9行至第16行为测试文件中信号的定义,以前我们总是理解说这些信号就是待测试模块的端口,需要在测试文件中定义。那么这里小梅哥更喜欢换一种方式来理解:我们自己的设计,本设计中即特权的串口模块,是一个功能未知的黑盒子,这个黑盒子有一些信号线引出,有的信号线是作为输入的,即需要外部输入一定的信号作为激励,而有的信号线是作为输出的,能够输出一些数据,当然还有一些信号线是既能够作为输入,又能够作为输出的,即三态。我们要想知道这个黑盒子的功能,就需要给这个黑盒子的输入信号线接上信号源,通过给这些输入信号线一定的激励,观察其输出端口上的响应,从而获知该黑盒子的功能。那么在这里,对于待测试模块的输入端口,我们就接上信号发生器,对于输出端口,我们就接上示波器或者逻辑分析仪,这样,我们就能够通过信号发生器给输入端口产生一定的激励,然后通过示波器观察输出端口的输出了。即如下图所示:

    

 

  那么,我们的testbench主要实现信号发生器的功能,既然是信号发生器那么就一定有数据信号输出,这个数据信号输出就可以连接到我们的待测模块上。待测试模块的输出端口,连接到我们的示波器或者逻辑分析仪的探头上,这样就实现了一个完整的测试系统,那么我们信号发生器的信号源,可能命名叫做,a,b,c,d,e……. 而我们示波器的探头则命名为探头1,探头2……接下来就好理解了,在testbench,我们将信号发生器的输出信号定义为reg型,而示波器的探头定义为wire型。我们信号发生器的输出信号线和示波器的探头线都可以任意命名,实际使用时一一对应连接到待测试模块的端口上,也可以就直接与待测模块的各个端口名保持一致。本设计中,小梅哥让testbench中的信号与待测模块的端口保持一致。

  第18行和19行为产生50MHz时钟的语句。

  因为本仿真模型实质上就是一个串口收发模块,因此也需要有收发波特率发生器,这里小梅哥为了省事,直接调用了特权同学的波特率发生器模块,来作为我仿真模型的波特率发生器。因为该波特率发生器本身也属于待测试部分,小梅哥之所以敢放心的调用,是因为事先我已经通过仿真,确定了该波特率发生器功能的正确性。第21行至33行为分别例化得到发送波特率发生器和接收波特率发生器的代码。

  第54行至71行为发送一个完整字节的数据(自动添加起始位和停止位)的代码,该部分写成任务的形式,方便调用。当我们需要发送一个字节的数据时,例如,发送8'hb6,只需要写“Uart_Send ( 8'hb6 )”即可,该任务便将自动执行,将数据发送出去。在一个字节的数据发送完成后,同时使用系统任务$display来打印当前发送的数据是多少,以方便我们直观的观察仿真运行过程。至于$display这个系统任务中各个部分的含义,请读者自行阅读verilog的语法书。代码的42至49行便是调用此任务进行了多次数据的发送。

  73行至91行为模拟串口接收部分,通过对串口模块发送出来的数据进行接收,并将接收到的数据用$display函数打印出来。我们只需要阅读发送数据和接收数据后打印出来的信息,即可判断通信是否成功,待测模块功能是否正常。

  这里需要注意的是,打印出来的接收数据和发送数据是针对仿真模型来说的,send data是仿真模型发送出去的数据,对应待测模块应该接收到的数据。receive data则是仿真模型接收到的数据,对应待测模块发送的数据。

  我们所编写的测试文件,一定要是可控的,即在所有事务完成后,将仿真停下来,否则,仿真会一直进行下去,导致出现大量冗余波形,影响我们对仿真结果的分析。因此在第51行,当所有测试已经完成后,使用系统任务$stop将仿真停下来。

  以上对小梅哥写的串口仿真模型进行了介绍,在实际使用中,只需要将该模型与待测模块按照如下图所示的方式连接起来即可。

    

 

  这里,小梅哥使用一个testbench文件作为顶层,将这两个部分连接起来,同时产生my_uart_top工作所需的时钟和复位信号。该文件详细代码如下:

  1 `timescale 1ns /1ns

  2

  3 module Uart_tb ;

  4

  5 reg Clk;

  6 reg Rst_n ;

  7

  8 wire uart_rx ;

  9 wire uart_tx ;

  10 wire send_state ;

  11

  12 my_uart_top u1 (

  13 . clk( Clk),

  14 . rst_n ( Rst_n ),

  15 . rs232_rx ( uart_tx ),

  16 . rs232_tx ( uart_rx )

  17 );

  18

  19 Uart_module u2 (

  20 . uart_rx ( uart_rx ),

  21 . uart_tx ( uart_tx ),

  22 . send_state ( send_state )

  23 );

  24

  25 initial begin

  26 Clk = 1;

  27 Rst_n = 0;

  28 #200 ;

  29 Rst_n = 1;

  30 end

  31

  32 always #10 Clk = ~Clk;

  33

  34 endmodule

  35

  该代码实在简单,只是实现了一个启动时的初始化和50MHz时钟的产生,因此小梅哥就不做任何分析了。

 


上一页 1 2 3 下一页

关键词: FPGA 串口调试

评论


相关推荐

技术专区

关闭