一级目录
二级目录
三级目录
1、V-R握手FIFO简介
V-R握手FIFO机制,即是两级时钟速率不同的模块之间传递信号的机制,上游往下游传递的数据暂时缓存在FIFO中,上游和FIFO、FIFO和下游之间通过握手传递信号。即在一个FIFO模块外层套了一层握手机制。如下图:
如何用Verilog代码实现呢?我们可以这么来做,
1、先实现一个同步FIFO,
2、再实现一个单信号握手,
3、把握手机制套在FIFO外面。
2、先实现一个同步FIFO
2.1 FIFO简介
FIFO:Frist-in-first-out,先进先出,是一种数据缓存器,实现速率匹配。FIFO包括同步 FIFO 和异步 FIFO 两种,同步FIFO有一个时钟信号,读和写逻辑全部使用这一个时钟信号,异步FIFO有两个时钟信号,读和写逻辑用的各自的读写时钟。
FIFO 与普通存储器RAM 的区别是没有外部读写地址线,使用起来非常简单,但缺点就是只能顺序写入数据,顺序读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 FIFO本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。
2.2 同步FIFO指标
FIFO的宽度:即 FIFO一次读写操作的数据位。
FIFO的深度:指的是 FIFO可以存储多少个 N位的数据(如果宽度为 N)。
满标志:FIFO 已满或将要满时由 FIFO 的状态送出的一个信号,以阻止 FIFO的写操作继续向 FIFO 中写数据而造成溢出(overflow)。
空标志:FIFO 已空或将要空时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO中读出数据而造成无效数据的读出(underflow)。
读/写时钟:读/些操作所遵循的时钟,在每个时钟沿来临时读/写数据。
2.3 同步FIFO设计
FIFO 读写指针(读写地址)的工作原理:
写指针:总是指向下一个被写入的单元,复位时,指向第1个单元(编号为0);
读指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0);
FIFO 的“空”/“满”检测
FIFO 设计的关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”标志。
当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出 FIFO 中最后一个字后,追赶上了写指针时,如下图1所示:
当读写指针再次相等时,表明 FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针,如下图2:
读写指针可以在读写使能有效时,每时钟周期+1,而如何产生可靠的“空”/“满”信号则成了同步FIFO设计的重点。下面有两种解决方法:计数器法和高位扩展法。 本次采用计数器法。
2.4 计数器法实现同步FIFO
构建一个计数器fifo_cnt,该计数器用于指示当前 FIFO 中数据的个数:
复位时,该计数器为0,FIFO中的数据个数为0;
当读写使能信号均有效时,说明又读又写,计数器不变,FIFO中的数据个数无变化;
当写使能有效且 full =0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1;
当读使能有效且 empty=0,则 fifo_cnt -1;;表示读操作且 FIFO 未空时候,FIFO 中的数据个数减少了 1 ;
fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;
fifo_cnt = fifo的深度 的时候,表示 FIFO 现在已经满,需要设置 full=1。
2.5 同步FIFO代码
//计数器法实现同步FIFO
module sync_fifo_cnt
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
input clk , //系统时钟
input rst_n , //低电平有效的复位信号
input [DATA_WIDTH-1:0] data_in , //写入的数据
input rd_en , //读使能信号,高电平有效
input wr_en , //写使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out, //输出的数据
output empty , //空标志,高电平表示当前FIFO已被写满
output full , //满标志,高电平表示当前FIFO已被读空
output reg [$clog2(DATA_DEPTH) : 0] fifo_cnt //$clog2是以2为底取对数
);
//reg define
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0]; //用二维数组实现RAM
reg [$clog2(DATA_DEPTH) - 1 : 0] wr_addr; //写地址
reg [$clog2(DATA_DEPTH) - 1 : 0] rd_addr; //读地址
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
rd_addr <= 0;
else if (!empty && rd_en)begin //读使能有效且非空
rd_addr <= rd_addr + 1'd1;
data_out <= fifo_buffer[rd_addr];
end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
wr_addr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_addr <= wr_addr + 1'd1;
fifo_buffer[wr_addr]<=data_in;
end
end
//更新计数器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
fifo_cnt <= 0;
else begin
case({wr_en,rd_en}) //拼接读写使能信号进行判断
2'b00:fifo_cnt <= fifo_cnt; //不读不写
2'b01: //仅仅读
if(fifo_cnt != 0) //fifo没有被读空
fifo_cnt <= fifo_cnt - 1'b1; //fifo个数-1
2'b10: //仅仅写
if(fifo_cnt != DATA_DEPTH) //fifo没有被写满
fifo_cnt <= fifo_cnt + 1'b1; //fifo个数+1
2'b11:fifo_cnt <= fifo_cnt; //读写同时
default:;
endcase
end
end
//依据计数器状态更新指示信号
//依据不同阈值还可以设计半空、半满 、几乎空、几乎满
assign full = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0; //满信号
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0; //空信号
endmodule
`timescale 1ns/1ns //时间单位/精度
//------------<模块及端口声明>----------------------------------------
module tb_sync_fifo_cnt();
parameter DATA_WIDTH = 8 ; //FIFO位宽
parameter DATA_DEPTH = 8 ; //FIFO深度
reg clk ;
reg rst_n ;
reg [DATA_WIDTH-1:0] data_in ;
reg rd_en ;
reg wr_en ;
wire [DATA_WIDTH-1:0] data_out;
wire empty ;
wire full ;
wire [$clog2(DATA_DEPTH) : 0] fifo_cnt;
//------------<例化被测试模块>----------------------------------------
sync_fifo_cnt
#(
.DATA_WIDTH (DATA_WIDTH), //FIFO位宽
.DATA_DEPTH (DATA_DEPTH) //FIFO深度
)
sync_fifo_cnt_inst(
.clk (clk ),
.rst_n (rst_n ),
.data_in (data_in ),
.rd_en (rd_en ),
.wr_en (wr_en ),
.data_out (data_out ),
.empty (empty ),
.full (full ),
.fifo_cnt (fifo_cnt )
);
//------------<设置初始测试条件>----------------------------------------
initial begin
clk = 1'b0; //初始时钟为0
rst_n <= 1'b0; //初始复位
data_in <= 'd0;
wr_en <= 1'b0;
rd_en <= 1'b0;
//重复8次写操作,让FIFO写满
repeat(8) begin
@(negedge clk)begin
rst_n <= 1'b1;
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
//重复8次读操作,让FIFO读空
repeat(8) begin
@(negedge clk)begin
wr_en <= 1'b0;
rd_en <= 1'd1;
end
end
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b0;
end
end
//持续同时对FIFO读写,写入数据为随机数据
forever begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b1;
end
end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk; //系统时钟周期20ns
endmodule
3、再实现一个同步握手
3.1 Valid-Ready简介
设计中经常会用到几个模块之间的数据准确无误的传输,那么我们就需要加上握手信号来控制信号的传输。通常它有两组接口:一个接上游、一个传下游。
3.2 Valid-Ready原理
datain_val用来指示上游数据输入data_in的有效性;
datain_rdy用来指示本模块是否准备好接收上游数据;
dataout_val用来指示往下游数据输出data_out的有效性;
dataout_rdy表示下游是否准备好接收本模块的输出数据;
clk是时钟信号;rst_n是异步复位信号。
其中,datain_ready,由dataout_rdy和data_val组合得到。
3.3 Valid-Ready三种情况
上下游各自的valid和ready信号都有三种情况,即Ready-Before-Valid,Valid-Before-Ready,Ready-With-Valid。这三种各自对应如下:
3.3.1 Ready-Before-Valid(输入)
Ready-Before-Valid是Ready信号在Valid信号之前有效。如下时序图:
先准备好,再来数据,输入端口适合采取这种情况。这种设计使得在上游数据来临之前,通道已经准备好接收数据了。
3.3.2 Valid-Before-Ready(输出)
Valid-Before-Ready是Valid信号在Ready信号之前有效。如下时序图:
先有数据,再看是否准备好往下传,输出端口采取这种情况。提前把数据给准备等待下游来取。这样可以避免数据已经好了但没人来取的情况。
3.3.3 Valid-With-Ready
Valid-With-Ready是Valid信号和Ready信号同时有效,同时到达就很简单,数据来了就被取走。
3.3.4 Stalemate(僵局)
输出端用Ready-Before-Valid, 输出端等待接受端给出的Ready来输出数据
接受端用Valid-before-Ready, 接收端也在等待输出端给出Valid信号来接受数据
两者都在等待却没有一方先给, 通道无效,被“锁住”了。
握手需要特别注意三件事:
- valid与ready不可过度依赖,他俩彼此可以互相独立发出请求拉高;
- 输如输出各自的valid与数据同步;
- 数据与valid一起拉高后,若无新的数据,valid要及时置0。
3.4 代码
//annotation one
//datain_rdy
//何时准备好接收上游的输入数据?即 datain_rdy == 1?
//当输出下游的数据无效,即没有数据往下游传送,此时“管道为空”,等待数据来。即dataout_val=0;
//当输出下游的数据准备好时,即接下来一边往下游送数据,一边等待上游有效数据来。即dataout_rdy=1。
//annotation two
//rst_n
//复位信号来时,输出数据和输出数据有效置0,输入数据准备不置0。
//annotation three
//dataout_val
//输出数据有效,当输入数据准备好后(即datain_rdy=1),输入数据有效即为输出数据有效。
//annotation three
//dataout
//输出数据,当输入数据准备好且有效后(即datain_rdy 、datain_val=1),输入数据即为输出数据。
module Valid_Ready #(parameter wd = 4)(
input clk,
input rst_n,
input [wd-1:0] datain,
input datain_val,
input dataout_rdy,
output datain_rdy,
output reg dataout_val,
output reg [wd-1:0] dataout
);
//annotation one
assign datain_rdy = dataout_rdy || !dataout_val;
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
dataout <= 0; //annotation two
dataout_val <= 0;
end
else
begin
if (datain_rdy)
dataout_val <= datain_val; // annotation three
else
dataout_val <=dataout_val;
if (datain_rdy && datain_val)
dataout <= datain; // annotation three
else
dataout <= dataout;
end
end
endmodule
testbench如下:
`timescale 1ns/1ns
module Valid_Ready_tb #(parameter wd = 4);
reg clk;
reg rst_n;
reg [wd-1:0] datain;
reg datain_val;
reg dataout_rdy;
wire datain_rdy;
wire dataout_val;
wire [wd-1:0] dataout;
Valid_Ready r2 (.clk(clk),
.rst_n(rst_n),
.datain(datain),
.datain_val(datain_val),
.dataout_rdy(dataout_rdy),
.datain_rdy(datain_rdy),
.dataout_val(dataout_val),
.dataout(dataout));
always
begin
#2 clk = ~clk;
end
// assign datain_rdy = dataout_rdy || !dataout_val;
initial begin
clk = 0;
rst_n = 1;
datain = 0;
datain_val = 0; // 输入数据无效
dataout_rdy = 0; // 准备输出置0。
#5 rst_n = 0; datain = 5;
#5 rst_n = 1; //此时复位,“管道为空”,dataout_val=0,则datain_rdy为1,准备接收上游数据
#5 datain_val = 1; //输入数据有效,此时datain_rdy && datain_val都为1,数据存入dataout了
//握手已经完成
#5 datain_val = 0; //由于datain寄存于dataout,发送端认为捕获端已获取数据,datain_val端要置0
#5 dataout_rdy = 1; //slave端可以接受数据,将pipe中数据取走,此时pipe可以寄存下一个数据dataout_val=0,但为了避免bubble,dataout保持不变
#5 dataout_rdy = 0; //slave端接收数据后,停止接收数据
#5 datain = 15; datain_val = 1; //master端发送数据,因为此时pipe可以寄存数据,由pipe寄存,dataout_val变为1
#5 datain_val = 0; //后续分析过程与上类似
#5 dataout_rdy = 1;
#5 dataout_rdy = 0;
#10 $stop;
end
endmodule
4、最后整合为同步Vaild-Ready握手FIFO
4.1 思路原理
如何把valid-ready和握手联系起来?
模块外部是握手,内部是FIFO。
故模块整体的输入输出端口应和valid-read保持一致,在内部声明一个FIFO。如下:
4.2 具体操作
如何把模块外部变量和FIFO定义的变量联系起来?
1、读写使能:
当前reg准备好接收且输入数据有效,即可 使能 写信号;
当后reg准备好接收且当前reg输出数据有效,即可 使能 读信号;
2、FIFO自己写数据与写地址、读数据与读数据、计数器自增自减、空满标志判断不变;
3、把空满标志传出去:
没有空(有数据)则可以继续给下面传; assign out_val = (!empty);
没有满(有地方可以接着写)则可以继续接受。assign in_rdy = (!full);
这样,就把一个Valid-Ready的外衣,套在了FIFO上。具体代码如下:
4.3 代码
模块代码:
module VR_FIFO (//外部Valid_Ready输入输出端口
input clk,
input rst_n,
input [7:0] data_in,
input in_val,
input out_rdy,
output in_rdy,
output out_val,
output reg [7:0] data_out
);
//内部FIFO的输入输出端口,以下三组变量是FIFP的输入输出端口,故而要在内部定义。
reg wr_en ; //FIFO 写使能
reg rd_en ; //FIFO 读使能
wire empty ; //FIFO 空信号
wire full ; //FIFO 满信号
reg [4:0] fifo_cnt ; //FIFO 计数器
//FIFO内部自带的变量定义
reg [7:0] fifo_buffer[15 : 0]; //用二维数组实现RAM
reg [3:0] wr_addr; //写地址
reg [3:0] rd_addr; //读地址
//写使能,当前reg准备好接收且输入数据有效,即可 使能 写信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wr_en <= 1'b0;
else if(in_val && in_rdy)
wr_en <= 1'b1;
else
wr_en <= 1'b0;
end
//读使能,当后reg准备好接收且当前reg输出数据有效,即可 使能 读信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rd_en <= 1'b0;
else if(out_val && out_rdy)
rd_en <= 1'b1;
else
rd_en <= 1'b0;
end
//向FIFO写数据操作,并且更新写地址
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
wr_addr <= 0;
else if (!full && wr_en)
begin //写使能有效且没有写满
wr_addr <= wr_addr + 1'd1;
fifo_buffer[wr_addr] <= data_in;
end
end
//从FIFO读数据操作,并且更新读地址
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
rd_addr <= 0;
else if (!empty && rd_en)begin //读使能有效且没有读空
rd_addr <= rd_addr + 1'd1;
data_out <= fifo_buffer[rd_addr];
end
end
//更新计数器,来判断空满信号
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_cnt <= 0;
else begin
case({wr_en,rd_en})
2'b00: fifo_cnt <= fifo_cnt; //不读不写
2'b01:
begin //仅仅读
if(fifo_cnt != 0) //fifo没有被读空
fifo_cnt <= fifo_cnt - 1'b1; //fifo个数-1
end
2'b10: //仅仅写
begin
if(fifo_cnt != 5'd16) //fifo没有被写满
fifo_cnt <= fifo_cnt + 1'b1; //fifo个数+1
end
2'b11: fifo_cnt <= fifo_cnt; //读写同时
default:;
endcase
end
end
//依据计数器状态更新指示信号
//依据不同阈值还可以设计半空、半满 、几乎空、几乎满
assign full = (fifo_cnt == 5'd16) ? 1'b1 : 1'b0; //满信号
assign empty = (fifo_cnt == 0) ? 1'b1 : 1'b0; //空信号
assign in_rdy = (!full);
assign out_val = (!empty);
endmodule
testbench:文章来源:https://www.toymoban.com/news/detail-567121.html
`timescale 1ns/1ns
module tb_VR_FIFO;
reg clk;
reg rst_n;
reg [7:0] data_in;
reg in_val;
reg out_rdy;
wire in_rdy;
wire out_val;
wire [7:0] data_out;
VR_FIFO VR_FIFO_u0 (
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.in_val(in_val),
.out_rdy(out_rdy),
.in_rdy(in_rdy),
.out_val(out_val),
.data_out(data_out));
initial begin
clk = 1'b0;
rst_n = 1'b0;
data_in = 8'd0;
in_val = 1'd0;
out_rdy = 1'b0;
#10 rst_n = 1'b1;
#20 repeat(50)
begin
/*
//正常写一次读一次
#20 w_data;
#20 r_data;
//只写不读,会满
#20 w_data;
//写多读少
#20 w_data;
#20 w_data
#20 r_data;
//写少读多
#20 w_data;
#20 r_data;
#20 r_data;
*/
end
#20000 $stop;
end
task w_data;
begin
data_in = $random;
in_val = 1'b1;
#20 in_val = 1'b0;
end
endtask
task r_data;
begin
out_rdy = 1'b1;
#20 out_rdy = 1'b0;
end
endtask
always #10 clk =~ clk;
endmodule
至此就完了。
第一次写文章,有的地方写的很粗糙,有问题提出,欢迎大家讨论。
最后贴上邮箱:boyue8817505@163.com文章来源地址https://www.toymoban.com/news/detail-567121.html
到了这里,关于手把手Verilog HDL同步Vaild-Ready握手FIFO机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!