FPGA Verilog开发实战指南:基于Intel Cyclone IV(进阶篇)
上QQ阅读APP看书,第一时间看更新

2.2 实战演练

2.2.1 实验目标

使用两个读写时钟不同的双口RAM实现低速模块处理高速数据的乒乓操作。

2.2.2 程序设计

1. 整体说明

首先画出工程的整体模块框图,如图2-2所示。

图2-2 乒乓操作模块整体框图

图2-2中,ram为数据缓冲模块,这里我们使用两个双端口RAM来缓存数据。clk_gen为时钟生成模块,使用PLL核来生成不同的读写时钟。data_gen为数据生成模块,产生输入数据(由于我们是举例为大家讲解乒乓操作,数据就由我们自己产生)。ram_ctrl为输入输出数据流选择模块。pingpang为顶层模块。各模块简介如表2-1所示。

表2-1 工程模块简介

下面分模块为大家讲解。

2. 时钟生成模块

这里我们调用PLL核来生成RAM的读写时钟。生成一个50MHz时钟(写时钟),一个25MHz时钟(读时钟)。使用50MHz时钟输入数据,25MHz时钟输出数据。具体调用方法在基础篇第20章已有详细讲解,这里不再过多说明。

3. 数据缓冲模块

该实验调用RAM IP核作为存储单元来完成乒乓操作。此处调用RAM时需要注意的是:由于我们使用的是不同时钟进行数据输入输出,所以需要使用不同的RAM读写时钟,由于使用的是50MHz时钟输入数据,25MHz时钟输出数据,所以这里需要设置RAM的写入时钟为50MHz,读出时钟为25MHz。

由于我们设置的读写时钟不一致,而要实现乒乓操作的无缝缓存与处理,因此需要设置不同的写入数据位宽与不同的读出数据位宽,才能与读写时钟相匹配,这里就体现了面积与速度互换原则。此处设置的输入时钟的频率是输出时钟的两倍,即输入数据的速度是输出数据速度的两倍,所以这里需要设置输出数据的位宽是输入数据位宽的两倍,即面积的两倍。换句话说,就是输入速度与面积的乘积与输出速度与面积的乘积要相等,即输入和输出的时间相等,这样才能保证在“数据缓冲模块1”读/写完的同时“数据缓冲模块2”也写/读完,才能保证输入与输出数据的无缝传输与处理。这就是其低高速数据传输[1]特点的原理,只要我们遵从输入与输出数据的频率与位宽的乘积相等,那么就可以实现不同模块频率之间的数据传输。

这里设置写入RAM的数据位宽为8位,读出RAM的数据位宽为16位,深度都设置为128。当然大家也可自行设置时钟频率与数据位宽,只要频率与位宽的乘积相等即可。同样的原理,若接收模块为低速模块,需要输出的数据为串行数据(1位)时,我们设置相应的频率和位宽即可实现数据的并行输入、串行输出的无缝处理。

这里调用简单双端口RAM即可满足该功能,具体的调用方法在基础篇第20章已有详细讲解,大家跟着操作步骤,结合自己的需求去调用即可,这里不再过多说明。

4. 数据生成模块

数据生成模块需要生成输入RAM中的数据,这里为了方便产生,我们循环生成数据8’d0~8’d199。8’d0~8’d99作为第一包数据写入第一个缓冲模块,8’d100~8’d199作为第二包数据写入第二个缓冲模块,依次循环写入。当然也可生成不同的数据流,只要满足RAM中设置的深度及位宽即可。

(1)模块框图

模块框图如图2-3所示。

图2-3 数据生成模块框图

模块输入输出信号描述如表2-2所示。

表2-2 数据生成模块输入输出信号描述

这里我们生成一个数据的使能信号,当使能信号为高时,输出数据有效,具体的时序通过绘制波形图进行介绍。

(2)波形图绘制

如图2-4所示,复位之后就开始拉高使能信号(data_en),让其开始传输数据。所以只需要在使能为高时让数据像计数器一样一直加即可,加到199时让其归0,从头开始相加,这样就能产生循环的数据了。

图2-4 数据生成波形图

(3)代码编写

参照绘制的波形图编写模块代码,具体参见代码清单2-1。

代码清单2-1 数据生成模块参考代码(data_gen.v)


 1 module  data_gen
 2 (
 3     input   wire        clk_50m     ,   // 模块时钟,频率为50MHz
 4     input   wire        rst_n       ,   // 复位信号,低电平有效
 5
 6     output  reg         data_en     ,   // 数据使能信号,高电平有效
 7     output  reg [7:0]   data_in         // 输出数据
 8
 9 );
10
11 // ******************************************************************** //
12 // ******************************* Main Code ************************** //
13 // ******************************************************************** //
14
15 // data_en:让其一直为高电平,一直输出数据
16 always@(posedge clk_50m or  negedge rst_n)
17     if(rst_n == 1'b0)
18         data_en  <=  1'b0;
19     else
20         data_en  <=  1'b1;
21
22 // data_in:循环生成写入的数据(8'd0 ~ 8'd199)
23 always@(posedge clk_50m or  negedge rst_n)
24     if(rst_n == 1'b0)
25         data_in <=  8'd0;
26     else    if(data_in  == 8'd199)
27         data_in <=  8'd0;
28     else    if(data_en == 1'b1)
29         data_in <=  data_in +   1'b1;
30     else
31         data_in <=  data_in;
32
33 endmodule

5. 输入输出数据选择模块

(1)模块框图

该模块是乒乓操作的核心模块,我们需要通过该模块对输入输出数据进行选择,从而达到乒乓操作的处理效果。模块框图如图2-5所示。

图2-5 输入输出数据选择模块框图

该模块需要产生控制两个双端口RAM的读写相关信号,同时将两个RAM中读出的数据作为输入,通过简单的处理后对读出的数据进行输出。该模块的各个信号描述如表2-3所示。

表2-3 数据选择模块输入输出信号描述

我们通过一个简单的状态机了解模块要实现的功能,如图2-6所示。

图2-6 数据选择状态跳转图

▪ IDLE:初始状态,在不工作或复位时就让状态机置为初始状态。

▪ WRAM1:写RAM1状态。该状态下我们开始往RAM1中写入数据,此时由于RAM2中并没有写入数据,所以不用对RAM2进行读取。那什么时候跳转到这个状态呢?从前面的数据生成模块中可知,当输入数据使能为高时,数据有效,开始传输,所以当数据使能为高时,让状态跳转到写RAM1状态,在该状态下将第一个数据包(8’d0~8’d99)写入RAM1之中。

▪ WRAM2_RRAM1:写RAM2读RAM1状态,当第一包数据写入完毕之后,马上跳到该状态,将第二包数据写入RAM2中的同时读出RAM1中写入的第一包数据。当第二包数据写完之后,第一包数据应该也是刚好读完的,此时跳转到下一状态。

▪ WRAM1_RRAM2:写RAM1读RAM2状态。在该状态下开始向RAM1中写入第三包数据,此时第三包数据会把第一包数据覆盖,而第一包数据已经读取出来了,并不会使数据丢失。在往RAM1中写入第三包数据的同时,读出RAM2中的第二包数据,当读写完成之后,跳回WRAM2_RRAM1状态,开始下一包数据的写入以及读取,如此循环,就能无缝地将连续的输入数据读取出来了。

通过状态机的讲解,相信大家对大概的控制流程都已了解,下面通过绘制波形图具体讲解各个信号的时序逻辑。

(2)波形图绘制

首先看看RAM的写入相关信号的时序波形图,如图2-7所示。

图2-7 写入信号时序波形图

前面介绍过输入数据用的是50MHz时钟,所以使用50MHz时钟进行对各输入相关信号进行控制。

如图2-7所示,当检测到数据使能信号(data_en)为高时,跳转到WRAM1状态,这里使用时钟的下降沿进行检测触发跳转,这是为什么呢?通过对RAM的学习我们知道,无论是写入还是读取,都是在时钟的上升沿进行的,而如果使用时钟的上升沿产生使能、地址、数据的话、写入或读取时上升沿采集到的就是数据变化的那一刻,这样采集到的信号可能就是不稳定的状态,从而导致数据出错,所以如果使用时钟的下降沿去产生这些信号的话,上升沿就能采集到数据的稳定状态了。

要往ram里写入数据,需要产生写使能和写地址和写数据信息。

▪ ram1_wr_en:RAM1写使能,初始值为0。当状态机为写RAM1状态时,我们让RAM1写使能为高,这里可以使用组合逻辑赋值。

▪ ram1_wr_addr:RAM1写地址,初始值为0。当RAM1写使能为高时让写地址开始相加,一个时钟写一个数据,同样采用时钟的下降沿触发。当地址加到8’d99时,说明100个数据已经写完,。写完之后地址归0,状态机跳到下一状态。

▪ ram1_wr_data:RAM1写数据。RAM1和RAM2中的写数据都是由数据生成模块传过来的,而上一模块的数据是由时钟上升沿产生的,所以这里需要先对传来的数据使用下降沿进行寄存,当写使能为1时,让写入的数据为寄存的数据即可。这里我们使用组合逻辑赋值,这样使能、地址、数据在同一时钟沿下才能相互对应。

当状态机跳转到WRAM2_RRAM1状态时,需要在往RAM2中写入数据的同时读取RAM1中的数据。往RAM2中写入数据时,使能、地址和数据的时序产生方法与RAM1的使能、地址和数据的时序产生方法是一致的,这里不再过多讲解。

RAM2写完之后,状态机跳转到WRAM1_RRAM2状态,在该状态需要对RAM1写入,RAM2读出,相关信号的时序与前面状态的产生方法一致。写完之后又跳回WRAM2_RRAM1状态,如此循环。

介绍完ram写使能相关信号的时序之后,下面看一下读相关信号的时序该如何产生,如图2-8所示。

图2-8 读信号时序波形图

同写相关信号一样,读相关信号也使用时钟下降沿产生,这样读数据时能采集到稳定的读地址。我们使用的读时钟是25MHz时钟,所以读相关信号也使用该时钟产生。

如图2-8所示,状态机是在clk_50m时钟的下降沿处变化的,在WRAM2_RRAM1状态时需要读取RAM1里的数据,这时就需要产生读使能和读地址。在该状态下让读使能为1,此时不能用组合逻辑去产生读使能,而需要使用clk_25m时钟下降沿触发去产生,这样读使能才能与读时钟对应。

▪ ram1_rd_addr:在读RAM1使能信号为高时让其一直加即可,因为我们设置的读取数据位宽是16bit,是输入数据位宽的两倍,即读出的一个数据为写入的两个数据。所以当其地址加到49时,表明读出了50个16bit数据,这说明写入的100个8bit数据已读完。这个时候我们让地址归0,等待下一次的读取。
当状态为WRAM1_RRAM2时,需要读取RAM2中的数据,使能信号和地址信号的产生方法与RAM1一致。
读使能信号和地址产生了之后,读时钟的上升沿采集到使能和地址信号后就会读出数据,每个数据为16bit,为两个写入数据。即读取的第一个数据为写入的前两个数据16’h0100,写入RAM1的最后两个数据为十进制的98、99,转换为十六进制就是62、63,所以读取的最后一个数据为16’h6362。同理,RAM2读取的第一个数据为16’h6564,最后一个数据为16’hc7c6。

▪ data_out:乒乓操作输出的数据。当读RAM1使能为高时,输出读RAM1的值;读RAM2使能为高时,输出读RAM2的值。因为读数据是在读时钟上升处产生的,所以我们使用读时钟下降沿将读出的数据给data_out输出,这样就能无缝地把写入的数据全部输出。

(3)代码编写

参照绘制的波形图编写模块代码,具体参见代码清单2-2。

代码清单2-2 输入输出数据选择模块参考代码(ram_ctrl.v)


  1 module  ram_ctrl
  2 (
  3     input   wire            clk_50m     ,   // 写ram时钟,50MHz
  4     input   wire            clk_25m     ,   // 读ram时钟,25MHz
  5     input   wire            rst_n       ,   // 复位信号,低电平有效
  6     input   wire    [15:0]  ram1_rd_data,   // RAM1读数据
  7     input   wire    [15:0]  ram2_rd_data,   // RAM2读数据
  8     input   wire            data_en     ,   // 输入数据使能信号
  9     input   wire    [7:0]   data_in     ,   // 输入数据
 10
 11     output  reg             ram1_wr_en  ,   // RAM1写使能
 12     output  reg             ram1_rd_en  ,   // RAM1读使能
 13     output  reg     [6:0]   ram1_wr_addr,   // RAM1写地址
 14     output  reg     [5:0]   ram1_rd_addr,   // RAM1读地址
 15     output  wire    [7:0]   ram1_wr_data,   // RAM1写数据
 16     output  reg             ram2_wr_en  ,   // RAM2写使能
 17     output  reg             ram2_rd_en  ,   // RAM2读使能
 18     output  reg     [6:0]   ram2_wr_addr,   // RAM2写地址
 19     output  reg     [5:0]   ram2_rd_addr,   // RAM2读地址
 20     output  wire    [7:0]   ram2_wr_data,   // RAM2写数据
 21     output  reg     [15:0]  data_out        // 输出乒乓操作数据
 22
 23 );
 24
 25 // ******************************************************************** //
 26 // ****************** Parameter and Internal Signal ******************* //
 27 // ******************************************************************** //
 28
 29 // parameter  define
 30 parameter   IDLE        =   4'b0001,        // 初始状态
 31             WRAM1       =   4'b0010,        // 写RAM1状态
 32             WRAM2_RRAM1 =   4'b0100,        // 写RAM2、读RAM1状态
 33             WRAM1_RRAM2 =   4'b1000;        // 写RAM1、读RAM2状态
 34
 35 // reg   define
 36 reg     [3:0]   state           ;   // 状态机状态
 37 reg     [7:0]   data_in_reg     ;   // 数据寄存器
 38
 39 // ******************************************************************** //
 40 // ******************************* Main Code ************************** //
 41 // ******************************************************************** //
 42
 43 // 使用组合逻辑赋值,这样使能和数据地址才能对应
 44 assign  ram1_wr_data    =   (ram1_wr_en == 1'b1) ? data_in_reg: 8'd0;
 45 assign  ram2_wr_data    =   (ram2_wr_en == 1'b1) ? data_in_reg: 8'd0;
 46
 47 // 使用写数据时钟下降沿寄存数据,使数据写入存储器时上升沿能采集到稳定的数据
 48 always@(negedge clk_50m or  negedge rst_n)
 49     if(rst_n == 1'b0)
 50         data_in_reg <=  8'd0;
 51     else
 52         data_in_reg <=  data_in;
 53
 54 // 状态机状态跳转
 55 always@(negedge clk_50m or  negedge rst_n)
 56     if(rst_n == 1'b0)
 57             state   <=  IDLE;
 58     else    case(state)
 59         IDLE:// 检测到数据使能信号为高时,跳转到下一状态将数据写到RAM1
 60             if(data_en == 1'b1)
 61                 state   <=  WRAM1;
 62         WRAM1:// RAM1数据写完之后,跳转到写RAM2、读RAM1状态
 63             if(ram1_wr_addr == 7'd99)
 64                 state   <=  WRAM2_RRAM1;
 65         WRAM2_RRAM1:// RAM2数据写完之后,跳转到写RAM1、读RAM2状态
 66             if(ram2_wr_addr == 7'd99)
 67                 state   <=  WRAM1_RRAM2;
 68         WRAM1_RRAM2:// RAM1数据写完之后,跳转到写RAM2、读RAM1状态
 69             if(ram1_wr_addr == 7'd99)
 70                 state   <=  WRAM2_RRAM1;
 71         default:
 72                 state   <=  IDLE;
 73     endcase
 74
 75 // RAM1、RAM2写使能赋值
 76 always@(*)
 77     case(state)
 78         IDLE:
 79             begin
 80                 ram1_wr_en  =  1'b0;
 81                 ram2_wr_en  =  1'b0;
 82             end
 83         WRAM1:
 84             begin
 85                 ram1_wr_en  =  1'b1;
 86                 ram2_wr_en  =  1'b0;
 87             end
 88         WRAM2_RRAM1:
 89             begin
 90                 ram1_wr_en  =  1'b0;
 91                 ram2_wr_en  =  1'b1;
 92             end
 93         WRAM1_RRAM2:
 94             begin
 95                 ram1_wr_en  =  1'b1;
 96                 ram2_wr_en  =  1'b0;
 97             end
 98         default:;
 99     endcase
100
101 // RAM1读使能,使用读时钟赋值
102 always@(negedge clk_25m or  negedge rst_n)
103     if(rst_n == 1'b0)
104         ram1_rd_en  <=  1'b0;
105     else    if(state == WRAM2_RRAM1)
106         ram1_rd_en  <=  1'b1;
107     else
108         ram1_rd_en  <=  1'b0;
109
110 // RAM2读使能,使用读时钟赋值
111 always@(negedge clk_25m or  negedge rst_n)
112     if(rst_n == 1'b0)
113         ram2_rd_en  <=  1'b0;
114     else    if(state == WRAM1_RRAM2)
115         ram2_rd_en  <=  1'b1;
116     else
117         ram2_rd_en  <=  1'b0;
118
119 // RAM1写地址
120 always@(negedge clk_50m or  negedge rst_n)
121     if(rst_n == 1'b0)
122         ram1_wr_addr   <=  7'd0;
123     else    if(ram1_wr_addr    ==  7'd99)
124         ram1_wr_addr   <=  7'd0;
125     else    if(ram1_wr_en == 1'b1)
126         ram1_wr_addr   <=  ram1_wr_addr   +   1'b1;
127
128 // RAM2写地址
129 always@(negedge clk_50m or  negedge rst_n)
130     if(rst_n == 1'b0)
131         ram2_wr_addr   <=  7'b0;
132     else    if(ram2_wr_addr    ==  7'd99)
133         ram2_wr_addr   <=  7'b0;
134     else    if(ram2_wr_en == 1'b1)
135         ram2_wr_addr   <=  ram2_wr_addr   +   1'b1;
136
137 // RAM1读地址
138 always@(negedge clk_25m or  negedge rst_n)
139     if(rst_n == 1'b0)
140         ram1_rd_addr   <=  6'd0;
141     else    if(ram1_rd_addr    ==  6'd49)
142         ram1_rd_addr   <=  6'b0;
143     else    if(ram1_rd_en == 1'b1)
144         ram1_rd_addr   <=  ram1_rd_addr   +   1'b1;
145
146 // RAM2读地址
147 always@(negedge clk_25m or  negedge rst_n)
148     if(rst_n == 1'b0)
149         ram2_rd_addr   <=  6'd0;
150     else    if(ram2_rd_addr    ==  6'd49)
151         ram2_rd_addr   <=  6'b0;
152     else    if(ram2_rd_en == 1'b1)
153         ram2_rd_addr   <=  ram2_rd_addr   +   1'b1;
154
155 // 将乒乓操作读出的数据选择输出
156 always@(negedge clk_25m or  negedge rst_n)
157     if(rst_n == 1'b0)
158         data_out    <=  16'd0;
159     else    if(ram1_rd_en == 1'b1)
160         data_out    <=  ram1_rd_data;
161     else    if(ram2_rd_en == 1'b1)
162         data_out    <=  ram2_rd_data;
163     else
164         data_out    <=  16'd0;
165
166 endmodule

代码是根据绘制的波形图绘制的,对各个信号的介绍可参考波形图部分,此处不再赘述。本设计思路只作为参考,并非唯一方法,读者也可利用所学知识,按照自己的思路进行设计。

6. 顶层模块

(1)模块框图

乒乓操作的顶层模块如图2-9所示。

图2-9 乒乓操作顶层模块

对于该工程,只需要输入时钟复位即可。

(2)代码编写

顶层代码的编写较为容易,无须绘制波形图。参考代码具体参见代码清单2-3。

代码清单2-3 乒乓操作顶层模块参考代码(pingpang.v)


  1 module  pingpang
  2 (
  3     input   wire    sys_clk     ,   // 系统时钟,频率为50MHz
  4     input   wire    sys_rst_n       // 复位信号,低电平有效
  5
  6 );
  7
  8 // ******************************************************************** //
  9 // ******************** Parameter And Internal Signal ***************** //
 10 // ******************************************************************** //
 11
 12 // wire   define
 13 wire            clk_50m      ;       // 50MHz时钟
 14 wire            clk_25m      ;       // 25MHz时钟
 15 wire            rst_n        ;       // 复位信号
 16 wire    [15:0]  ram1_rd_data ;       // RAM1读数据
 17 wire    [15:0]  ram2_rd_data ;       // RAM2读数据
 18 wire            data_en      ;       // 输入数据使能信号
 19 wire    [7:0]   data_in      ;       // 输入数据
 20 wire            ram1_wr_en   ;       // RAM1写使能
 21 wire            ram1_rd_en   ;       // RAM1读使能
 22 wire    [6:0]   ram1_wr_addr ;       // RAM1写地址
 23 wire    [5:0]   ram1_rd_addr ;       // RAM1写地址
 24 wire    [7:0]   ram1_wr_data ;       // RAM1写数据
 25 wire            ram2_wr_en   ;       // RAM2写使能
 26 wire            ram2_rd_en   ;       // RAM2读使能
 27 wire    [6:0]   ram2_wr_addr ;       // RAM2写地址
 28 wire    [5:0]   ram2_rd_addr ;       // RAM2写地址
 29 wire    [7:0]   ram2_wr_data ;       // RAM2写数据
 30 wire    [15:0]  data_out     ;       // 输出乒乓操作数据
 31 wire            locked       ;       // PLL核输出稳定时钟标志信号,高电平有效
 32
 33 // 时钟不稳定时视为复位
 34 assign  rst_n   =   sys_rst_n   &   locked;
 35
 36 // ******************************************************************** //
 37 // *************************** Instantiation ************************** //
 38 // ******************************************************************** //
 39
 40 // ----------- ram_ctrl_inst -----------
 41 ram_ctrl    ram_ctrl_inst
 42 (
 43     .clk_50m     (clk_50m       ),   // 写ram时钟,50MHz
 44     .clk_25m     (clk_25m       ),   // 读ram时钟,25MHz
 45     .rst_n       (rst_n         ),   // 复位信号,低电平有效
 46     .ram1_rd_data(ram1_rd_data  ),   // RAM1读数据
 47     .ram2_rd_data(ram2_rd_data  ),   // RAM2读数据
 48     .data_en     (data_en       ),   // 输入数据使能信号
 49     .data_in     (data_in       ),   // 输入数据
 50
 51     .ram1_wr_en  (ram1_wr_en    ),   // RAM1写使能
 52     .ram1_rd_en  (ram1_rd_en    ),   // RAM1读使能
 53     .ram1_wr_addr(ram1_wr_addr  ),   // RAM1读写地址
 54     .ram1_rd_addr(ram1_rd_addr  ),   // RAM1读地址
 55     .ram1_wr_data(ram1_wr_data  ),   // RAM1写数据
 56     .ram2_wr_en  (ram2_wr_en    ),   // RAM2写使能
 57     .ram2_rd_en  (ram2_rd_en    ),   // RAM2读使能
 58     .ram2_wr_addr(ram2_wr_addr  ),   // RAM2写地址
 59     .ram2_rd_addr(ram2_rd_addr  ),   // RAM2读地址
 60     .ram2_wr_data(ram2_wr_data  ),   // RAM2写数据
 61     .data_out    (data_out      )    // 输出乒乓操作数据
 62
 63 );
 64
 65 // ----------- data_gen_inst -----------
 66 data_gen    data_gen_inst
 67 (
 68     .clk_50m     (clk_50m   ),   // 模块时钟,频率为50MHz
 69     .rst_n       (rst_n     ),   // 复位信号,低电平有效
 70
 71     .data_en     (data_en   ),   // 数据使能信号,高电平有效
 72     .data_in     (data_in   )    // 输出数据
 73
 74 );
 75
 76 // ----------- clk_gen_inst -----------
 77 clk_gen     clk_gen_inst
 78 (
 79     .areset (~sys_rst_n ),       // 异步复位
 80     .inclk0 (sys_clk    ),       // 输入时钟
 81
 82     .c0     (clk_50m    ),       // 输出时钟,频率为50MHz
 83     .c1     (clk_25m    ),       // 输出时钟,频率为25MHz
 84     .locked (locked     )        // 时钟稳定输出标志信号
 85
 86 );
 87
 88 // ------------ dq_ram1-------------
 89 dp_ram  dp_ram1
 90 (
 91     .data       (ram1_wr_data   ),
 92     .rdaddress  (ram1_rd_addr   ),
 93     .rdclock    (clk_25m        ),
 94     .rden       (ram1_rd_en     ),
 95     .wraddress  (ram1_wr_addr   ),
 96     .wrclock    (clk_50m        ),
 97     .wren       (ram1_wr_en     ),
 98
 99     .q          (ram1_rd_data   )
100
101 );
102
103 // ------------ dq_ram2-------------
104 dp_ram  dp_ram2
105 (
106     .data       (ram2_wr_data   ),
107     .rdaddress  (ram2_rd_addr   ),
108     .rdclock    (clk_25m        ),
109     .rden       (ram2_rd_en     ),
110     .wraddress  (ram2_wr_addr   ),
111     .wrclock    (clk_50m        ),
112     .wren       (ram2_wr_en     ),
113
114     .q          (ram2_rd_data   )
115
116 );
117 endmodule

此处的乒乓操作使用的是两个双端口RAM,这里调用双端口RAM实例化两次,实例化名不一样即可。

7. RTL视图

编译完成后,查看一下RTL视图,仔细看RTL视图展示的信息与顶层模块框图是否一致,具体如图2-10所示。

图2-10 实验工程RTL视图

8. 仿真验证

(1)仿真代码编写

编写仿真代码,对参考代码进行仿真验证。仿真参考代码具体参见代码清单2-4。

代码清单2-4 乒乓操作仿真参考代码(tb_pingpang.v)


 1 module  tb_pingpang();
 2
 3 // reg    define
 4 reg         sys_clk     ;
 5 reg         sys_rst_n   ;
 6
 7 // ******************************************************************** //
 8 // ***************************** Main Code **************************** //
 9 // ******************************************************************** //
10
11 initial
12     begin
13         sys_clk     =   1'b1;
14         sys_rst_n   <=  1'b0;
15       #200
16         sys_rst_n   <=  1'b1;
17     end
18
19 // sys_clk:模拟系统时钟,每10ns电平取反一次,周期为20ns,频率为50MHz
20 always #10 sys_clk =   ~sys_clk;
21
22 // ******************************************************************** //
23 // *************************** Instantiation ************************** //
24 // ******************************************************************** //
25
26 // -------------pingpang_inst-------------
27 pingpang    pingpang_inst
28 (
29     .sys_clk     (sys_clk   ),    // 系统时钟
30     .sys_rst_n   (sys_rst_n )     // 复位信号,低电平有效
31
32 );
33
34 endmodule

该工程的仿真代码只需要产生时钟信号和复位信号即可。

(2)仿真波形分析

首先,看一看数据生成模块是否能正确生成我们的实验中所设计的数据。

如图2-11和图2-12所示抓取的是数据生成模块的一次循环数据的开头和结尾波形图。8’hc7即为十进制的199,可以看到该模块的波形图与我们所绘制的数据生成模块的波形图是一致的,能达到我们的数据生成要求。

图2-11 循环数据开头波形图

图2-12 循环数据结尾波形图

如图2-13和图2-14所示,抓取的是状态机为WRAM1(写RAM1)状态时的开头部分以及结尾部分的波形图。方框中为写RAM1相关信号的波形图,可以看到这与我们所绘制的波形图是一致的,RAM1的使能、地址、数据信号也能达到我们的设计要求。

图2-13 WRAM1状态仿真波形图开头部分

图2-14 WRAM1状态仿真波形图结尾部分

如图2-15和图2-16所示,抓取的是状态机为WRAM2_RRAM1状态的开头及结尾部分的仿真波形图。其中框为写RAM2的相关信号的时序,框为读RAM1的相关信号的时序。可以看到这些信号的时序关系与我们所绘制的波形图是一致的。

图2-15 WRAM2_RRAM1状态仿真波形图开头部分

图2-16 WRAM2_RRAM1状态仿真波形图结尾部分

如图2-17和图2-18所示,抓取的是状态机为WRAM1_RRAM2状态的开头及结尾部分的仿真波形图。其中框为写RAM1的相关信号的时序,框为读RAM2的相关信号的时序。可以看到这些信号的时序关系与我们所绘制的波形图是一致的。

图2-17 WRAM1_RRAM2状态仿真波形图开头部分

图2-18 WRAM1_RRAM2状态仿真波形图结尾部分

各状态的ram相关信号都与我们设计的一致,下面看一看乒乓操作输出的数据(data_out)是否正确。

如图2-19和图2-20所示,抓取的是读一次RAM1与RAM2输出的数据的开头及结尾部分波形图,可以看到输出数据的位宽为16bit。其中16’hc7c6即为十进制198、199拼接后的数据,这说明输出的数据即为我们输入的数据。

图2-19 乒乓操作输出数据仿真波形图开头部分

图2-20 乒乓操作输出数据仿真波形图结尾部分

9. SignalTap波形抓取

由于该工程上板没有实际的效果显示,我们就使用Quartus软件的SignalTap工具实时抓取一下输出数据,查看其是否与我们仿真输出的数据一致。

由图2-21和图2-22可以看到输出的数据与我们仿真时输出的数据是一致的。这说明我们设计的工程是正确的,能达到实验要求。

图2-21 SignalTap仿真波形(一)

图2-22 SignalTap仿真波形(二)

[1] 低高速数据传输,指低速模块处理高速数据或高速模块处理低速数据。