总结:本节重点讨论解决跨时钟域问题,在本节设计实现了异步fifo,在设计实现时不同时钟对同一个寄存器进行了修改,激励仿真通过,最后布局布线不通过。
1、定义
跨时钟域问题是在一个数字系统中使用多个时钟域(时钟信号)时可能面临的挑战。当不同的时钟域存在时,由于时钟的相位差异、频率不同或者时钟边沿的不同步等原因,可能会导致数据在时钟域之间的传输出现问题。
2、代码描述
简单定义一个输入时钟和一个输出时钟模块
`timescale 1ns / 1ps
module CDC(
input clk1,
input clk2,
input rst,
input [7:0] data,
output [7:0] out
);
reg [7:0] reg1;
reg [7:0] reg2;
always @(posedge clk1 or posedge rst)begin
if(rst)
reg1 <= 0;
else
reg1 <= data;
end
always @(posedge clk2 or posedge rst)begin
if(rst)
reg2 <= 0;
else
reg2 <= reg1;
end
assign out = reg2;
endmodule
波形展现
module CDC_tb(
);
// Inputs
reg clk1;
reg clk2;
reg rst;
reg [7:0] data;
// Outputs
wire [7:0] out;
// Instantiate the module
CDC uut (
.clk1(clk1),
.clk2(clk2),
.rst(rst),
.data(data),
.out(out)
);
// Clock generation
initial begin
clk1 = 0;
clk2 = 0;
end
always #5 clk1 = ~clk1;
always #3 clk2 = ~clk2;
// Stimulus
initial begin
// Test case 1
rst = 1;
data = 8'b00000000;
#6 rst = 0;
// Test case 2
data = 8'b10101010;
#12;
// Test case 3
data = 8'b11001100;
#18;
// Test case 4
data = 8'b11001111;
#24;
// Test case 5
data = 8'b10101010;
#30;
// Test case 6
data = 8'b11001111;
$finish;
end
endmodule
可以看到的输入时钟频率慢(10ns),输出时钟频率快(6ns),最终得到的数据不稳定,没有按照输出时钟周期固定输出。其中输出第一个aa值时在一个周期内输出,而cc则在4个始终周期内输出,这样不确定始终周期的读数会导致最终结果的异常,我们希望的是一个时钟周期输出一个值。
3、使用异步FIFO解决
设计实现一个简单的FIFO,输入与输出时钟不同,包括简单的FIFO空满验证,空满验证的方式采用计数器,即,计数器等于0为空,计数器为fifo深度为满。
`timescale 1ns / 1ps
module Asyn_fifo(
input clk1,
input clk2,
input rst,
input [7:0] data,
output [7:0] out
);
reg [7:0] fifo [0:15];
reg [3:0] read_ptr, write_ptr;
reg [3:0] count;
reg [7:0] out_reg;
always @(posedge clk1 or posedge rst) begin
if(rst) begin
read_ptr <= 4'b0;
write_ptr <= 4'b0;
count <= 4'b0;
end
else if(count < 16) begin
fifo[read_ptr] <= data;
read_ptr <= read_ptr + 1;
count <= count + 1;
end
else begin
read_ptr <= read_ptr;
count <= count;
end
end
always @(posedge clk2 or posedge rst)begin
if(rst) begin
read_ptr <= 4'b0;
write_ptr <= 4'b0;
count <= 4'b0;
end
else if(count > 0) begin
out_reg <= fifo[write_ptr] ;
write_ptr <= write_ptr + 1;
count <= count - 1;
end
else begin
write_ptr <= write_ptr;
count <= count;
end
end
assign out = out_reg;
endmodule
在上面设计中出现了问题,问题为在两个不同时钟中对同一个寄存器进行了写,其中的count用于判断当前fifo的空满,所以在两个模块中都进行了修改,在读模块(输入数据)中进行加,写模块(输出数据)中进行减,最终testbench是正常的,但是布局布线是异常的,这是由于最终实现过程count由触发器实现,触发器是边沿触发,只能在一个时钟边沿触发,在综合时会查看到两个count寄存器,寄存器中包含触发器。
测试代码如下
`timescale 1ns / 1ps
module Asyn_fifo_tb(
);
// Inputs
reg clk1;
reg clk2;
reg rst;
reg [7:0] data;
// Outputs
wire [7:0] out;
// Instantiate the module
Asyn_fifo uut (
.clk1(clk1),
.clk2(clk2),
.rst(rst),
.data(data),
.out(out)
);
// Clock generation
initial begin
clk1 = 0;
clk2 = 0;
end
always #5 clk1 = ~clk1;
always #6 clk2 = ~clk2;
// Stimulus
initial begin
// Test case 1
rst = 1;
data = 8'b00000000;
#10
rst = 0;
// Test case 2
data = 8'b10101010;
#10;
// Test case 3
data = 8'b11001100;
#10;
// Test case 4
data = 8'b11001111;
#10;
// Test case 5
data = 8'b10101010;
#10;
// Test case 6
data = 8'b11001111;
# 100
$finish;
end
endmodule
在测试代码中输入的时钟频率要比输出时钟频率更快,输入时钟周期为10ns,输出时钟周期为12ns,数据就会缓存在FIFO中,测试使用的FIFO的深度比较小,当一次传输的数据量大时,要么采用更深的FIFO,要么就采用握手,当FIFO满时,就要限制输入数据。
测试波形
可以看到输出数据是比较稳定与正常的。
4、RTL实现
RTL实现的比较容易理解,reg1模块按照clk1存储输入数据,reg2模块按照clk2从右边
5、综合
综合存在问题,原本的一个count最终被综合为两个触发器,与原本设计是不相符合的。
6、实现
实现失败