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] 低高速数据传输,指低速模块处理高速数据或高速模块处理低速数据。