以异步fifo为例使用vcs进行仿真(一):建立asyn_fifo仿真工程

fifo具体理论可以参考https://blog.csdn.net/alangaixiaoxiao/article/details/81432144

主要点:

  1. 时钟之间通过打两拍进行同步,减小亚稳态的概率(不一定逻辑是正确的)
  2. 地址采用格雷码。因为二进制可能同时有多位进行变化,相对于每次只发生一位变化的格雷码易发生亚稳态。

                                                                     异步FIFO结构图

知识延伸:

  1. 亚稳态:亚稳态是指触发器无法在 某个规定的时间段 内达到可以确认的状态。一旦触发器进入亚稳态,则既无法预测触发器的输出电平,也无法预测什么时候稳定在某个确认的电平上。此时的触发器输出端Q在较长时间内处于震荡状态且不等于输入端D,并且这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去。在同步系统中,输入信号总是与系统时钟同步,能够达到寄存器的时序要求,所以亚稳态不会发生。亚稳态通常发生在跨时钟域信号传输以及异步信号采集上
  2. 格雷码和独热码: 二进制编码、格雷码编码使用最少的触发器,消耗较多的组合逻辑,而独热码编码反之。独热码编码的最大优势在于状态比较时仅仅需要比较一个位,从而一定程度上简化了译码逻辑。虽然在需要表示同样的状态数时,独热编码占用较多的位,也就是消耗较多的触发器,但这些额外触发器占用的面积可与译码电路省下来的面积相抵消。

FIFO代码如下:

       RAM 模块

module Ram # (
	parameter	DATA_WIDTH = 4,
	parameter	ADDR_WIDTH = 8
)
(
	input			[ADDR_WIDTH-1:0]    wr_addr,
	input			[ADDR_WIDTH-1:0]    rd_addr,
    input                               arst_n,
    input                               wr_clk,
    input				           		rd_clk,
    input	        [DATA_WIDTH-1:0]	wr_data,
	output	reg     [DATA_WIDTH-1:0]	rd_data
);
    
    localparam  DEPTH = 1 << ADDR_WIDTH;
    
    //建立memory
    reg     [DATA_WIDTH-1:0]    Ram_mem     [0:DEPTH-1];
    integer i;

    //初始化memory,并在写时钟读对应地址的数据
    always@(posedge wr_clk or negedge arst_n)  begin
        if(!arst_n) begin
            for(i = 0;i < DEPTH;i = i + 1)
                Ram_mem[i] <= 8'b0;
        end
        else 
        	Ram_mem[wr_addr] <= wr_data;
    end
    
    //在读时钟域读取数据(可以写成组合逻辑,看个人习惯)
    always@(posedge rd_clk or negedge arst_n) begin
    	if(!arst_n) begin
    		rd_data <= 0;
    	end
    	else
    		rd_data <= Ram_mem[rd_addr];
    end
endmodule

地址在异步时钟域同步模块 

module syn_r2w #(
	parameter	ADDR_WIDTH = 8
	)
(
	input	     [ADDR_WIDTH:0]	rptr,
	input			       		wclk,
	input		    			arst_n,
	output	reg [ADDR_WIDTH:0]  ptr_r2w
	);
	
	reg		[ADDR_WIDTH:0]	pad_rptr;

    //打两排实现地址在时钟域上的同步,降低亚稳态概率
	always @(posedge wclk or negedge arst_n) begin
		if (!arst_n) begin
			{ptr_r2w,pad_rptr} <= 0;
		end
		else begin
		     {ptr_r2w,pad_rptr} <= {pad_rptr,rptr};
		end
	end


endmodule
module syn_w2r #(
	parameter	ADDR_WIDTH = 8
	)
(
	input	        [ADDR_WIDTH:0]	wptr,
	input				          	rclk,
	input			           		arst_n,
	output	reg     [ADDR_WIDTH:0]  ptr_w2r
	);
	
	reg		[ADDR_WIDTH:0]	pad_wptr;

	always @(posedge rclk or negedge arst_n) begin
		if (!arst_n) begin
			{ptr_w2r,pad_wptr} <= 0;
		end
		else begin
			{ptr_w2r,pad_wptr} <= {pad_wptr,wptr};
		end
	end


endmodule

读时钟域判空模块

module rptr_empty #(
	parameter	ADDR_WIDTH = 8
	)
(
	output		reg							rempty,
	output		    	[ADDR_WIDTH-1:0]	raddr,//写到RAM的二进制地址
	output		reg		[ADDR_WIDTH:0]		rptr,//输入到写时钟域的格雷码地址
	input  				[ADDR_WIDTH:0]		ptr_w2r,//同步到读时钟域的写格雷码地址
	input									rinc,//读使能
	input									rclk,
	input									arst_n
	);
	
	reg		[ADDR_WIDTH:0]		rbin;
	wire	[ADDR_WIDTH:0]		rgaynext,rbinnext;
	wire	                    rempty_val;

    //时序逻辑二进制和格雷码地址,负责初始化和时序逻辑
	always @(posedge rclk or negedge arst_n) begin
		if (!arst_n) begin
			rbin <= 0;
			rptr <= 0;
		end
		else begin
			rbin <= rbinnext;
			rptr <= rgaynext;
		end
	end

    //根据使能信号判断是否对读地址加一
	assign rbinnext  = !arst_n ? 0: 
							!rinc? rbin : 
								rempty ? rbin : 
											(rbin+1);
    //格雷码地址转换
	assign rgaynext = !arst_n ? 0 : 
						(rbinnext>>1)^(rbinnext);
    //给RAM的实际地址
	assign raddr = rbin[ADDR_WIDTH-1:0];

	//判空逻辑
	assign rempty_val = !arst_n ? 1:
							(rgaynext == ptr_w2r);

    //时序逻辑完成同步
	always @(posedge rclk or negedge arst_n) begin
		if (!arst_n) begin
			rempty <= 1'b1;
		end
		else begin
			rempty <= rempty_val;
		end
	end



endmodule

写时钟判满模块 

module wptr_full #(
	parameter	ADDR_WIDTH = 8
	)
(
	output	reg							wfull,
	output	     	[ADDR_WIDTH-1:0]	waddr,
	output	reg		[ADDR_WIDTH:0]		wptr,
	input			[ADDR_WIDTH:0]		ptr_r2w,
	input								winc,//写使胿
	input								wclk,
	input								arst_n						
	);
	
	reg		[ADDR_WIDTH:0]		wbin;
	wire		[ADDR_WIDTH:0]		wgraynext,wbinnext,r2w_graytmp;
	wire	wfull_eval;

	always @(posedge wclk or negedge arst_n) begin
		if (!arst_n) begin
			wbin <= 0;
			wptr <= 0;
		end
		else begin
			wbin <= wbinnext;
			wptr <= wgraynext;
		end
	end

	assign wbinnext = !arst_n ? 0 :
								!winc ? wbin : 
										( wfull ? wbin : 
													( wbin+1 ) );
	assign wgraynext = !arst_n ? 0:
							(wbinnext >> 1)^(wbinnext);
	assign waddr = wbin[ADDR_WIDTH-1:0];
	
	assign r2w_graytmp = !arst_n ? 0:
	                   {~ptr_r2w[ADDR_WIDTH:ADDR_WIDTH-1],ptr_r2w[ADDR_WIDTH-2:0]};

	assign wfull_eval = !arst_n ? 0:
						( wgraynext == r2w_graytmp );

	always @(posedge wclk or negedge arst_n) begin
		if (!arst_n) begin
			wfull <= 1'b1;
		end
		else begin
			wfull <= wfull_eval;
		end
	end


endmodule


FIFO顶层模块

module asyn_fifo #(
		parameter	ADDR_WIDTH = 4,
		parameter	DATA_WIDTH = 2
	)
(
	input								arst_n,
	input			[DATA_WIDTH-1:0]	wdata,
	input								winc,
	input								wclk,
	output			[DATA_WIDTH-1:0]	rdata,
	input								rinc,
	input								rclk,
	output								rempty,
	output								wfull,
	//接出两个接口看信息
	output			[ADDR_WIDTH-1:0]	waddr,
	output			[ADDR_WIDTH-1:0]	raddr
	);


	wire	[ADDR_WIDTH-1:0]	waddr,raddr;
	wire	[ADDR_WIDTH:0]	wptr,rptr,ptr_r2w,ptr_w2r;

	syn_r2w #(
	.ADDR_WIDTH(ADDR_WIDTH)
	)
	fifo_syn_r2w(
	.rptr(rptr),
	.wclk(wclk),
	.arst_n(arst_n),
	.ptr_r2w(ptr_r2w)
	);

	syn_w2r #(
	.ADDR_WIDTH(ADDR_WIDTH)
	)
	fifo_syn_w2r(
	.wptr(wptr),
	.rclk(rclk),
	.arst_n(arst_n),
	.ptr_w2r(ptr_w2r)
	);

	Ram # (
	.DATA_WIDTH(DATA_WIDTH),
	.ADDR_WIDTH(ADDR_WIDTH)
	)
	fifo_Ram(
	    .wr_addr(waddr),
	    .rd_addr(raddr),
        .arst_n(arst_n),
        .wr_clk(wclk),
        .rd_clk(rclk),
        .wr_data(wdata),
	    .rd_data(rdata)
	);

 	rptr_empty #(
	.ADDR_WIDTH(ADDR_WIDTH)
	)
	fifo_rptr_empty(
	.rempty(rempty),
	.raddr(raddr),//写到RAM的二进制地址
	.rptr(rptr),//写到读时钟域的格雷码地址
	.ptr_w2r(ptr_w2r),//读时钟域的写格雷码地坿
	.rinc(rinc),//写使胿
	.rclk(rclk),
	.arst_n(arst_n)
	);

	wptr_full #(
	.ADDR_WIDTH(ADDR_WIDTH)
	)
	fifo_wptr_full(
	.wfull(wfull),
	.waddr(waddr),
	.wptr(wptr),
	.ptr_r2w(ptr_r2w),
	.winc(winc),//写使胿
	.wclk(wclk),
	.arst_n(arst_n)						
	);

endmodule

testbench

`timescale 1ns / 1ps


module tb_asyn_fifo;

	parameter	ADDR_WIDTH = 4;
	parameter	DATA_WIDTH = 2;
	
	localparam	DEPTH = 1 << ADDR_WIDTH;

	reg 						tb_arst_n,tb_wclk,tb_rclk,tb_winc,tb_rinc;
	reg		[DATA_WIDTH-1:0]	tb_wdata;
	wire						tb_rempty,tb_wfull;
	wire	[DATA_WIDTH-1:0]	tb_rdata;
	wire	[ADDR_WIDTH-1:0]	tb_waddr,tb_raddr;
	
	initial begin
		tb_arst_n = 0;tb_wclk = 0;tb_rclk = 0;tb_wdata = 0;
		tb_winc = 0;tb_rinc = 0;
        #11
		tb_arst_n = 1;
		#500
		tb_arst_n = 0;
	end

//    initial begin
//        $vcdpluson;
//    end
	
	initial begin
		#21
		read_data();
	end
	
	initial begin
	   #41
		write_data();
	end
	
	always #2 tb_wclk = ~tb_wclk;
	always #5 tb_rclk = ~tb_rclk;


asyn_fifo #(
	.ADDR_WIDTH(ADDR_WIDTH),
	.DATA_WIDTH(DATA_WIDTH)
	)
 uut_asyn_fifo(
	.arst_n(tb_arst_n),
	.wdata(tb_wdata),
	.winc(tb_winc),
	.wclk(tb_wclk),
	.rdata(tb_rdata),
	.rinc(tb_rinc),
	.rclk(tb_rclk),
	.rempty(tb_rempty),
	.wfull(tb_wfull),
	.waddr(tb_waddr),
	.raddr(tb_raddr)
	);

task write_data;
	begin
		forever begin
			@(posedge tb_wclk) begin
				tb_winc <= 1;
				tb_wdata <= ($random)%4;
			end
		end
	end
endtask

task read_data;
	begin
		forever begin
			@(posedge tb_rclk) begin
				tb_rinc <= 1;
			end
		end
	end
endtask
endmodule

仿真结果: 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值