FPGA_乒乓操作

乒乓操作是一个经常用于数据流控制的处理技术,具有节约缓冲空间、对数据流无缝处理等特点。乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

乒乓操作过程:

外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口RAM,FIFO,SDRAM等。

第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓冲模块2”的同时“输出数据流选择单元”将“数据缓冲模块1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块1”的同时将“数据缓冲模块2”的数据读出。如此反复循环地操作,即为乒乓操作。

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

整体框图:

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

 时钟生成模块

PLL核来生成RAM的读写时钟,生成一个50MHz时钟(写时钟),一个25MHz时钟(读时钟)。使用50MHz时钟输入数据,25MHz时钟输出数据。

数据缓冲模块

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

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

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

 数据生成模块

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

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

module data_gen
(
input clk_50m , //模块时钟,频率50MHz
input rst_n , //复位信号,低电平有效

output reg data_en , //数据使能信号,高电平有效
output reg [7:0] data_in //输出数据

);

 
 //\* Main Code \//
 

 //data_en:让其一直为高电平,一直输出数据
 always@(posedge clk_50m or negedge rst_n)
 if(rst_n == 1'b0)
 data_en <= 1'b0;
 else
 data_en <= 1'b1;

 //data_in:循环生成写入的数据(8'd0 ~ 8'd199)
 always@(posedge clk_50m or negedge rst_n)
 if(rst_n == 1'b0)
 data_in <= 8'd0;
 else if(data_in == 8'd199)
 data_in <= 8'd0;
 else if(data_en == 1'b1)
 data_in <= data_in + 1'b1;
 else
 data_in <= data_in;

 endmodule
输入输出数据选择模块

该模块是乒乓操作的核心模块,我们需要通过该模块对输入输出数据进行选择,从而达到乒乓操作的处理效果。状态机:

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状态开始 下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。当检测到数据使能信号(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读相关信号

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

如上图所示:状态机是在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,转换为16进制就是62、63,所以读取的最后一个数据为16’h6362。同理ram2 读取的第一个数据为16’h6564,最后一个数据为16’hc7c6。

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

module ram_ctrl
(
input wire clk_50m , //写ram时钟,50MHz
input wire clk_25m , //读ram时钟,25MHz
input wire rst_n , //复位信号,低有效
input wire [15:0] ram1_rd_data, //ram1读数据
input wire [15:0] ram2_rd_data, //ram2读数据
input wire data_en , //输入数据使能信号
input wire [7:0] data_in , //输入数据

output reg ram1_wr_en , //ram1写使能
output reg ram1_rd_en , //ram1读使能
output reg [6:0] ram1_wr_addr, //ram1写地址
output reg [5:0] ram1_rd_addr, //ram1读地址
output wire [7:0] ram1_wr_data, //ram1写数据
output reg ram2_wr_en , //ram2写使能
output reg ram2_rd_en , //ram2读使能
output reg [6:0] ram2_wr_addr, //ram2写地址
output reg [5:0] ram2_rd_addr, //ram2读地址
output wire [7:0] ram2_wr_data, //ram2写数据
output reg [15:0] data_out //输出乒乓操作数据

);


//\* Parameter and Internal Signal \//


//parameter define
parameter IDLE = 4'b0001, //初始状态
WRAM1 = 4'b0010, //写RAM1状态
WRAM2_RRAM1 = 4'b0100, //写RAM2读RAM1状态
WRAM1_RRAM2 = 4'b1000; //写RAM1读RAM2状态

//reg define
reg [3:0] state ; //状态机状态
reg [7:0] data_in_reg ; //数据寄存器


//\* Main Code \//


//使用组合逻辑赋值,这样使能和数据地址才能对应
assign ram1_wr_data = (ram1_wr_en == 1'b1) ? data_in_reg: 8'd0;
assign ram2_wr_data = (ram2_wr_en == 1'b1) ? data_in_reg: 8'd0;

//使用写数据时钟下降沿寄存数据,使数据写入存储器时上升沿能踩到稳定的数据
always@(negedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
data_in_reg <= 8'd0;
else
data_in_reg <= data_in;

//状态机状态跳转
always@(negedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE://检测到数据使能信号为高时,跳转到下一状态将数据写到RAM1
if(data_en == 1'b1)
state <= WRAM1;
WRAM1://RAM1数据写完之后,跳转到写RAM2读RAM1状态
if(ram1_wr_addr == 7'd99)
state <= WRAM2_RRAM1;
WRAM2_RRAM1://RAM2数据写完之后,跳转到写RAM1读RAM2状态
if(ram2_wr_addr == 7'd99)
state <= WRAM1_RRAM2;
WRAM1_RRAM2://RAM1数据写完之后,跳转到写RAM2读RAM1状态
if(ram1_wr_addr == 7'd99)
state <= WRAM2_RRAM1;
default:
state <= IDLE;
endcase

//RAM1,RAM2写使能赋值
always@(*)
case(state)
IDLE:
begin
ram1_wr_en = 1'b0;
ram2_wr_en = 1'b0;
end
WRAM1:
begin
ram1_wr_en = 1'b1;
ram2_wr_en = 1'b0;
end
WRAM2_RRAM1:
begin
ram1_wr_en = 1'b0;
ram2_wr_en = 1'b1;
end
WRAM1_RRAM2:
begin
ram1_wr_en = 1'b1;
ram2_wr_en = 1'b0;
end
default:;
endcase

 //RAM1读使能,使用读时钟赋值
 always@(negedge clk_25m or negedge rst_n)
 if(rst_n == 1'b0)
 ram1_rd_en <= 1'b0;
 else if(state == WRAM2_RRAM1)
 ram1_rd_en <= 1'b1;
 else
 ram1_rd_en <= 1'b0;

 //RAM2读使能,使用读时钟赋值
 always@(negedge clk_25m or negedge rst_n)
 if(rst_n == 1'b0)
 ram2_rd_en <= 1'b0;
 else if(state == WRAM1_RRAM2)
 ram2_rd_en <= 1'b1;
 else
 ram2_rd_en <= 1'b0;

 //RAM1写地址
 always@(negedge clk_50m or negedge rst_n)
 if(rst_n == 1'b0)
 ram1_wr_addr <= 7'd0;
 else if(ram1_wr_addr == 7'd99)
 ram1_wr_addr <= 7'd0;
 else if(ram1_wr_en == 1'b1)
 ram1_wr_addr <= ram1_wr_addr + 1'b1;

 //RAM2写地址
 always@(negedge clk_50m or negedge rst_n)
 if(rst_n == 1'b0)
 ram2_wr_addr <= 7'b0;
 else if(ram2_wr_addr == 7'd99)
 ram2_wr_addr <= 7'b0;
 else if(ram2_wr_en == 1'b1)
 ram2_wr_addr <= ram2_wr_addr + 1'b1;

 //RAM1读地址
 always@(negedge clk_25m or negedge rst_n)
 if(rst_n == 1'b0)
 ram1_rd_addr <= 6'd0;
 else if(ram1_rd_addr == 6'd49)
 ram1_rd_addr <= 6'b0;
 else if(ram1_rd_en == 1'b1)
 ram1_rd_addr <= ram1_rd_addr + 1'b1;

 //RAM2读地址
 always@(negedge clk_25m or negedge rst_n)
 if(rst_n == 1'b0)
 ram2_rd_addr <= 6'd0;
 else if(ram2_rd_addr == 6'd49)
 ram2_rd_addr <= 6'b0;
 else if(ram2_rd_en == 1'b1)
 ram2_rd_addr <= ram2_rd_addr + 1'b1;

 //将乒乓操作读出的数据选择输出
 always@(negedge clk_25m or negedge rst_n)
 if(rst_n == 1'b0)
 data_out <= 16'd0;
 else if(ram1_rd_en == 1'b1)
 data_out <= ram1_rd_data;
 else if(ram2_rd_en == 1'b1)
 data_out <= ram2_rd_data;
 else
 data_out <= 16'd0;

 endmodule

乒乓操作使用的是两个真双口RAM,这里我们调用双口RAM例化两次,例化名不一样即可

RTL视图

仿真结果

不知道这两个时钟为何有一段时间变红了,

数据产生

往RAM1写数据0-99

往RAM2写数据100-199

同时读RAM1数据(16位)

读RAM2数据同时往RAM1写数据

最后一个读地址8‘h31,数据是16’hc5c4

data_out输出

以上就是所有仿真时序,本人小白自学,记录一下学习笔记。

参考资料:野火《FPGA Verilog开发实战指南——基于Altera EP4CE10》

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值