前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FIFO系列(三):fifo与格雷码以及异步fifo设计

FIFO系列(三):fifo与格雷码以及异步fifo设计

作者头像
根究FPGA
发布2020-06-29 15:28:30
2.9K0
发布2020-06-29 15:28:30
举报
文章被收录于专栏:根究FPGA

本系列分为以下部分:

1、FIFO深度计算

2、同步fifo设计

3、fifo与格雷码以及异步fifo设计

计划分三次更新完毕,本次为本系列终结篇!

本次增加异步FIFO设计,异常有趣!

格雷码的相关知识

关于同步fifo的设计疑惑了半天,本以为这个代码是错的,后来自己又写了一遍,但是写到最后又觉得这个是正确的,主要是wr_cnt和rd_cnt的理解。

1、格雷码是什么

英文名Grey Code,在一组数的编码中,任意的两个相邻的代码只有一位二进制数不同,即为格雷码,常用于异步操作中,比如异步fifo,读数据侧使用数据读取时钟,写数据侧使用数据写入时钟,当发生状态跳转时,只会有一位会发生变化,可有效降低由于建立时间和保持时间导致的误差,比如从0001跳转到0011,可能检测到的是0001,那么就在该节拍等待一个时钟,如果检测到0011,直接就跳转到0011状态。

而使用二进制时,若当前状态为0001跳转到0010,可能会检测到0000或者0011,因为不确定谁先变化,导致状态跳转错误。

2、格雷码的优势

(1)、降低亚稳态的发生概率。十进制计数容易产生毛刺,多个bit变化容易导致潜在的竞争和冒险,异步操作时使用格雷码可有效消除竞争和冒险,比如异步fifo,当发生状态跳转时,只会有一位会发生变化,可有效降低由于建立时间和保持时间导致的误差。

(2)、有利于降低功耗。当发生状态跳转时,寄存器的翻转是消耗动态功耗的,而这种编码将翻转次数次数降到最低,只会有一位会发生变化,可有效降低功耗。

3、二进制转格雷码

二进制码转换为格雷码的方法为:保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码的其他位与次高位的求法类似,即:

代码语言:javascript
复制
assign gray=(binary>>1)^(binary);

4、异步fifo与格雷码的爱恨情愁

我不想排版了!!!直接截图~~

实际上,读写指针指向的都是“101”这个存储单元,但是写操作比读操作多跑了一圈,所以要产生一个full标志。

此外,在空满标志的产生要提前比较,即默认为当前读取非空,写入操作非满,比较的是下一次的操作地址。

5、异步fifo代码+分析

代码语言:javascript
复制
module asyn_fifo#(
parameter DATA_WIDTH=8,
parameter ADDR_WIDTH=3,  //地址位宽为log2(deepth)
parameter DATA_DEPTH=1<<ADDR_WIDTH
)
(
input rst_n,   //复位信号输入(应该对复位进行处理同步,个人认为,参考xilinx fifo IP同步stage)
input rd_clk,  //读取时钟
input wr_clk, //写时钟
input rd_en,  //读取使能
input wr_en,  //写使能
input [DATA_WIDTH-1:0] data_w, //写入数据
output reg [DATA_WIDTH-1:0]data_r,  //读出数据
output reg full,    //满标志
output reg empty    //空标志
); 
//定义DATA_DEPTH个数据宽度为DATA_WIDTH的数据
reg [DATA_WIDTH-1:0]mem[DATA_DEPTH-1:0];
//二进制格式下的写地址,二进制的地址宽度为3 写地址指针
reg [ADDR_WIDTH:0]wr_addr_bin;  
//二进制格式下的读地址,二进制的地址宽度为3(深度以2为底的对数)
reg [ADDR_WIDTH:0]rd_addr_bin;

//gray code 表示的地址
reg [ADDR_WIDTH:0]syn_wr_addr0_gray;  //格雷码写地址0
reg [ADDR_WIDTH:0]syn_wr_addr1_gray;  //格雷码写地址1
reg [ADDR_WIDTH:0]syn_wr_addr2_gray;  //格雷码写地址2

reg [ADDR_WIDTH:0]syn_rd_addr0_gray;  //格雷码读地址0
reg [ADDR_WIDTH:0]syn_rd_addr1_gray;  //格雷码读地址1
reg [ADDR_WIDTH:0]syn_rd_addr2_gray;  //格雷码读地址2

wire [ADDR_WIDTH-1:0] fifo_enter_addr;  //fifo传入的地址

wire [ADDR_WIDTH-1:0]fifo_exit_addr;    //fifo输出的地址
/*
关于为什么binary下的地址宽度大一位,是因为要转化为格雷码,作用是为了空满比较
*/
wire [ADDR_WIDTH:0] wr_nextaddr_bin;
wire [ADDR_WIDTH:0] rd_nextaddr_bin;
wire [ADDR_WIDTH:0] wr_nextaddr_gray;
wire [ADDR_WIDTH:0] rd_nextaddr_gray;

wire asyn_full;   //满标志信号,由于写入数据导致,属于写时钟域
wire asyn_empty;  //空标志信号,由于读取数据导致,属于读时钟域

//fifo数据的写入和读出地址,格雷码的最高位表示第二轮
assign fifo_enter_addr = wr_addr_bin[ADDR_WIDTH-1:0];  //写入数据到fifo的写指针指向的地址
assign fifo_exit_addr  = rd_addr_bin[ADDR_WIDTH-1:0];  //从fifo中读取数据指针指向的地址

//写入数据
always@(posedge wr_clk or negedge rst_n)
if(~rst_n)
begin   //复位将fifo清零
 integer i;
 for(i=0;i<DATA_DEPTH;i=i+1)
  mem[i]<={DATA_DEPTH{1'b0}}; 
end 
else if(wr_en&(~full))  //写使能且fifo未满
 mem[fifo_enter_addr]<=data_w;   //将数据写入到mem
else  
 mem[fifo_enter_addr]<=mem[fifo_enter_addr];
 
//数据读取
always@(posedge rd_clk or negedge rst_n)
if(~rst_n)
 data_r<={DATA_DEPTH{1'b0}};
else 
 data_r<=mem[fifo_exit_addr]; 
 
//fifo读写地址生成器,二进制转格雷码,
//在fifo未满且写请求时,将写地址自动加一(binary)
assign wr_nextaddr_bin= (wr_en&(~full))  ?  fifo_enter_addr+1'b1: fifo_enter_addr;
assign rd_nextaddr_bin= (rd_en&(~empty)) ?  fifo_exit_addr+1'b1 : fifo_exit_addr ;
//convert binary to gray code 就是二进制地址右移一位之后与原地址异或
assign wr_nextaddr_gray=(wr_nextaddr_bin>>1)^wr_nextaddr_bin;
assign rd_nextaddr_gray=(rd_nextaddr_bin>>1)^rd_nextaddr_bin;

always@(posedge wr_clk or negedge rst_n)
if(~rst_n)
begin
  wr_addr_bin<=0;
  syn_wr_addr0_gray<=0;
end 
else 
begin
 wr_addr_bin<=wr_nextaddr_bin;  //下一个地址赋值给写数据地址
 syn_wr_addr0_gray<=wr_nextaddr_gray;  //将下一个地址的格雷码赋值给syn_wr_addr0_gray进行同步寄存
end 

always@(posedge rd_clk or negedge rst_n)
if(~rst_n)
begin
 rd_addr_bin<=0;
 syn_rd_addr0_gray<=0;
end 
else begin
 rd_addr_bin<=rd_nextaddr_bin;
 syn_rd_addr0_gray<=rd_nextaddr_gray;
end 

/*
对格雷码进行同步:
 1、将格雷码写地址同步到读时钟域,用于空判断
 2、将格雷码读地址同步到写时钟域,用于满判断
*/
always@(posedge rd_clk or negedge rst_n)
if(~rst_n)
begin
 syn_wr_addr1_gray<=0;
 syn_wr_addr2_gray<=0;
end 
else begin
 syn_wr_addr1_gray<=syn_wr_addr0_gray;
 syn_wr_addr2_gray<=syn_wr_addr1_gray;
end 

always@(posedge wr_clk or negedge rst_n )
if(~rst_n)
begin
 syn_rd_addr1_gray<=0;
 syn_rd_addr2_gray<=0;
end 
else begin
 syn_rd_addr1_gray<=syn_rd_addr0_gray;
 syn_rd_addr2_gray<=syn_rd_addr1_gray;
end 

//将产生的同步后的地址进行比较
assign asyn_empty = ( rd_nextaddr_gray==syn_wr_addr2_gray );
//格雷码的高两位相反,其余位相同时,表示fifo满
assign asyn_full  = ( wr_nextaddr_gray=={ ~{syn_rd_addr2_gray[ADDR_WIDTH:ADDR_WIDTH-1]},syn_rd_addr2_gray[ADDR_WIDTH-2:0] } );

always@(posedge wr_clk or negedge  rst_n )
if(~rst_n )
 full<=1'b0;
else
 full<=asyn_full;
 
always@(posedge rd_clk or negedge rst_n )
if(~rst_n)
 empty<=1'b1;
else 
 empty<=asyn_empty;
endmodule

~~感谢阅读~~周末愉快

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 根究FPGA 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 格雷码的相关知识
  • 1、格雷码是什么
  • 2、格雷码的优势
  • 3、二进制转格雷码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档