前言
信号的跨时钟传输的方法很多,在上篇专栏中,就说了两种有关单比特脉冲信号的跨时钟域传输问题,FPGA逻辑设计回顾(4)亚稳态与单比特脉冲信号的CDC处理问题,建议大家看看,后面我还会扩展更多的方法。本篇承接上一篇文章,和单比特有点关系,但是是一种处理多比特信号的跨时钟域方法,MUX同步器!一起来看看吧。
多比特信号跨时钟域处理的场景与方案
多比特信号即位宽不为1的数据,对这种信号进行跨时钟域处理时,我们关注的重点就和单比特信号不太一样了,有的时候我们甚至不再关注源时钟与目的时钟之间的快慢,而是如何将数据传输到对面而不会出错。
有的人就说了,既然单比特信号我们可以直接两级寄存器同步,为什么对于多比特信号就不行了呢?原因有很多,因不同的使用用途侧重点也不同,例如最简单的考虑,如果数据位宽很大,那么全部使用寄存器同步,岂不是让电路面积很大?即使基于这个考虑我们也要改进下我们的设计。
还记得上一篇我们讲到的两级寄存器同步方案来解决从慢时钟域到快时钟域内传输单比特脉冲信号的方法吗? 链接如下:FPGA逻辑设计回顾(4)亚稳态与单比特脉冲信号的CDC处理问题
对,我要强调的是我们对每一种方案都有一种名字,这像是读我的文章的一种约定,说到某个方案的名字你就知道我指的是哪个设计。
- 两级寄存器同步,即 two flip-flop synchronizer ;
下面介绍一种对多比特信号的跨时钟域处理方法,我们称之为MUX同步器,英文名叫:Mux synchronizer,它适用的场景理论上也得是让目的时钟域能检测到数据,也就是说要么数据持续时间够长(从快时钟域到慢时钟域),要么是从数据本身在较慢的时钟域内。
- MUX 同步器:Mux synchronizer
如果这么说,在特定条件的限制下,MUX同步器也就是无所谓时钟域的快慢问题了。还有一点限制就是这种设计是单向的数据跨时钟域传输,也就是说,只能从源时钟域传输到目的时钟域,而不是反过来传输数据,这是设计本身决定的,单向设计。
正所谓,如果你选择了这种方式,你就得承担它的局限性呀。人生不如意者,常十之八九!可是这种设计方式也完全可以作为你的武器库(储存库),或者说十八般武器中的一种嘛,有实力才有选择权,多么通透的道理呀。
MUX同步器
MUX同步器这种方式,要求被同步的数据,跟随一个使能信号,如下图类型:
这在特定的场景下是不难实现的,下面具体讲它的实现方式:
我们今天想要跨时域的是图中的Data bus,想要将 data bus 从 clkA 转到 clkB(不论谁的频率快慢都一样),我们通过 data bus 的 valid 信号(属于clkA),也就是图中的 data enable A,将 data enable A 使用 two flip-flop synchronizer 跨到 clkB,也就是 data enable B,并且使用 data enable B当作最右边DFF的 flip-flop enable 信号(在图中使用mux来示意),由于data enable A 的时序等同于 data bus,跨到data enable B 时也就保证了 data bus 穿过 DFF 的信号已经稳定,即可拿来锁入最后一级DFF,最后一级的DFF的Q即是data bus存在于clk B domain的稳定信号。
我们将中间信号标注一下,以便于波形图分析使用:
如上图,我们假设时钟A是慢时钟,我们的数据仅持续一个时钟即可被同步到B时钟域。根据电路,得到的波形图如下:
根据电路框图,我们使用Verilog语言进行描述,然后仿真,看其效果:
module mux_synchronizer(
input wire clka ,
input wire clkb ,
input wire rst ,
input wire [3:0] data_bus ,
input wire data_enable_a ,
output reg data_bus_b
);
reg reg_data_enable_a ;
reg data_enable_b_mid ;
reg data_enable_b ;
reg [3:0] reg1_data_bus_a ;
wire [3:0] data_bus_mux ;
//时钟域a下同步本地数据及其有效标志信号,改善时序
always@(posedge clka or posedge rst) begin
if(rst) begin
reg_data_enable_a <= 1'b0 ;
reg1_data_bus_a <= 4'd0 ;
end
else begin
reg_data_enable_a <= data_enable_a ;
reg1_data_bus_a <= data_bus ;
end
end
//将数据有效标志信号同步到b时钟域,两级同步器
always@(posedge clkb or posedge rst) begin
if(rst) begin
data_enable_b_mid <= 1'b0 ;
data_enable_b <= 1'b0;
end
else begin
data_enable_b_mid <= reg_data_enable_a;
data_enable_b <= data_enable_b_mid;
end
end
//写法1:
assign data_bus_mux = data_enable_b ? reg1_data_bus_a : data_bus_b;
always@(posedge clkb or posedge rst) begin
if(rst) begin
data_bus_b <= 4'b0;
end
else begin
data_bus_b <= data_bus_mux;
end
end
// //写法2:
// always@(posedge clkb or posedge rst) begin
// if(rst) begin
// data_bus_b <= 4'b0;
// end
// else if(data_enable_b) begin
// data_bus_b <= reg1_data_bus_a;
// end
// else begin
// data_bus_b <= data_bus_b;
// end
// end
endmodule
注意到,我们的注释处,有写法2:
//写法2:
always@(posedge clkb or posedge rst) begin
if(rst) begin
data_bus_b <= 4'b0;
end
else if(data_enable_b) begin
data_bus_b <= reg1_data_bus_a;
end
else begin
data_bus_b <= data_bus_b;
end
end
这种描述,和电路图中的MUX写法是一致的:
//写法1:
assign data_bus_mux = data_enable_b ? reg1_data_bus_a : data_bus_b;
always@(posedge clkb or posedge rst) begin
if(rst) begin
data_bus_b <= 4'b0;
end
else begin
data_bus_b <= data_bus_mux;
end
end
其实MUX充当的作用就是触发器的一个使能信号,如果有带有使能的触发器,那就直接使用无疑了。 而Xilinx的FPGA中,就有这样的触发器呀,如下为上述电路的RTL图:
综合后的原理图:
实现后的原理图:
将触发器放大了看:
可见,这类寄存器在FPGA中太常见了。
下面对其功能进行仿真:
首先我们仿真从慢时钟到快时钟的情况,仿真平台如下:
module sim_mux_synchronizer(
);
reg clka ;
reg clkb ;
reg rst ;
reg [3:0] data_bus ;
reg data_enable_a ;
wire [3:0] data_bus_b ;
initial begin
clka = 1'b0;
forever begin
#5 clka = ~clka;
end
end
initial begin
clkb = 1'b0;
forever begin
#2 clkb = ~clkb;
end
end
initial begin
rst = 1'b1;
data_bus = 4'b0;
data_enable_a = 1'b0;
#15
rst = 1'b0;
#20
@(posedge clka) begin
data_bus = #0.5 4'b1101;
data_enable_a = #0.5 1'b1;
end
@(posedge clka) begin
data_bus = #0.5 4'b0;
data_enable_a = #0.5 1'b0;
end
end
mux_synchronizer u_mux_synchronizer(
.clka ( clka ),
.clkb ( clkb ),
.rst ( rst ),
.data_bus ( data_bus ),
.data_enable_a ( data_enable_a ),
.data_bus_b ( data_bus_b )
);
endmodule
由此得到仿真波形:
对比上面我们自己画的:
可见,实现了我们想要的功能。
下面提一个问题:如果是从快时钟跨时钟域到慢时钟域呢?我们不妨简单仿真一下:
假设数据在快时钟域内持续时间大概是慢时钟域的三个时钟那么长,如下稍微修改即可得到仿真平台:
module sim_mux_synchronizer(
);
reg clka ;
reg clkb ;
reg rst ;
reg [3:0] data_bus ;
reg data_enable_a ;
wire [3:0] data_bus_b ;
initial begin
clka = 1'b0;
forever begin
#2 clka = ~clka;
end
end
initial begin
clkb = 1'b0;
forever begin
#5 clkb = ~clkb;
end
end
initial begin
rst = 1'b1;
data_bus = 4'b0;
data_enable_a = 1'b0;
#15
rst = 1'b0;
#20
@(posedge clka) begin
data_bus = #0.5 4'b1101;
data_enable_a = #0.5 1'b1;
end
repeat(7) begin
@(posedge clka);
end
@(posedge clka) begin
data_bus = #0.5 4'b0;
data_enable_a = #0.5 1'b0;
end
end
mux_synchronizer u_mux_synchronizer(
.clka ( clka ),
.clkb ( clkb ),
.rst ( rst ),
.data_bus ( data_bus ),
.data_enable_a ( data_enable_a ),
.data_bus_b ( data_bus_b )
);
endmodule
得到仿真图:
可见也是没问题的。
好了,这篇文章就到这里,更多的多比特信号的CDC问题,我们下篇见!