赞
踩
目录
3.1.1慢时钟———快时钟。(满足三边沿准则,有效事件可以被安全采样)
3.1.2慢时钟———快时钟。(不满足三边沿准则,有效事件可以被安全采样)
3.2.1有效事件传输背景下确保有效事件的数量定义一致。(如何确保跨时钟前后单电平对应单事件?)
异步时钟信号直接传输在信号跳变时违背本地时钟域的时序要求(建立时间约束,保持时间约束),容易产生亚稳态,无法确定亚稳态何时结束以及结束时保持在何种状态上。
接收时钟的频率是发送时钟的1.5倍以上,跨时钟信号的最小持续时间必然跨越接受时钟说的三个相邻时钟边沿,经过同步器该有效事件可以被安全传输。
接收时钟的频率小于发送时钟的1.5倍,将有效事件的持续时间拉长满足三边沿准则,然后再经过同步器传输。
- always@(posedge clk_a or negedge arst)//(D触发器)(打拍可拉长持续时间)
- if(!arst)
- data_reg <=0;
- else
- data_reg <=data_in;
-
-
- always@(posedge clk_b or negedge brst)//同步器(时钟域为将要同步的时钟域)(两个连续的D触发器)
- if(!brst)
- begin
- data_breg <=0;
- data_breg2<=0;
- end
- else
- begin
- data_breg <=data_reg;
- data_breg2<=data_breg2;
- end
对一个持续电平的上升沿或者下降沿进行检测,并将检测后的电平作为有效事件,就可以做到无论跨时钟输出电平持续多少个cycle,有效检测电平只持续一个cycle,边沿检测常适用于慢时钟到快时钟。
要实现边沿检测,最直接的想法是用两级寄存器,第二级寄存器锁存住某个时钟上升沿到来时的输入电平,第一级寄存器锁存住下一个时钟沿到来时的输入电平,如果这两个寄存器锁存住的电平信号不同,就说明检测到了边沿,具体是上升沿还是下降沿可以通过组合逻辑来实现。如下图所示:(思想:延迟一个寄存器)
- //边沿检测电路
- //2014/12/10
- module edge_cap
- (
- input clk, rst_n,
- input pulse,
-
- output pos_edge,
- output neg_edge
-
- );
- reg pulse_r1, pulse_r2;
-
- always @ (posedge clk or negedge rst_n)
- if(!rst_n)
- begin
- pulse_r1 <= 1'b0;
- pulse_r2 <= 1'b0;
- end
- else
- begin
- pulse_r1 <= pulse;
- pulse_r2 <= pulse_r1;
- end
-
- assign pos_edge = (pulse_r1 && ~pulse_r2) ?1:0;
- assign neg_edge = (~pulse_r1 && pulse_r2) ?1:0;
-
- endmodule
异步信号同步化
,一般采用多加一级寄存器的方法来减小亚稳态的发生概率,如下图所示:
- //异步信号边沿检测电路,三级寄存器实现
- //2014/12/08
-
- module edge_cap
- (
- input clk, rst_n,
- input pulse,
-
- output pos_edge,
- output neg_edge
-
- );
- reg pulse_r1, pulse_r2, pulse_r3;
-
- always @ (posedge clk or negedge rst_n)
- if(!rst_n)
- begin
- pulse_r1 <= 1'b0;
- pulse_r2 <= 1'b0;
- pulse_r3 <= 1'b0;
- end
- else
- begin
- pulse_r1 <= pulse;
- pulse_r2 <= pulse_r1;
- pulse_r3 <= pulse_r2;
- end
-
- assign pos_edge = (pulse_r2 && ~pulse_r3) ?1:0;
- assign neg_edge = (~pulse_r2 && pulse_r3) ?1:0;
-
-
- endmodule
对于快时钟域单电平脉冲信号跨时钟到慢时钟域常使用脉冲同步器电路,脉冲同步器在异步时钟域时钟频率彼此差距较大的场景下能节省触发器资源。快时钟域脉冲持续时间无法满足三边沿准则,需要通过翻转电路拉长脉冲电平以保证有效事件被采样,在接收时钟通过边沿检测回复原单电平脉冲。
- `timescale 1ns/1ns
-
- module pulse_detect(
- input clk_fast ,
- input clk_slow ,
- input rst_n ,
- input data_in ,
-
- output dataout
- );
-
- reg data_level,data_level1,data_level2,data_level3;//翻转电路
- always @(posedge clk_fast or negedge rst_n)
- if(!rst_n)
- data_level<=0;
- else
- data_level<= (data_in)? ~data_level : data_level;
-
- always @(posedge clk_slow or negedge rst_n)//同步器
- if(!rst_n)
- begin
- data_level1<=0;
- data_level2<=0;
- end
- else
- begin
- data_level1<=data_level;
- data_level2<=data_level1;
- end
-
- always @(posedge clk_slow or negedge rst_n)// D触发器
- if(!rst_n)
- data_level3<=0;
- else
- data_level3<=data_level2;
-
- assign dataout= data_level2^data_level3;
-
-
- endmodule
现有多个连续的有效事件需要进行跨时钟,单个事件的发起时刻是可控的,这时建议使用反馈机制保证各有效事件跨时钟传输的安全性。
4.1FIFO定义
FIFO是英文First In First Out的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
1)对连续的数据流]进行缓存,防止在进机和存储操作时丢失数据;
2)数据集中起来进行进栈和存储,可避免频繁的总线操作,减轻CPU的负担;
3)允许系统进行DMA操作,提高数据的传输速度。这是至关重要的一点,如果不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作。
同步 FIFO 常用于同步时钟的数据缓存,异步 FIFO 常用于跨时钟域的数据信号的传递,例如时钟域 A 下的数据 data1 传递给异步时钟域 B,当 data1 为连续变化信号时,如果直接传递给时钟域 B 则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中。
FIFO 的宽度:FIFO 一次读写操作的数据位 N;
FIFO 的深度:FIFO 可以存储多少个宽度为 N 位的数据。
空标志:对于双时钟 FIFO 又分为读空标志 rdempty 和写空标志 wrempty。FIFO 已空或将要空时由 FIFO
的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出。
满标志:对于双时钟 FIFO 又分为读满标志 rdfull 和写满标志 wrfull。FIFO 已满或将要写满时由 FIFO
的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出。
读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
写时钟:写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
- `timescale 1ns/1ns
- /**********************************RAM************************************/
- module dual_port_RAM #(parameter DEPTH = 16,
- parameter WIDTH = 8)(
- input wclk
- ,input wenc
- ,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
- ,input [WIDTH-1:0] wdata //数据写入
- ,input rclk
- ,input renc
- ,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
- ,output reg [WIDTH-1:0] rdata //数据输出
- );
-
- reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
-
- always @(posedge wclk) begin
- if(wenc)
- RAM_MEM[waddr] <= wdata;
- end
-
- always @(posedge rclk) begin
- if(renc)
- rdata <= RAM_MEM[raddr];
- end
-
- endmodule
-
- /**********************************SFIFO************************************/
- module sfifo#(
- parameter WIDTH = 8,
- parameter DEPTH = 16
- )(
- input clk ,
- input rst_n ,
- input winc ,
- input rinc ,
- input [WIDTH-1:0] wdata ,
-
- output reg wfull ,
- output reg rempty ,
- output wire [WIDTH-1:0] rdata
- );
-
-
- reg [$clog2(DEPTH):0] waddr,raddr;
-
- dual_port_RAM #(.DEPTH (DEPTH),
- .WIDTH (WIDTH))
- dual_port_RAM (
- .wclk (clk ),
- .wenc (wenc ),
- .waddr (waddr),
- .wdata (wdata),
- .rclk (clk ),
- .renc (renc ),
- .raddr (raddr),
- .rdata (rdata)
- );
-
-
-
- always @(posedge clk or negedge rst_n)//读数据计数加一
- if(!rst_n)
- raddr<=0;
- else if(renc)
- raddr<=raddr+1'd1;
- else
- raddr<=raddr;
- always @(posedge clk or negedge rst_n)//写数据计数加一
- if(!rst_n)
- waddr<=0;
- else if(wenc)
- waddr<=waddr+1'd1;
- else
- waddr<=waddr;
-
-
- always @(posedge clk or negedge rst_n) //判断空满状态
- if(!rst_n)begin
- wfull<=0;
- rempty<=0;
- end
-
- else begin
- wfull<=(waddr==raddr+DEPTH);
- rempty<=(waddr==raddr);
- end
-
- assign wenc = winc && !wfull;
- assign renc = rinc && !rempty;
-
- endmodule
-
-
1. 双口RAM,用于数据的存储。(RAM中的指针的地址取FIFO中的地址的除地址第一位外的地址,因为FIFO中的第一位是标志位)
2. 数据写入控制器,在wenc信号的使能下,数据写入控制器使RAM读入数据,同时数据写地址指针加一。
3. 数据读取控制器,在renc信号的使能下,数据读出控制器使RAM读出数据,同时数据读地址指针加一。
4. 读指针同步器:使用写时钟的两级触发器采集读指针,输出到数据写入控制器。
5. 写指针同步器: 使用读时钟的两级触发器采集写指针,输出到数据读取控制器。
本题解采用的空满判断的方式是用格雷码的比较来产生空满信号,同时产生的空满信号,会与输入的winc,rinc输入的是能信号,共同控制数据是否写入和读出。
- `timescale 1ns/1ns
-
- /***************************************RAM*****************************************/
- module dual_port_RAM #(parameter DEPTH = 16,
- parameter WIDTH = 8)(
- input wclk
- ,input wenc
- ,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
- ,input [WIDTH-1:0] wdata //数据写入
- ,input rclk
- ,input renc
- ,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
- ,output reg [WIDTH-1:0] rdata //数据输出
- );
-
- reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
-
- always @(posedge wclk) begin
- if(wenc)
- RAM_MEM[waddr] <= wdata;
- end
-
- always @(posedge rclk) begin
- if(renc)
- rdata <= RAM_MEM[raddr];
- end
-
- endmodule
-
- /***************************************AFIFO*****************************************/
- module asyn_fifo#(
- parameter WIDTH = 8,
- parameter DEPTH = 16
- )(
- input wclk ,
- input rclk ,
- input wrstn ,
- input rrstn ,
- input winc ,
- input rinc ,
- input [WIDTH-1:0] wdata ,
-
- output wire wfull ,
- output wire rempty ,
- output wire [WIDTH-1:0] rdata
- );
-
-
- /**********************addr bin gen*************************/
- //RAM读写地址的变化
- reg [ADDR_WIDTH:0] waddr_bin;
- reg [ADDR_WIDTH:0] raddr_bin;
-
- parameter ADDR_WIDTH = $clog2(DEPTH);
-
- always@(posedge rclk or negedge rrstn)//counter read
- if(!rrstn)
- raddr_bin<=0;
- else if(renc)
- raddr_bin<=raddr_bin+1'd1;
- else
- raddr_bin<=raddr_bin;
- always@(posedge wclk or negedge wrstn)//counter write
- if(!wrstn)
- waddr_bin<=0;
- else if(wenc)
- waddr_bin<=waddr_bin+1'd1;
- else
- waddr_bin<=waddr_bin;
-
- assign wenc = winc && !wfull;
- assign renc = rinc && !rempty;
-
- /**********************addr gray gen*************************/
- //二进制转换为格雷码
- wire [ADDR_WIDTH:0] waddr_gray;
- wire [ADDR_WIDTH:0] raddr_gray;
- reg [ADDR_WIDTH:0] wptr;
- reg [ADDR_WIDTH:0] rptr;
-
- assign waddr_gray = waddr_bin ^ (waddr_bin>>1);//二进制转换为格雷码
- assign raddr_gray = raddr_bin ^ (raddr_bin>>1);//二进制转换为格雷码
-
- always @(posedge rclk or negedge rrstn)//打一拍避免冲突与竞争
- if(!rrstn)
- rptr<=0;
- else
- rptr<=raddr_gray;
-
- always @(posedge wclk or negedge wrstn)//打一拍避免冲突与竞争
- if(!wrstn)
- wptr<=0;
- else
- wptr<=waddr_gray;
-
-
- /**********************syn addr gray*************************/
- //同步器打两拍
- reg [ADDR_WIDTH:0] wptr_buff;
- reg [ADDR_WIDTH:0] wptr_syn;
- reg [ADDR_WIDTH:0] rptr_buff;
- reg [ADDR_WIDTH:0] rptr_syn;
-
-
- always@(posedge rclk or negedge rrstn)
- //同步器:格雷码写地址同步至读时钟域
- if(!rrstn)
- begin
- wptr_buff<=0;
- wptr_syn<=0;
- end
- else
- begin
- wptr_buff<=wptr;
- wptr_syn<=wptr_buff;
- end
-
- always@(posedge wclk or negedge wrstn)// 格雷码读地址同步至写时钟域
- if(!wrstn)
- begin
- rptr_buff<=0;
- rptr_syn<=0;
- end
- else
- begin
- rptr_buff<=rptr;
- rptr_syn<=rptr_buff;
- end
-
- /**********************full empty gen*************************/
- //空满状态判断
- assign wfull = (wptr == {~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]});
- assign rempty = (rptr == wptr_syn);
-
-
- /**********************RAM*************************/
-
-
- wire [ADDR_WIDTH-1:0] waddr;
- wire [ADDR_WIDTH-1:0] raddr;
-
- assign waddr = waddr_bin[ADDR_WIDTH-1:0];
- assign raddr = raddr_bin[ADDR_WIDTH-1:0];
-
- dual_port_RAM #(.DEPTH(DEPTH),
- .WIDTH(WIDTH)
- )dual_port_RAM(
- .wclk (wclk),
- .wenc (wenc),
- .waddr(waddr[ADDR_WIDTH-1:0]), //深度对2取对数,得到地址的位宽。
- .wdata(wdata), //数据写入
- .rclk (rclk),
- .renc (renc),
- .raddr(raddr[ADDR_WIDTH-1:0]), //深度对2取对数,得到地址的位宽。
- .rdata(rdata) //数据输出
- );
-
- endmodule
采用格雷码的原因:单bit翻转
每两个相邻编码之间只有一位是不同的,并且对于N位格雷码,当从最高位编码(对应二进制2^N -1)跳转到最低位编码(对应0)时也只会发生1bit跳转。单bit跳转意味着格雷码在通过二级同步器跨时钟时,输出不会出现不可控的中间状态,只能是正确的更新状态或者保持原来的状态。
二进制码与gray码的转换关系
二进制转换成格雷码: assign gray = (bin >>1)^bin;
格雷码转换成二进制码:
bin[N-1] = gray[N-1];
for(i=0;i<(N-1);i+1)begin
bin[i] = ^(gray[N-1:0]>>i);
end
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。