在数字IC设计领域,时钟分频电路是面试官最常考察的基础知识点之一。无论是校招还是社招,奇数分频器的设计几乎成为"手撕代码"环节的必考题。本文将带你从最基础的三分频入手,逐步深入七分频、九分频的实现,并特别解析面试中可能遇到的"非常规占空比"问题。
时钟分频的本质是通过计数器对原时钟周期进行计数,在特定计数值时翻转输出时钟信号。对于奇数分频(N分频,N为奇数),关键在于如何实现接近50%的占空比。
最简单的奇数分频可以通过以下步骤实现:
例如,一个三分频器(N=3)的Verilog实现可能如下:
verilog复制module divider_3 (
input clk,
input rst_n,
output reg clk_out
);
reg [1:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 2'b0;
clk_out <= 1'b0;
end
else begin
if (cnt == 2'd2)
cnt <= 2'd0;
else
cnt <= cnt + 1'b1;
clk_out <= (cnt < 2'd1) ? 1'b1 : 1'b0;
end
end
endmodule
这种实现方式会产生占空比为1:2的时钟信号,即高电平持续1个原时钟周期,低电平持续2个原时钟周期。
要实现50%占空比的奇数分频,需要采用"双沿采样"技术:
以三分频为例,波形关系如下:
| 信号类型 | 波形特征 |
|---|---|
| 上升沿采样信号 | 高电平在cnt=0时有效 |
| 下降沿采样信号 | 高电平在cnt=1时有效 |
| 最终输出信号 | 两个采样信号的逻辑或 |
对应的Verilog代码需要增加下降沿采样部分:
verilog复制module divider_3_50 (
input clk,
input rst_n,
output clk_out
);
reg [1:0] cnt;
reg clk_p, clk_n;
// 上升沿采样部分
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 2'b0;
clk_p <= 1'b0;
end
else begin
if (cnt == 2'd2)
cnt <= 2'd0;
else
cnt <= cnt + 1'b1;
clk_p <= (cnt < 2'd1) ? 1'b1 : 1'b0;
end
end
// 下降沿采样部分
always @(negedge clk or negedge rst_n) begin
if (!rst_n)
clk_n <= 1'b0;
else
clk_n <= (cnt < 2'd1) ? 1'b1 : 1'b0;
end
assign clk_out = clk_p | clk_n;
endmodule
掌握了三分频的原理后,我们可以将其推广到任意奇数分频。下面以七分频为例,展示如何设计一个通用的奇数分频器。
七分频器需要计数器计数到6(因为0-6共7个状态),并在适当的时候改变输出状态。50%占空比的七分频器需要高电平持续3.5个原时钟周期,这需要通过双沿采样技术实现。
verilog复制module divider_7_50 (
input clk,
input rst_n,
output clk_out
);
reg [2:0] cnt;
reg clk_p, clk_n;
// 上升沿采样部分
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 3'b0;
clk_p <= 1'b0;
end
else begin
if (cnt == 3'd6)
cnt <= 3'b0;
else
cnt <= cnt + 1'b1;
clk_p <= (cnt < 3'd3) ? 1'b1 : 1'b0;
end
end
// 下降沿采样部分
always @(negedge clk or negedge rst_n) begin
if (!rst_n)
clk_n <= 1'b0;
else
clk_n <= (cnt < 3'd3) ? 1'b1 : 1'b0;
end
assign clk_out = clk_p | clk_n;
endmodule
为了提高代码的复用性,我们可以设计一个参数化的奇数分频器模块:
verilog复制module odd_divider #(
parameter N = 3 // 分频系数,必须为奇数
)(
input clk,
input rst_n,
output clk_out
);
localparam CNT_WIDTH = $clog2(N);
reg [CNT_WIDTH-1:0] cnt;
reg clk_p, clk_n;
// 上升沿采样部分
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {CNT_WIDTH{1'b0}};
clk_p <= 1'b0;
end
else begin
if (cnt == N-1)
cnt <= {CNT_WIDTH{1'b0}};
else
cnt <= cnt + 1'b1;
clk_p <= (cnt < (N-1)/2) ? 1'b1 : 1'b0;
end
end
// 下降沿采样部分
always @(negedge clk or negedge rst_n) begin
if (!rst_n)
clk_n <= 1'b0;
else
clk_n <= (cnt < (N-1)/2) ? 1'b1 : 1'b0;
end
assign clk_out = clk_p | clk_n;
endmodule
使用该模块时,只需实例化并传入所需的分频系数:
verilog复制// 三分频实例
odd_divider #(.N(3)) div3 (
.clk(clk),
.rst_n(rst_n),
.clk_out(clk_out_3)
);
// 七分频实例
odd_divider #(.N(7)) div7 (
.clk(clk),
.rst_n(rst_n),
.clk_out(clk_out_7)
);
面试中,面试官可能会提出更复杂的要求,例如实现特定占空比的奇数分频器。这类问题的解决思路与50%占空比类似,但需要使用不同的逻辑运算。
以5/18占空比的九分频为例,我们需要:
verilog复制module divider_9_5_18 (
input clk,
input rst_n,
output clk_out
);
reg [3:0] cnt;
reg clk_p, clk_n;
// 上升沿采样部分
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 4'b0;
clk_p <= 1'b0;
end
else begin
if (cnt == 4'd8)
cnt <= 4'b0;
else
cnt <= cnt + 1'b1;
clk_p <= (cnt < 4'd3) ? 1'b1 : 1'b0;
end
end
// 下降沿采样部分
always @(negedge clk or negedge rst_n) begin
if (!rst_n)
clk_n <= 1'b0;
else
clk_n <= (cnt < 4'd3) ? 1'b1 : 1'b0;
end
assign clk_out = clk_p & clk_n;
endmodule
对于任意占空比的奇数分频器,可以总结出以下设计步骤:
例如,要实现3/10占空比的五分频:
在数字IC设计的面试中,除了要求写出分频器代码外,面试官通常会追问一些相关问题。以下是一些常见问题及回答建议:
如何验证分频器的正确性?
分频器设计中的亚稳态问题如何解决?
分频器的jitter问题如何优化?
在实际面试中,写出功能正确的代码只是第一步,面试官更看重代码的质量和可维护性。以下是一些优化建议:
以下是一个完整的testbench示例,可用于验证三分频器:
verilog复制module tb_divider_3;
reg clk;
reg rst_n;
wire clk_out;
divider_3_50 uut (
.clk(clk),
.rst_n(rst_n),
.clk_out(clk_out)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试流程
initial begin
rst_n = 0;
#20 rst_n = 1;
#200 $finish;
end
// 波形记录
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb_divider_3);
end
endmodule