本系列旨在提供100%准确的数字IC设计/验证手撕代码环节的题目,原理,RTL设计,Testbench和参考仿真波形,每篇文章的内容都经过仿真核对。快速导航链接如下:
奇数分频
偶数分频
半整数分批
小数/分数分频
序列检测器
模三检测器
饮料机
异步复位,同步释放
边沿检测(上升沿,下降沿,双边沿)
全加器,半加器
格雷码转二进制
单bit跨时钟域(打两拍,边沿同步,脉冲同步)
奇偶校验
伪随机数生成器[线性反馈移位寄存器]
同步FIFO
应当说,手撕代码环节是面试流程中既重要又简单的一个环节,跟软件类的岗位相比起来,数字IC的手撕代码题目固定,数量有限,属于整个面试中必得分的一个环节,在这个系列以外,笔者同样推荐数字IC求职者使用“HdlBits”进行代码的训练
链接如下
HDLBits — Verilog Practice
1.使用Verilog语言,将单bit宽度为10ns的data信号由频率为周期为10ns的时钟域同步至周期为7ns的时钟域,(慢到快)。
2.使用Verilog语言,将单bit宽度为7ns的data由频率为周期为7ns的时钟域同步到周期为20ns的时钟域,确保同步后的频率展宽为一个时钟周期。(快到慢)
这个题目存在三个关键点
- 第一个关键点,怎么进行单bit的跨时钟域?,
- 第二个关键点,同步前后时钟域的快慢关系
- 第三个关键点,如何保证同步后的信号展宽为脉冲信号?
先说第一个问题
怎么进行跨时钟域的处理? 答案是打两拍进行同步
再说第二个问题
同步前后时钟域的快慢延伸出了两种情况
1.前快后慢
2.前慢后快
对于将信号从慢时钟域同步到快时钟域的情况来说,以题目1为例,10ns的data宽度确保了它一定能被7ns的时钟域上升沿采样到值
但是对于将信号从“快时钟域同步到慢时钟域”对于题目2来说,7ns的data宽度有可能正好处在周期为20ns的信号的两个上升沿之间,这就造成了信号有可能采空的情况出现。
那么我们怎么样才能将信号从快时钟域同步到慢时钟域呢?
答案是:将一个脉冲信号展宽为电平信号,让 一个很短的脉冲信号变成一个很长的电平信号,那么不就一定能被快时钟域采到了吗?
为了这个目的,我们在慢时钟域重新通过组合逻辑设计寄存器外围电路,这里我们使用异或门来进行展宽,电路结构如下
当然我们也可以选用mux来进行展宽,这里不再赘述。
这里的命名与后面题目二的RTL的代码是一一对应的,大家可以参考着看
单独为这个电路结构跑了个仿真,可以发现,一个短的脉冲信号,在这个电路结构下,变成了一个长的电平信号。那么这个情况下,慢时钟域无论如何都应该能采样到脉冲信号了
最后说第三个问题,如何确保最后输出的信号是脉冲信号?
我们在第二个问题中将脉冲信号展宽为电平信号,这个信号肯定能被输送到慢时钟域去了,但是我们要怎么保证慢时钟域输出的信号是一个脉冲而非一个一直为高的电平信号呢?
答案:边沿检测(上升沿,下降沿,双边沿)
参考另一篇文章的内容,通过边沿检测,我们就可以得到脉冲信号,而非电平信号了。
什么是电平同步,什么是脉冲同步,什么是边沿同步
电平同步就是正常的打两拍
边沿同步是在正常的打两拍的基础上,clkb时钟域用边沿检测电路处理一下output信号,让这个信号保证为一个周期
脉冲同步是指在clka时钟域将脉冲信号处理成电平信号,再进行打两拍,再重复边沿同步的边沿检测电路的操作。
module one_bit_cdc(data,clka,clkb,rst_n,data_out);
input data;
input clka;
input clkb;
input rst_n;
output data_out;
reg data_r;
reg [1:0] data_b_r;
always@(posedge clka or negedge rst_n)
begin
if(!rst_n)
data_r <= 1'b0;
else
data_r <= data;
end
always@(posedge clkb or negedge rst_n)
if(!rst_n)
data_b_r <=2'b00;
else
begin
data_b_r[0] <= data_r;
data_b_r[1] <= data_b_r[0];
end
assign data_out = data_b_r[1];
endmodule
`timescale 1ns/1ps
module one_bit_cdc_tb();
parameter a = 5;
parameter b = 3.5;
reg clka;
reg clkb;
reg data;
reg rst_n;
wire data_out;
one_bit_cdc u1(.data(data),.clka(clka),.clkb(clkb),.rst_n(rst_n),.data_out(data_out));
always #(a) clka = !clka;
always #(b) clkb = !clkb;
initial
begin
clka = 0;
clkb = 0;
rst_n = 1;
data = 0;
#14
rst_n = 0;
#35
rst_n = 1;
#20
data = 1;
#10
data = 0;
#400
$stop;
end
endmodule
可以发现,信号顺利的从慢时钟域同步到了快时钟域,设计成立。
module one_bit_cdc(data,clka,clkb,rst_n,data_out);
input data;
input clka;
input clkb;
input rst_n;
output data_out;
reg data_r;
wire data_mux_r;
reg [1:0] data_b_r;
reg data_b_edge_r;
assign data_mux_r = data ^ data_r ;
always@(posedge clka or negedge rst_n)
begin
if(!rst_n)
data_r <= 1'b0;
else
data_r <= data_mux_r;
end
always@(posedge clkb or negedge rst_n)
if(!rst_n)
data_b_r <=2'b00;
else
begin
data_b_r[0] <= data_mux_r;
data_b_r[1] <= data_b_r[0];
end
always@(posedge clkb or negedge rst_n)
if(!rst_n)
data_b_edge_r <= 1'b0;
else
data_b_edge_r <= data_b_r[1];
assign data_out = data_b_edge_r ^ data_b_r[1];
endmodule
`timescale 1ns/1ps
module one_bit_cdc_tb();
parameter a = 3.5;
parameter b = 10;
reg clka;
reg clkb;
reg data;
reg rst_n;
wire data_out;
one_bit_cdc u1(.data(data),.clka(clka),.clkb(clkb),.rst_n(rst_n),.data_out(data_out));
always #(a) clka = !clka;
always #(b) clkb = !clkb;
initial
begin
clka = 0;
clkb = 0;
rst_n = 1;
data = 0;
#14
rst_n = 0;
#35
rst_n = 1;
#20
data = 1;
#(2*a)
data = 0;
#400
$stop;
end
可以发现,信号顺利的从快时钟域同步到了慢时钟域,设计成立。