Verilog FIFO 设计

FIFO 简介

FIFO 是 First In First Out 的缩写,先进先出,顺序写入数据,顺序读出数据,没有指定的读写地址线,读地址和写地址都从0开始,每读或写一次,指针加一,指向下一个存储单元,读写相互独立。

FIFO 可用于同步或异步时钟域内的数据传输,可以从快时钟域到慢时钟域,也可以从慢时钟域到快时钟域。

FIFO 工作原理

  • 写指针

    用写时钟,写地址从0开始,每写一次地址指针加一,指向下一个存储单元。

    若 FIFO 为满状态,则不可再写。

  • 读指针

    用读时钟,读地址从0开始,每读一次地址指针加一,指向下一个存储单元。

    若 FIFO 为空状态,则不可再读。

  • 读空状态

    复位时为空状态,写入数据后空状态清除。

    当读地址追赶上写地址,两者相等时,FIFO 为读空状态,此时不可再读。

  • 写满状态

    复位时写满状态无效。

    当写地址超过读地址一个 FIFO 深度,两者再次相等时,FIFO 为写满状态,此时不可再写入数据。

  • 宽度

    一次读写操作的数据位数。

  • 深度

    一个 FIFO 可以存储数据的个数。

FIFO 空状态和满状态

FIFO 为空或者为满时,写地址和读地址都相等,为了区分空状态和满状态,需要再加一个额外的地址位(MSB 位),表示写指针是否越过最后一个 FIFO 地址,若越过,MSB 为 1。

若读写地址与扩展位都相同时,表明读写数据的数量是一致的,FIFO 此时为空状态;若读写地址相同,扩展位相反时,表明写数据的数量越过了一个 FIFO 深度,FIFO 此时为满状态。

fifo_full_empty

FIFO 指针编码

在不同时钟域下,用二进制计数可能会出现亚稳态,因为二进制计数时所有位可能同时发生变化,所以我们采用一次只会改变一位的格雷码。

DECBINGrayDECBINGray
000000000810001100
100010001910011101
2001000111010101111
3001100101110111110
4010001101211001010
5010101111311011011
6011001011411101001
7011101001511111000

需要一个二进制转换Gray码的转换电路。

// convert bin code to gray code
assign	wrAddrGray	= (wrAddrPtr >> 1) ^ wrAddrPtr;
assign	rdAddrGray	= (rdAddrPtr >> 1) ^ rdAddrPtr;

然后问题就转变成了如何通过格雷码判断 FIFO 的空和满状态

  • 空状态

    空状态依旧是读写指针的格雷码相同

    assign	emptyFlag	= (rdAddrGray == wrAddrGray);
    
  • 满状态

    gray_fifo_full

    满状态要满足三个条件:

    1. 写指针 wPtr 和读指针 rPtr 的 MSB 位相反。因为 wPtr 越过了一个 FIFO 深度。
    2. 写指针 wPtr 和读指针 rPtr 的次高位相反。因为 gray 码具有镜像对称的特点。
    3. 写指针 wPtr 和读指针 rPtr 的其余位相同。
    assign	fullFlag	= (wrAddrGray == {~rdAddrGray[ADDRWIDTH:ADDRWIDTH-1], rdAddrGray[ADDRWIDTH-2:0]});
    

FIFO 实现代码

`define __FIFO_V__

module Fifo #(
	parameter DATAWIDTH = 4,
	parameter DATADEPTH = 8,
	parameter ADDRWIDTH = 3
)(
	input  wire					wrClk,
	input  wire					rdClk,
	input  wire					rst_n,
	input  wire					wrEn,
	input  wire					rdEn,

	input  wire	[DATAWIDTH-1:0]	din,
	output reg	[DATAWIDTH-1:0]	dout,

	output reg					wrValid,
	output reg					rdValid,
	output wire					fullFlag,
	output wire					emptyFlag
);

	reg		[ADDRWIDTH:0]			wrAddrPtr;									// width = ADDRWIDTH + 1: MSB + addr
	reg		[ADDRWIDTH:0]			rdAddrPtr;
	wire	[ADDRWIDTH-1:0]			wrAddr;
	wire	[ADDRWIDTH-1:0]			rdAddr;

	wire	[ADDRWIDTH:0]			wrAddrGray;									// gray code
	wire	[ADDRWIDTH:0]			rdAddrGray;

	reg		[DATAWIDTH-1:0]			fifoRam[DATADEPTH-1:0];

	assign	wrAddr		= wrAddrPtr[ADDRWIDTH-1:0];
	assign	rdAddr		= rdAddrPtr[ADDRWIDTH-1:0];

	assign	wrAddrGray	= (wrAddrPtr >> 1) ^ wrAddrPtr;			// convert bin code to gray code
	assign	rdAddrGray	= (rdAddrPtr >> 1) ^ rdAddrPtr;

	// this expression is valid under sync mode
	// if under async mode, need to add two step register to sync first
	// assign	emptyFlag	= (rdAddrGray == wrAddrGray);
	// assign	fullFlag	= (wrAddrGray == {~rdAddrGray[ADDRWIDTH:ADDRWIDTH-1], rdAddrGray[ADDRWIDTH-2:0]});

	genvar i;
	generate
		for(i = 0; i < DATADEPTH; i = i + 1)
		begin: fifoInit
			always @(posedge wrClk or negedge rst_n) begin
				if(!rst_n) begin
					fifoRam[i]		<= {DATAWIDTH{1'b0}};
					wrValid			<= 1'b0;
				end
				else if(wrEn & !fullFlag) begin
					fifoRam[wrAddr]	<= din;
					wrValid			<= 1'b1;
				end
				else begin
					wrValid			<= 1'b0;
				end
			end
		end
	endgenerate

	always @(posedge rdClk or negedge rst_n) begin
		if(!rst_n) begin
			dout	<= {DATAWIDTH{1'b0}};
			rdValid	<= 1'b0;
		end
		else if(rdEn & !emptyFlag) begin
			dout	<= fifoRam[rdAddr];
			rdValid	<= 1'b1;
		end
		else begin
			rdValid	<= 1'b0;
		end
	end

	always @(posedge wrClk or negedge rst_n) begin
		if(!rst_n) begin
			wrAddrPtr	<= {(ADDRWIDTH + 1){1'b0}};
		end
		else if(wrEn & !fullFlag) begin
			wrAddrPtr	<= (wrAddrPtr == {(ADDRWIDTH + 1){1'b1}}) ? {(ADDRWIDTH + 1){1'b0}} : wrAddrPtr + 1;
		end
	end

	always @(posedge rdClk or negedge rst_n) begin
		if(!rst_n) begin
			rdAddrPtr	<= {(ADDRWIDTH + 1){1'b0}};
		end
		else if(rdEn & !emptyFlag) begin
			rdAddrPtr	<= (rdAddrPtr == {(ADDRWIDTH + 1){1'b1}}) ? {(ADDRWIDTH + 1){1'b0}} : rdAddrPtr + 1;
		end
	end

	// if async mode
	reg		[ADDRWIDTH:0]			wrAddrGrayD1;
	reg		[ADDRWIDTH:0]			wrAddrGrayD2;
	reg		[ADDRWIDTH:0]			rdAddrGrayD1;
	reg		[ADDRWIDTH:0]			rdAddrGrayD2;

	always @(posedge wrClk or negedge rst_n) begin
		if (!rst_n) begin
			wrAddrGrayD1	<= {(ADDRWIDTH + 1){1'b0}};
			wrAddrGrayD2	<= {(ADDRWIDTH + 1){1'b0}};
		end
		else begin
			wrAddrGrayD1	<= wrAddrGray;
			wrAddrGrayD2	<= wrAddrGrayD1;
		end
	end

	always @(posedge rdClk or negedge rst_n) begin
		if (!rst_n) begin
			rdAddrGrayD1	<= {(ADDRWIDTH + 1){1'b0}};
			rdAddrGrayD2	<= {(ADDRWIDTH + 1){1'b0}};
		end
		else begin
			rdAddrGrayD1	<= rdAddrGray;
			rdAddrGrayD2	<= rdAddrGrayD1;
		end
	end

	assign	emptyFlag	= rdAddrGray == wrAddrGrayD2;
	assign	fullFlag	= wrAddrGray == {~rdAddrGrayD2[ADDRWIDTH:ADDRWIDTH-1], rdAddrGrayD2[ADDRWIDTH-2:0]};

endmodule

Conclusion

2022 一开年就太忙了,2021 年终总结可以变成牛年总结了。

这周接受了一个观点,和一个和你思考方式不同的人生活,比和另一个你一起生活更有趣。

2022 年想把联合国儿童基金会的月捐金额提高,提高多少就要看老板给我涨多少工资了。觉得慈善不光是在帮助孩子,更是在救赎我自己,回馈每一个在阴影之下给过我光的人。在物竞天择适者生存的丛林之下,希望每个孩子有一次尝试走出困境的机会。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Verilog FIFO(First-In-First-Out)是一种数据结构,用于在两个或多个模块之间传输数据。FIFO 通常用于缓冲数据,以便发送者和接收者之间的速度不匹配时进行数据传输。 下面是一个基本的 Verilog FIFO 模块的示例: ```verilog module fifo( input clk, input reset, input read, input write, input [7:0] data_in, output [7:0] data_out, output full, output empty ); // 设置 FIFO 的深度 parameter depth = 16; // 定义 FIFO 的数据结构 reg [7:0] fifo_data [0:depth-1]; reg [4:0] write_pointer = 0; reg [4:0] read_pointer = 0; // 定义 FIFO 的状态 reg [4:0] count = 0; wire [4:0] next_count; // 计算下一个状态 assign next_count = count + write - read; // 计算 FIFO 是否为空或满 assign full = (next_count == depth); assign empty = (next_count == 0); // 写数据 always @(posedge clk) begin if (reset) begin write_pointer <= 0; count <= 0; end else if (write && !full) begin fifo_data[write_pointer] <= data_in; write_pointer <= write_pointer + 1; count <= count + 1; end end // 读数据 always @(posedge clk) begin if (reset) begin read_pointer <= 0; end else if (read && !empty) begin data_out <= fifo_data[read_pointer]; read_pointer <= read_pointer + 1; count <= count - 1; end end endmodule ``` 该模块具有以下输入和输出: - `clk`:时钟信号。 - `reset`:重置信号。 - `read`:读使能信号。 - `write`:写使能信号。 - `data_in`:写入的数据。 - `data_out`:读出的数据。 - `full`:FIFO 是否已满。 - `empty`:FIFO 是否为空。 其,`depth` 参数定义了 FIFO 的深度,`fifo_data` 数组存储 FIFO 的数据,`write_pointer` 和 `read_pointer` 指向 FIFO 下一个可写/可读的位置,`count` 表示 FIFO 当前存储的数据量。 模块的两个 `always` 块分别处理写和读操作。当写使能信号为高电平且 FIFO 未满时,将数据写入 FIFO 并更新指针和计数器。当读使能信号为高电平且 FIFO 不为空时,从 FIFO 读取数据并更新指针和计数器。注意,在读操作,`data_out` 的值将在下一个时钟周期更新。 以上是一个简单的 Verilog FIFO 模块示例。在实际应用FIFO 的实现可能会更加复杂,并且需要考虑各种情况下的同步和异步数据传输。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值