校招Verilog——单bit跨时钟域(脉冲展宽法-握手法)
如何处理跨时钟域信号?
单bit:
- (1)双D触发器打拍同步;(只能慢到快)
- (2)脉冲展宽处理(握手法);
多bit:
- (1)格雷码+双D触发器打拍;(不推荐)
- (2)DMUX数据使能选通设计;(FIFO设计)
- (3)异步握手协议;(效率较低)
这里写一下单bit进行跨时钟域的代码设计,如果设计时知道一定是慢时钟域到快时钟域,那么该信号到快时钟域信号打两拍,检测上升沿就行。如果是快时钟域到慢时钟域,或者不知道两边时钟域情况,那么可以用【脉冲展宽处理】,如果脉冲展宽是有限的,就是说拉高的展宽信号后面还得拉低,那就是【握手法】。
基本思想是:
-
快时钟域对脉冲信号进行检测,检测为高电平时输出高电平信号 req。
-
慢时钟域对快时钟域的信号 req 进行延迟打拍采样。因为此时的脉冲信号被快时钟域保持拉高状态,延迟打拍肯定会采集到该信号。
-
慢时钟域确认采样得到高电平信号 req_r1 后,拉高反馈信号 ack 再反馈给快时钟域。
-
快时钟域对反馈信号进行延迟打拍采样得到 ack_r1。如果检测到反馈信号为高电平,证明慢时钟域已经接收到有效的高电平信号,信号恢复原来状态。
1、代码设计
module Sync_Pulse
(
input clka,
input clkb,
input rst_n,
input bit_a,
output bit_b
);
//--------------------------------------------------------
reg req_a;
reg ack_a;
reg ack_a_r1;
reg req_b;
reg req_b_r1;
//--------------------------------------------------------
//-- a时钟域生成展宽信号
//--------------------------------------------------------
always @(posedge clka or negedge rst_n)begin
if(!rst_n)begin
req_a <= 1'b0;
end
else if(bit_a) begin //检测到脉冲
req_a <= 1'b1; //进行展宽
end
else if(ack_a_r1) begin //同步到b时钟域后得到应答
req_a <= 1'b0; //展宽使命完成
end
end
//--------------------------------------------------------
//-- 展宽信号同步到b时钟域
//--------------------------------------------------------
always @(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
req_b <= 1'b0;
req_b_r1 <= 1'b0;
end
else begin
req_b <= req_a;
req_b_r1 <= req_b;
end
end
//--------------------------------------------------------
//-- 展宽信号同步回b时钟域,作为应答
//--------------------------------------------------------
always @(posedge clka or negedge rst_n)begin
if(!rst_n)begin
ack_a <= 1'b0;
ack_a_r1 <= 1'b0;
end
else begin
ack_a <= req_b_r1;
ack_a_r1 <= ack_a;
end
end
//--------------------------------------------------------
//-- 脉冲信号输出,上升沿检测
//--------------------------------------------------------
assign bit_b = ~req_b_r1 & req_b;
endmodule
2、Testbench
`timescale 1ns/1ps //时间精度
`define Clock_a 10 //时钟周期,数字越小时钟越快
`define Clock_b 100 //时钟周期,数字越大时钟越慢
module Sync_Pulse_tb;
//========================< 端口 >==========================================
reg clka ;
reg clkb ;
reg rst_n ;
reg bit_a ;
//==========================================================================
//== 模块例化
//==========================================================================
Sync_Pulse u_Sync_Pulse
(
.clka (clka ),
.clkb (clkb ),
.rst_n (rst_n ),
.bit_a (bit_a ),
.bit_b (bit_b )
);
//==========================================================================
//== 时钟信号和复位信号
//==========================================================================
initial begin
clka = 1;
forever
#(`Clock_a/2) clka = ~clka;
end
initial begin
clkb = 1;
forever
#(`Clock_b/2) clkb = ~clkb;
end
initial begin
rst_n = 0; #(`Clock_a*2+1);
rst_n = 1;
end
//==========================================================================
//== 设计输入信号
//==========================================================================
initial begin
bit_a = 0;
#(`Clock_a*2+1); //初始化完成
bit_a = 1;
#(`Clock_a);
bit_a = 0;
#(`Clock_b*100);
$stop;
end
endmodule
3、波形展示
(1)快 -> 慢
(2)慢 -> 快
这个代码建议背出来,看不太懂就自己仿真看看吧!
如果是多bit跨时钟域处理,可以用参见异步FIFO的代码设计。