题目:
1.使用Verilog语言,将单bit宽度为10ns的data信号由频率为周期为10ns的时钟域同步至周期为7ns的时钟域,(慢到快)。
2.使用Verilog语言,将单bit宽度为14ns的data由频率为周期为7ns的时钟域同步到周期为20ns的时钟域,确保同步后的频率展宽为一个时钟周期。(快到慢)
问题1
单bit宽度10ns,周期10ns,7ns,20ns具体有什么关系?
问题2
从慢的时钟传到快的时钟,反之,会存在什么问题?
目的1
先解决题目1,宽度10ns的data,周期也是10ns,要变成7ns的周期
方案1
按如下图所示的方法来实现,当data出现时,首先由慢时钟clk1采集到,赋给寄存器datar ,然后触发快时钟clk2,赋给datarr[0],用上次学习的非阻塞赋值打两拍同步输出的方法,再写一个非阻塞赋值[0]给[1],就可以实现打两拍同步输出到clk2。
module onebittwoclk(clk1,clk2,rst,data,dataout);
input clk1;//慢的时钟
input clk2;//快的时钟
input rst;
input data;
output dataout;
reg datar;
reg [1:0] datarr;
always @(posedge clk1 or negedge rst)
begin
if(!rst)
datar<=0;
else
datar<=data;
end
always @(posedge clk2 or negedge rst)
begin
if(!rst)
datarr<=2'd0;
else
begin
datarr[0]<=datar;
datarr[1]<=datarr[0];
end
end
assign dataout=datarr[1];
endmodule
`timescale 1ns/1ps
module onebittwoclktest;
reg clk1;//慢的时钟
reg clk2;//快的时钟
reg rst;
reg data;
wire dataout;
onebittwoclk u1(.clk1(clk1), .clk2(clk2), .rst(rst), .data(data), .dataout(dataout));
always #5 clk1=!clk1;
always #3.5 clk2=!clk2;
initial
begin
clk1=0;clk2=0;data=0;rst=1;
#13 rst=0;
#10 rst=1;
#4 data=1;
#10 data=0;
#40 data=1;
#10 data=0;
#50 data=1;
#10 data=0;
#60 data=1;
#10 data=0;
#70 data=1;
#10 data=0;
#30 $stop;
end
endmodule
最后发现问题:有的输出可以正常输出一个快周期,但是有的输出是两个周期,研究过后发现原因可能是:
如果时钟触发的顺序是:慢快快慢,data早就已经消失了,但是慢时钟太慢采不到,快时钟连续两个周期采集到的datar都是1,所以打多了一拍,输出是两个周期。
如果时钟触发的顺序是:慢快慢快,就可以正常输出。
目的2
将14ns宽的data从周期7ns的快时钟同步到周期20ns的慢时钟,且保证输出是一个周期。
由于data比20ns短,可能会出现20ns采不到data的问题。
方案2
为了保证慢时钟一定可以采集到data,在data出现时进行信号展宽成电平信号,也就是让他一直是高电平。可以用异或门,mux多路器来实现。
具体流程:
a.首先是当data到来时,用data和寄存器datar做一个异或获得高电平highlevel,data为1datar为0,highlevel为1。
b.当快时钟clk2上升沿到来时,将highlevel赋给datar,此时highlevel变回0,直到data结束变成0,highlevel从此就一直为1。
c.当慢时钟clk1上升沿到来时,寄存器datar的值打两拍给到datarr[1],和慢变快的时候写的是一样的,所以在下个clk1上升沿时datarr1置1。
d.最后是边沿同步,将输出dataout设置为datarr1和dataedge的异或,在c中由于datarr1置1,所以输出为1,在这里将datarr1的值赋给dataedge,打多一拍,再做个异或,相同出0,输出置为0,保证输出为一个慢周期。
module onebittwoclk(clk1,clk2,rst,data,dataout);
input clk1;//慢的时钟
input clk2;//快的时钟
input rst;
input data;
output dataout;
wire highlevel;//存放高电平,数据和数据寄存器异或
reg datar;
reg [1:0] datarr;
reg dataedge;
assign highlevel= data^datar;
//快时钟采集到高电平后赋给寄存器
always @(posedge clk2 or negedge rst)
begin
if(!rst)
datar<=0;
else
datar<=highlevel;
end
//一样是对目标周期慢周期打两拍输出
always @(posedge clk1 or negedge rst)
begin
if(!rst)
datarr<=2'd0;
else
begin
datarr[0]<=datar;
datarr[1]<=datarr[0];
end
end
//做一个边沿同步并输出
always @(posedge clk1 or negedge rst)
begin
if(!rst)
dataedge<=0;
else
dataedge<=datarr[1];
end
assign dataout=dataedge^datarr[1];
endmodule
`timescale 1ns/1ps
module onebittwoclktest;
reg clk1;//慢的时钟
reg clk2;//快的时钟
reg rst;
reg data;
wire dataout;
onebittwoclk u1(.clk1(clk1), .clk2(clk2), .rst(rst), .data(data), .dataout(dataout));
always #10 clk1=!clk1;
always #3.5 clk2=!clk2;
initial
begin
clk1=0;clk2=0;data=0;rst=1;
#13 rst=0;
#10 rst=1;
#4 data=1;
#7 data=0;
//#40 data=1;
//#7 data=0;
//#50 data=1;
//#7 data=0;
//#60 data=1;
//#7 data=0;
//#70 data=1;
//#7 data=0;
#100 $stop;
end
endmodule
总结
花了整整一天才搞定的题目。主要有以下几个点:
1 从慢到快,快周期必然可以采集到data。但是从快到慢,由于可能存在data的长度都不够慢周期长,采集不到,需要将data进行展宽。
2 展宽的方法是data和寄存器进行异或,保证data出现后可以进入到datar
3 最后的边沿同步也是用异或的方法实现,在下一个慢时钟上升沿的时候做一个datarr1赋值给dataedge后再异或,就可以将输出置为0,保证输出是一个慢时钟周期。
华为树枝江哥的指导
单bit慢到快,直接打拍,两三拍,
单bit快到慢,先展宽再打拍,两三拍
多bit的跨时钟,一般有用握手,格雷码,异步fifo,异步ram的方法
目的就是跨时钟域传输数据,保证你的信号到目标时钟域能被正确采样,不会出现亚稳态,那问题来了,什么是亚稳态
是啊,你都同步到目标时钟了,就要按目标时钟的频率来,不然同步就没意义了