当前位置: 首页 > news >正文

【IC每日一题--单bitCDC跨时钟和同步FIFO】

IC Daily QA--CDC跨时钟和同步FIFO

  • 1 八股题:CDC跨时钟数据传输问题--单bit跨时钟
    • 1.1 从慢时钟到快时钟--->直接打两拍即可;
    • 1.2 快时钟到慢时钟
      • 1.2.1 脉冲信号展宽+边沿检测
      • 1.2.2 慢到快时钟--握手+边沿
  • 2 手撕题:同步FIFO代码;
    • 1.1 概念
    • 1.2 算法步骤
      • 1.2.1 计数器法判断
      • 1.2.2 读写地址--高位拓展法

本文介绍单bit跨时钟域同步方法以及同步FIFO;

1 八股题:CDC跨时钟数据传输问题–单bit跨时钟

CDC概念:在涉及不同的时钟域(相位差和频率存在差异),可能会导致数据在传输过程中出现亚稳态问题;
CDC基本分类:单bit信号传输(脉冲传输)、多bit信号传输、快到慢时钟、慢到快时钟

1.1 从慢时钟到快时钟—>直接打两拍即可;

按理说,会存在多采样问题,前提是快慢时钟的差距不大;

//==========================================================
//--Author  : colonel
//--Date    : 10-31
//--Module  : sync_1bit_from_slow_2_fast
//--Function: sync 1bit from slow clk to fast clk
//==========================================================module sync_1bit_from_slow_2_fast(
//==========================< 端口 >=========================input wire slow_clk,input wire rst_n,input wire din,input wire fast_clk,output wire sync_dout
);
//==========================< 信号 >=========================
reg din_ff0,din_ff1,din_ff2;//=========================================================
//-- din_ff0 : sync from slow_clk
//=========================================================
always @(posedge slow_clk or negedge rst_n) beginif (!rst_n) begindin_ff0 <= 1'b0;end else begindin_ff0 <= din;end
end//=========================================================
//-- din_ff1 : sync from fast_clk
//=========================================================
always @(posedge fast_clk or negedge rst_n) beginif (!rst_n) begindin_ff1 <= 0;din_ff2 <= 0;end else begindin_ff1 <= din_ff0;din_ff2 <= din_ff1;end
endassign sync_dout = din_ff2;endmodule

1.2 快时钟到慢时钟

可能存在采样丢失的问题;
从快时钟到慢时钟的两种方法:1.信号展宽 + 边沿检测; 2.握手机制;

1.2.1 脉冲信号展宽+边沿检测

脉冲信号展宽+边沿检测:将脉冲信号转换成电平信号再进行边沿检测;
在两次信号之间为电平信号;

电路图如下:


//==========================================================
//--Author  : colonel
//--Date    : 10-31
//--Module  : sync_1bit_from_fast_2_slow_pulse_widen
//--Function: sync 1bit from fast clk to slow clk
//==========================================================
module sync_1bit_from_fast_2_slow_pulse_widen(
//==========================< 端口 >=========================input wire fast_clk,input wire rst_n,input wire din,input wire slow_clk,output wire dout
);
//==========================< 信号 >=========================
reg din_fast_r;//=========================================================
//-- din_fast_r: 将脉冲信号在快时钟域展平为电平信号。即展宽脉冲信号。在两次脉冲信号之间为电平信号
//=========================================================
always @(posedge fast_clk or negedge rst_n) beginif (!rst_n) begindin_fast_r <= 'b0;end else begindin_fast_r <= din ? (~din_fast_r) : din_fast_r;end
end//==========================< 信号 >=========================
reg data_slow_ff1;
reg data_slow_ff2;
reg data_slow_ff3;//=========================================================
//-- data_slow_ff1/2/3: 将展宽的脉冲信号在慢时钟域打三拍,并检测边沿
//=========================================================
always @(posedge slow_clk or negedge rst_n) beginif (!rst_n) begindata_slow_ff1 <= 'b0;data_slow_ff2 <= 'b0;data_slow_ff3 <= 'b0;end else begindata_slow_ff1 <= din_fast_r;data_slow_ff2 <= data_slow_ff1;data_slow_ff3 <= data_slow_ff2;end
endassign dout = data_slow_ff3 ^ data_slow_ff2;  endmodule

1.2.2 慢到快时钟–握手+边沿

  • 将src时钟域的脉冲信号打一拍之后,在dst时钟域内打三拍进行同步,其中第二拍的结果作为dst时钟域的应答信号,第二拍和第三拍的结果做边沿检测,以保证在dst时钟域输出接收到的脉冲信号。

  • 其中第二拍的应答信号,在src时钟域经过两级同步作为src的应答信号,当输入脉冲信号时,src的请求信号有效。当src的应答信号有效时,请求信号无效。

//==========================================================
//--Author  : colonel
//--Date    : 10-31
//--Module  : sync_1bit_from_fast_2_slow_handshake
//--Function: sync 1bit from fast clk to slow clk
//==========================================================
module sync_1bit_from_fast_2_slow_handshake(
//==========================< 端口 >=========================input wire fast_clk,input wire rst_n,input wire din_pulse,input wire slow_clk,output wire dst_pulse
);
//==========================< 信号 >=========================
reg src_sync_req;
reg src_sync_ack;
reg src_req_ff1,src_req_ff2,src_req_ff3;//=========================================================
//-- src_sync_req: 
//=========================================================
always @(posedge fast_clk or negedge rst_n) beginif (!rst_n) beginsrc_sync_req <= 1'b0;end else beginif (din_pulse) beginsrc_sync_req <= 1'b1;end else if(src_sync_ack) beginsrc_sync_req <= 1'b0;end else beginsrc_sync_req <= src_sync_req;endend
end//=========================================================
//-- src_req_ff1/2 
//=========================================================
always @(posedge slow_clk or negedge rst_n) beginif (!rst_n) beginsrc_req_ff1 <= 'b0;src_req_ff2 <= 'b0;src_req_ff3 <= 'b0;end else beginsrc_req_ff1 <= src_sync_req;src_req_ff2 <= src_req_ff1;src_req_ff3 <= src_req_ff2;end
endwire dst_sync_ack = src_req_ff2;//=========================================================
//-- src_sync_ack
//=========================================================
reg dst_sync_ack_ff1;
reg dst_sync_ack_ff2;always @(posedge fast_clk or negedge rst_n) beginif (!rst_n) begindst_sync_ack_ff1 <= 'b0;    dst_sync_ack_ff2 <= 'b0;    end else begindst_sync_ack_ff1 <= dst_sync_ack;dst_sync_ack_ff2 <= dst_sync_ack_ff1;end
endassign src_sync_ack = dst_sync_ack_ff2;//=========================================================
//-- dst_pulse
//=========================================================
assign dst_pulse = src_req_ff3 & ! src_req_ff2;endmodule

2 手撕题:同步FIFO代码;

FIFO:用于缓存数据来匹配不同速度之间的操作,从而以稳定的速率进行处理;
IP设计中经常使用到的是同步FIFO;异步FIFO基本用在跨时钟域场景下;
FIFO及先进先出的队列,自己内部管理读写地址(读写地址自增1),不需要向外暴露读写地址端口;

1.1 概念

FIFO存储体:分为用寄存器搭建和SRAM搭建;
寄存器搭建:存储数据量少;SRAM搭建:存储数据量大;
FIFO的重要点在于:FIFO空满的判断,有两种方式:1.是使用计数器;2.使用读写指针判断;
另外FIFO重要原则:空时不再读,满时不再写;

1.2 算法步骤

fifo设计关键:FIFO读写指针的逻辑和生成FIFO 空/满的标志位;

1.2.1 计数器法判断

内部维护一个fifo_cnt的逻辑:

0.复位时,该计数器为0,FIFO中的数据个数为0;
1.当wr_en = rd_en=1时,说明又读又写,计数器不变,FIFO中的数据个数无变化。
2.当wr_en =1,rd_en = 0时且 full=0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1 。
3.当wr_en =0,rd_en = 1时且 empty=0,则 fifo_cnt -1; 表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1 。
最后FIFO空满判断逻辑:
fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;fifo_cnt = fifo的深度 的时候,表示 FIFO现在已经满,需要设置 full=1。

代码如下:

//====================================
//--Author  : colonel
//--Date    : 10-30
//--Module  : sync_fifo
//--Function: the sync clk fifo logic
//====================================module sync_fifo #(
//==========================< 参数 >=========================parameter DATA_WIDTH = 8,parameter FIFO_DEPTH = 16
) (
//==========================< 端口 >=========================input wire clk,input wire rst_n,input wire wr_en,input wire [DATA_WIDTH -1:0] write_data,output wire wr_full,input wire rd_en,input wire [DATA_WIDTH -1:0] read_data,output wire rd_empty);
//==========================< 参数 >=========================
localparam DATA_DEPTH = $clog2(DATA_WIDTH);//==========================< 信号 >=========================
reg [DATA_WIDTH -1:0] fifo_mem[FIFO_DEPTH];
reg [DATA_DEPTH -1:0] fifo_cnt;
reg [DATA_DEPTH -1:0] wr_ptr;
reg [DATA_DEPTH -1:0] rd_ptr;//=========================================================
//-- write_data
//=========================================================
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginfifo_mem[wr_ptr] <= 0;end else beginif (wr_en && !wr_full) beginfifo_mem[wr_ptr] <= write_data;end else beginfifo_mem[wr_ptr] <= fifo_mem[wr_ptr] ;endend
end//=========================================================
//-- wr_ptr
//=========================================================
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginwr_ptr <= 0;end else beginif(!wr_full && wr_en) beginwr_ptr <= wr_ptr + 1'b1;end else beginwr_ptr <= wr_ptr;endend
end//=========================================================
//-- write_data
//=========================================================always @(posedge clk or negedge rst_n) beginif(!rst_n) beginfifo_mem[wr_ptr] <= 'b0;end else beginif(!wr_full && wr_en) beginfifo_mem[wr_ptr] <= write_data;end else beginfifo_mem[wr_ptr] <= fifo_mem[wr_ptr];endend
end//=========================================================
//-- rd_ptr
//=========================================================
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginrd_ptr <= 'b0;end else beginif (!rd_empty && rd_en) beginrd_ptr <= rd_ptr + 1'b1;end else beginrd_ptr <= rd_ptr;endend
end//=========================================================
//-- read_data
//=========================================================
reg [DATA_WIDTH -1:0] read_data_tmp;
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginread_data_tmp <= 'b0;end else beginif (!rd_empty && rd_en) beginread_data_tmp <= fifo_mem[rd_ptr];end else beginread_data_tmp <= read_data_tmp;endend
endassign read_data = read_data_tmp;//=========================================================
//-- fifo_cnt
//=========================================================
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginfifo_cnt <= 'b0;end else begin//--cases:if (wr_en && rd_en) beginfifo_cnt <= fifo_cnt;end else if (wr_en && !rd_en) beginfifo_cnt <= fifo_cnt + 1'b1;end else if (!wr_en && rd_en) beginfifo_cnt <= fifo_cnt - 1'b1;end else beginfifo_cnt <= fifo_cnt;endend
endassign wr_full = fifo_cnt == FIFO_DEPTH;
assign rd_empty= fifo_cnt == 0;endmodule

1.2.2 读写地址–高位拓展法

  • FIFO读空的情况:(读指针追上写指针)–读写指针实质为读写地址
    ① 在复位操作时,当读写指针相等时,表明 FIFO 为空;
    ② 当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时,表明 FIFO 为空。如下左图。
  • FIFO写满的情况:(写指针追上读指针)
    ① 当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。如下右图。
    在这里插入图片描述

拓展高地址位判断法:

  • 当最高位不同,且其他位相同:则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈,所以可能出现的情况只能是写指针多跑了一圈,就意味着FIFO被写满了。
  • 当最高位相同,且其他位相同:则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了。

此处要考虑FIFO深度不是2^N的情况,因此需要把高低位的判断分开;

//==========================================================
//--Author  : colonel
//--Date    : 10-30
//--Module  : sync_fifo_cnt
//--Function: the sync_fifo logic using cnt way
//==========================================================module sync_fifo_cnt_way #(
//==========================< 参数 >=========================parameter DATA_WIDTH = 8,parameter FIFO_DEPTH = 16
) (
//==========================< 端口 >=========================input wire clk,input wire rst_n,input wire wr_en,input wire [DATA_WIDTH -1:0] write_data,output wire wr_full,input wire rd_en,input wire [DATA_WIDTH -1:0] read_data,output wire rd_empty);
//==========================< 参数 >=========================
localparam DATA_DEPTH = $clog2(DATA_WIDTH);
localparam FIFO_DEPTH_WIDTH = $clog2(FIFO_DEPTH);//==========================< 信号 >=========================
reg [DATA_WIDTH -1:0] fifo_mem[FIFO_DEPTH];
reg [FIFO_DEPTH_WIDTH -1:0] fifo_cnt;
reg [FIFO_DEPTH_WIDTH -1:0] wr_ptr;
reg [FIFO_DEPTH_WIDTH -1:0] rd_ptr;//=========================================================
//-- write_data
//=========================================================
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginfifo_mem[wr_ptr] <= 0;end else beginif (wr_en && !wr_full) beginfifo_mem[wr_ptr] <= write_data;end else beginfifo_mem[wr_ptr] <= fifo_mem[wr_ptr] ;endend
end//=========================================================
//-- wr_ptr
//=========================================================
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginwr_ptr <= 0;end else beginif(!wr_full && wr_en) beginwr_ptr <= wr_ptr + 1'b1;end else beginwr_ptr <= wr_ptr;endend
end//=========================================================
//-- write_data
//=========================================================always @(posedge clk or negedge rst_n) beginif(!rst_n) beginfifo_mem[wr_ptr] <= 'b0;end else beginif(!wr_full && wr_en) beginfifo_mem[wr_ptr] <= write_data;end else beginfifo_mem[wr_ptr] <= fifo_mem[wr_ptr];endend
end//=========================================================
//-- rd_ptr
//=========================================================
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginrd_ptr <= 'b0;end else beginif (!rd_empty && rd_en) beginrd_ptr <= rd_ptr + 1'b1;end else beginrd_ptr <= rd_ptr;endend
end//=========================================================
//-- read_data
//=========================================================
reg [DATA_WIDTH -1:0] read_data_tmp;
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginread_data_tmp <= 'b0;end else beginif (!rd_empty && rd_en) beginread_data_tmp <= fifo_mem[rd_ptr];end else beginread_data_tmp <= read_data_tmp;endend
endassign read_data = read_data_tmp;//=========================================================
//-- fifo_cnt
//=========================================================
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginfifo_cnt <= 'b0;end else begin//--cases:if (wr_en && rd_en) beginfifo_cnt <= fifo_cnt;end else if (wr_en && !rd_en) beginfifo_cnt <= fifo_cnt + 1'b1;end else if (!wr_en && rd_en) beginfifo_cnt <= fifo_cnt - 1'b1;end else beginfifo_cnt <= fifo_cnt;endend
end//=========================================================
//-- wr_full,rd_empty
//=========================================================
assign wr_full = fifo_cnt == FIFO_DEPTH;
assign rd_empty= fifo_cnt == 0;endmodule//==========================================================
//--Author  : colonel
//--Date    : 10-31
//--Module  : sync_fifo_addy_way
//--Function: the sync_fifo logic using addr way
//==========================================================
module sync_fifo_addr_way #(
//==========================< 参数 >=========================parameter DATA_WIDTH = 8,parameter FIFO_DEPTH = 16
) (
//==========================< 端口 >=========================input wire clk,input wire rst_n,input wire wr_en,input wire [DATA_WIDTH -1:0] write_data,output wire wr_full,input wire rd_en,input wire [DATA_WIDTH -1:0] read_data,output wire rd_empty
);
//==========================< 参数 >=========================
localparam DATA_DEPTH = $clog2(DATA_WIDTH);
localparam FIFO_DEPTH_WIDTH = $clog2(FIFO_DEPTH);//==========================< 信号 >=========================
reg [DATA_WIDTH -1:0] fifo_mem[FIFO_DEPTH -1:0];
reg [FIFO_DEPTH_WIDTH :0] wr_ptr;
reg [FIFO_DEPTH_WIDTH :0] rd_ptr;//=========================================================
//-- write_data
//=========================================================
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginfifo_mem[wr_ptr] <= 0;end else beginif (wr_en && !wr_full) beginfifo_mem[wr_ptr] <= write_data;end else beginfifo_mem[wr_ptr] <= fifo_mem[wr_ptr] ;endend
end//=========================================================
//-- wr_ptr
//=========================================================
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginwr_ptr <= 0;end else beginif(!wr_full && wr_en && (wr_ptr[FIFO_DEPTH_WIDTH -1:0] < (FIFO_DEPTH -1))) beginwr_ptr <= wr_ptr + 1'b1;end else if(!wr_full && wr_en && (wr_ptr[FIFO_DEPTH_WIDTH -1:0] == (FIFO_DEPTH -1))) beginwr_ptr[FIFO_DEPTH_WIDTH] <= ~wr_ptr[FIFO_DEPTH_WIDTH];wr_ptr[FIFO_DEPTH_WIDTH -1:0] <= 0;end else beginwr_ptr <= wr_ptr;endend
end//=========================================================
//-- write_data
//=========================================================always @(posedge clk or negedge rst_n) beginif(!rst_n) beginfifo_mem[wr_ptr] <= 'b0;end else beginif(!wr_full && wr_en) beginfifo_mem[wr_ptr] <= write_data;end else beginfifo_mem[wr_ptr] <= fifo_mem[wr_ptr];endend
end//=========================================================
//-- rd_ptr
//=========================================================
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginrd_ptr <= 'b0;end else beginif (!rd_empty && rd_en && (rd_ptr[FIFO_DEPTH_WIDTH -1:0] < (FIFO_DEPTH-1))) beginrd_ptr <= rd_ptr + 1'b1;end else if (!rd_empty && rd_en && (rd_ptr[FIFO_DEPTH_WIDTH -1:0] == (FIFO_DEPTH-1))) beginrd_ptr[FIFO_DEPTH_WIDTH] <= ~rd_ptr[FIFO_DEPTH_WIDTH];rd_ptr[FIFO_DEPTH_WIDTH -1:0] <= 'b0;end else beginrd_ptr <= rd_ptr;endend
end//=========================================================
//-- read_data
//=========================================================
reg [DATA_WIDTH -1:0] read_data_tmp;
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginread_data_tmp <= 'b0;end else beginif (!rd_empty && rd_en) beginread_data_tmp <= fifo_mem[rd_ptr];end else beginread_data_tmp <= read_data_tmp;endend
endassign read_data = read_data_tmp;//=========================================================
//-- wr_full,rd_empty
//=========================================================
assign wr_full = (wr_ptr[FIFO_DEPTH_WIDTH] != rd_ptr[FIFO_DEPTH_WIDTH]) && (wr_ptr[FIFO_DEPTH_WIDTH -1:0] == rd_ptr[FIFO_DEPTH_WIDTH -1:0]);
assign rd_empty= (wr_ptr[FIFO_DEPTH_WIDTH] == rd_ptr[FIFO_DEPTH_WIDTH]) && (wr_ptr[FIFO_DEPTH_WIDTH -1:0] == rd_ptr[FIFO_DEPTH_WIDTH -1:0]);endmodule

以上是同步FIFO的两种判断空满标志的方法

[ref]
1.https://kongsny.com/archives/112/
2.https://blog.csdn.net/qq_41895219/article/details/131932672?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-131932672-blog-139046484.235v43pc_blog_bottom_relevance_base5&spm=1001.2101.3001.4242.1&utm_relevant_index=3

3.https://blog.csdn.net/qq_41895219/article/details/131932672
4.https://cloud.tencent.com/developer/article/2011110
5.https://blog.csdn.net/weixin_50952710/article/details/128204972


http://www.mrgr.cn/news/62748.html

相关文章:

  • 利用Kubernetes原生特性实现简单的灰度发布和蓝绿发布
  • AspectJ without Spring framework
  • Linux_02 Linux常用软件——vi、vim
  • vue+element实战
  • 【GL07】C语言要点
  • IDEA连接数据库报错(javax.net.ssl.SSLHandshakeException: No appropriate protocol )
  • mysql8.0.32升级到8.0.40
  • LeetCode20:有效的括号
  • 支持ANC的头戴式蓝牙耳机,更有小金标认证,QCY H3 Pro体验
  • 10.30
  • AR基础知识:SLAM同时定位和构图
  • 功能强大视频编辑软件 Movavi Video Editor Plus 2024 v24.2.0 中文特别版
  • ValueError: Object arrays cannot be loaded when allow_pickle=False
  • Typora配置GitHub图床--结合PicGo
  • 配置DDNS结合光猫路由器实现外网映射
  • SAP 采购申请的增强(对内容的处理,比如批次)
  • MySQL的使用
  • 亚马逊云免费Amazon CloudFront服务
  • 基于单片机的无线气象仪系统设计(论文+源码)
  • 程序员工作七年,我踩过的那七个坑
  • 好的呼叫中心系统是什么样的
  • Postman中的API安全堡垒:全面安全性测试指南_postman安全测试
  • 命名空间是啥意思
  • 10 大开源无头浏览器推荐:自动化测试、爬虫与 RPA 的强大助手
  • SpringBoot+Shiro权限管理
  • [ 应急响应靶场实战 ] VMware 搭建win server 2012应急响应靶机 攻击者获取服务器权限上传恶意病毒 防守方人员应急响应并溯源