前言:
本专栏旨在记录高频笔面试手撕代码题,以备数字前端秋招,本专栏所有文章提供原理分析、代码及波形,所有代码均经过本人验证。
目录如下:
10.数字IC手撕代码-数据位宽转换器(宽-窄,窄-宽转换)
13.数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)
18.数字IC手撕代码-双端口RAM(dual-port-RAM)
...持续更新
更多手撕代码题可以前往 数字IC手撕代码--题库
目录
题目描述
乐鑫科技考了好几次四倍频了,输入一个clk,要求输出一个clk_out,满足clk_out的频率是clk的四倍。
解决思路
要注意的是,这里实现的是倍频,而不是分频,如果是分频的话很好实现,在笔者前期文章中对任意奇数分频、偶数分频、小数分频都做了具体介绍,但这里的倍频实现起来则需要一些不同于分频的技巧。
这里实现的技巧就是利用组合逻辑的延时来实现倍频。
二倍频
在实现四倍频之前,我们先来实现二倍频。利用一小段组合逻辑clk_out = Q^clk来做延迟,然后Q的改变通过检测clk_out的上升沿来实现。实现代码如下:
代码
module double_f(
input clk ,
input rstn ,
output clk_out
);
reg Q;
reg [4:0] count;
always @(posedge clk_out or negedge rstn)begin
if(!rstn)begin
Q <= 0;
count <= 1'd0;
end
else begin
Q <= ~Q;
count <= count + 1'b1;
end
end
assign clk_out = Q ^ clk;
endmodule
波形
通过计数器count可以看见,计数器每在clk_out的上升沿,计数器加1,从波形中我们可以看到计数器在每个clk的周期里,都加了两次1,也就是说clk_out的频率是clk频率的两倍,由此我们就实现了二倍频,只不过是占空比极小的二倍频。
实现原理
初始化Q为0,在clk为低时, Q=0,clk=0所以,clk_out=Q^clk=0。在clk上升沿到来的瞬间,clk=1,Q=0,所以得到clk_out=Q^clk=1。从而clk_out的时钟上升沿到来,clk_out的时钟上升沿到来后,触发always语句块后的敏感事件列表,使得Q = ~Q;所以Q=1。右因为Q =1,clk=1,所以clk_out = Q ^clk=0,相当于产生了一个极小的脉冲。
打开modelsim中仿真时间片的模式后,可以看见,clk上升沿时刻的数据变化和我们所描述分析的一致。下降沿同理。
四倍频
在理解了二倍频的实现原理(利用组合逻辑延时,配合always敏感事件列表创造极短脉冲)后,四倍频的实现也是同样的道理。
只不过再二倍频的基础上, 再次二倍时,使用的参考时钟为第一次二倍频得到的时钟,譬如第一次二倍频产生的二倍时钟令其为clk_out_temp1,那么第二次做二倍频的时候,以clk_out_temp1为参考时钟,令clk_out_temp2 = Q1 ^ clk_out_temp1;
代码
module four_f(
input clk ,
input rstn ,
output clk_out
);
wire clk_out_temp1,clk_out_temp2;
reg Q,Q1;
reg [4:0] count;
always@(posedge clk_out_temp1 or negedge rstn)begin
if(!rstn)begin
Q <= 0;
end
else begin
Q <= ~Q;
end
end
assign clk_out_temp1 = Q^clk;
always@(posedge clk_out_temp2 or negedge rstn)begin
if(!rstn)begin
Q1 <= 0;
end
else begin
Q1 <= ~Q1;
end
end
assign clk_out_temp2 = Q1^clk_out_temp1;
assign clk_out = clk_out_temp1 ^ clk_out_temp2;
always @(posedge clk_out or negedge rstn)begin
if(!rstn)begin
count <= 1'd0;
end
else begin
count <= count + 1'b1;
end
end
endmodule
testbench
module test_four();
reg rstn,clk;
wire clk_out;
initial begin
rstn <= 1;
clk <= 0;
#5
rstn <= 0;
#20
rstn <= 1;
end
always #5 clk = ~clk;
four_f u_four_f(
.clk (clk) ,
.rstn (rstn) ,
.clk_out(clk_out)
);
endmodule
波形
如何判断我们代码是否实现了二倍频呢?利用一个计数器,每在最终生成的clk_out时钟上升沿来进行计数器加1。
always @(posedge clk_out or negedge rstn)begin
if(!rstn)begin
count <= 1'd0;
end
else begin
count <= count + 1'b1;
end
end
从波形图可以看到,每一个clk时钟周期里,count计数器都加了4,也就是经历了4个clk_out的时钟上升沿,也即,每一个clk周期,对应四个clk_out周期,也就是实现了我们所说的时钟四倍频,只不过四倍频得到的时钟的占空比非常小(波形图上展示为尖峰脉冲)。
更多手撕代码题可以前往 数字IC手撕代码--题库