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

本文深入探讨了异步FIFO的设计原理,包括如何利用格雷码和双时钟域同步来减少亚稳态的影响。通过详细的代码示例,讲解了RAM模块、地址同步模块、判空模块和判满模块的实现,以及顶层模块的整合。

fifo具体理论可以参考https://blog.youkuaiyun.com/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

仿真结果: 

 

在补充个任务目标。核心结论        DT005设计通过系统性优化策略,最终PPA得分优化至原始版本的589.8%,功耗优化至原始版本的6.01%,面积优化至原始版本的51.4%。 【编辑本段】【回目录】 关键优化措施 MEM替换LATCH:将存储单元从寄存器(Register)替换为锁存器(LATCH),显著降低面积和功耗。 ID存储方式优化:通过融合高低ID的FIFO结构,减少冗余存储单元,节省面积。 提升寄存器门控率:修改代码为可被综合工具识别添加时钟门控,有效降低动态功耗。 架构并行优化尝试:探索并行仲裁机制以提升性能,但未达到预期效果。 使用无复位寄存器:在非控制信号中,使用无复位寄存器代替有复位寄存器。 去掉无用DFX信号:部分DFX信号在打榜测试中未使用到,可以去除。 添加模块级门控:手动化模块级时钟门控,在模块空闲时关闭其时钟,以降低动态功耗及整体时钟树功耗。 【编辑本段】【回目录】 详细优化过程 V0版本:基准        我初步方案为两级过滤+单RAM+双ID-FIFO+串行仲裁。            打榜得分如下。          对比往年优秀设计,无论是功耗面积都有着较大差距。因此准备着手对PPA进行优化,经和导师沟通讨论,确定了以下优化checklist。 内存模块中的锁存器替换为寄存器 优化寄存器的时钟门控率 识别并移除无需复位端的寄存器 基于时序分析优化中间缓冲与缓存 输出调度方案的并行与串行实现验证 锁存器替换后的功耗优化 基于波形分析抑制无效翻转 V1版本:MEM替换LATCH 分析V0各模块面积占比,发现RAM占据了主要面积: HF_3W_256D:1065 LF_3W_256D:1069 MEM_9W_1024D:10392 MEM_9W_129D:1336 FIFO_11W_33D:467 总计:8 * (1065 + 1069 + 10392 + 1336 + 467) = 114632 寄存器与锁存器差异分析: 寄存器由两个锁存器构成(主锁存器低通,从锁存器高通)。 使用高通锁存器替代寄存器,可节省面积与功耗。 同类型锁存器面积为寄存器的1/2,静态功耗也更低。 通过替换RF_MEM模型,将原本基于寄存器的模型改为锁存器实现。替换的MEM模型见附件。     优化后打榜得分如下。 优化效果: PPA提升115.84% 面积降低40.03% 功耗降低71.71% 结论:锁存器结构显著降低面积与功耗,是PPA优化的关键步。   V2版本:使用并行仲裁     在前期方案分析的时候。并行轮询,相比串行轮询,各输出通道可以并行发送数据,可以显著降低每个包的平均等待时间。因此修改代码架构改为并行轮询。     优化后打榜得分如下。     由于增加了7个RR仲裁器,面积功耗均略微增长。但是T0并未降低,整体PPA得分略有降低。使用使用并行轮训对于提高PPA无效。     原因可能是,对于最差情况(各输入通道转发目的为同输出通道)时,并行轮询和串轮询效率相等。而打榜测试的时候可能包含了这种情况。   V3版本:优化ID存储方式        V0方案里,为了简化设计难度。过滤后缓存中的包ID,是使用两个FIFO分别存储。占用了本设计将近15%的面积。参考其他人的优化方案DT005 ppa分析_侯胜凯 - 侯胜凯的博客 (huawei.com),ID存储可以和包尾信号进行融合存储。基于该方案,对代码进行优化。 优化后打榜得分如下。     优化后打榜得分如下。 优化后: PPA提升14.60% 面积降低13.29% 功耗降低12.18% 结论:该优化对架构改动小,效果显著,值得采用。   V4版本:提升寄存器门控率        时钟门控是种有效的低功耗设计方法。 always @(posedge clk or negedge rst_n) begin     if (!rst_n)         q <= 32'b0;     else         if (en)             q <= d; // 使能有效时,接收新数据         else             q <= q; // 使能无效时,保持寄存器Q端数据不变。不写else分支,也能达到相同的效果 end 上面是设计常用的带有复位和使能的寄存器,其对应电路图如下图a)所示。上面的写法满足EDA工具自动插入时钟门控的条件,所以工具可以综合出带有时钟门控的寄存器,如下图所示,由于ICG(Intergrated Clock Gating) Cell的引入,en无效的时候,寄存器的时钟是可以被关闭的。 V3设计的门控率如下。        门控率仅为76%,分析DC中clock_ungate报告,有大量的8位数据信号未进行门控,导致寄存器持续反转,功耗上升。因此着手进行代码优化,给相应多bit数据加上时钟门控。     经过优化后,门控率升至90.19%。 优化后打榜得分如下。 优化后: 门控率提升至90.19% PPA提升47.37% 功耗降低53.77% 结论:提升门控率是降低功耗、提升PPA的有效手段。 V5版本:尽量使用无复位寄存器 无复位寄存器相比有复位寄存器在功耗和面积上均有些优势。可以在些非控制信号无需复位的寄存器去掉复位信号提高PPA。 优化后打榜得分如下。 优化后: PPA提升0.97% 面积降低0.17% 功耗降低1.67% 结论:使用无复位寄存器有在PPA上有所提升,但总体幅度较小,可能是因为本设计寄存器占总逻辑单元数量比较少导致的。 【编辑本段】【回目录】 V6版本:去掉无用的DFX计数器 在打榜测评是有些无用的DFX信号,可以在提交时去掉。 如DT005的包计数器,包含16个32bit计数器,经测试回归测试最多会使用15位,手动优化去掉未使用到的位宽。 优化后: 优化后: PPA提升3.10% 面积降低0.67% 功耗降低5.27% 结论:去掉无用的DFX计数器可以小幅提高PPA成绩。   V7版本:分级时钟门控技术 时钟门控技术除了V4版本使用的自动时钟门控技术,还有分级时钟门控技术。自动时钟门控即使所有的寄存器都增加了门控,时钟跟节点到达所有门控单元的的路上消耗的功耗确依然存在,为了优化这部分功耗,引入了分级时钟门控技术。 如上图所示,电路不需要工作时,分级时钟门控技术能减少Level1 ICG和 Syn ICG之间时钟树上的功耗。般来说分级时钟门控需要在工具自动插入门控前面手动化ICG Cell。 DT005提供的标准单元库可以化ICG门控单元 在每级模块的接收和发送都手动插入了ICG,让其只在工作时打开时钟。 优化后: PPA提升49.91% 面积提升0.12% 功耗降低43.75% 结论:分级时钟门控可以有效降低功耗,有效提升PPA。   小结 version T0 Power Area PPA Score V0:基准 104576 0.0047 118548 95.14 V1:MEM替换LATCH 132291 0.00133 71096 205.35 V2:使用并行仲裁 132291 0.00136 71470 202.32 V3:优化ID存储方式 132293 0.00117 61644 235.33 V4:提升寄存器门控率 132293 0.00054 61362 346.8 V5:尽量使用无复位寄存器 132293 0.00053 61258 350.15 V6:去掉无用的DFX计数器 132294 0.0005 60845 361.02 V7:分级时钟门控技术 104578 0.00028 60921 541.19 注:V1-V6版本由于使用了其他版本的VCS导致T0偏大,实际应该与V0、V7相同。 针对DT005设计,最有效的PPA优化方案为: MEM替换LATCH(V1) 自动时钟门控(V4) 和分级时钟门控(V7) 本方案采用延时处理避免包饿死,实现简单,但PPA与单RAM方案仍有差距。 最后打榜成绩如下,比本地得分略低些。 附录 DC综合脚本分析:DC脚本仅插入时钟门控,未进行模块打平(ungroup),无法进行模块间时序优化。手动打平模块间信号可进步提升PPA。手动调用ungroup命令打平模块后,PPA提升明显。 门控插入规则:DC默认仅对3bit及以上信号插入门控,设置set_clock_gating_style -minimum_bitwidth 1后,可以让其对1bit信号也进行门控插入。最后功耗反而上升,表明门控引入功耗超过节省值。
08-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值