1 概述
在 FPGA 中,时钟分频是经常用到的。本节课讲解 2 分频、3 分频、4 分频和 8 分频的 Verilog 实现并且学习 generate 语法功能的应。
2 程序设计思路
1)整数倍分频,为 2、4、8,这种 2^n 次方倍数倍数关系的分频最容易实现,所以我们可以把这 3 种分频方式归为一类。
2)3 分频是奇数倍分频,这种分频比较麻烦,对于初学者肯定得思考一番。
3)2HZ 和前文中流水灯的延迟控制方法有一样,只要实现每过 500ms 对寄存器取反操作。
对于这类基础简单的方案,笔者认为,大家学习主要缺少的是思路,所以我们直接拿程序来分析。
Clk_Divider.v
`timescale 1ns / 1ps
module Clk_Divider#
(
parameter DEBUG_ENABLE = 1'b1,
parameter REF_CLK = 32'd100000000
)
(
input clk_i,
input rst_n_i,
output div2_o,
//output div3_o,
output div4_o,
output div8_o,
output div2hz_o
);
//2分频代码:只要基于源时钟每个时钟的上升沿对div2_o_r寄存器取反
reg div2_o_r;
always@(posedge clk_i)begin
if(!rst_n_i)
div2_o_r <= 1'b0;
else
div2_o_r <= ~div2_o_r;
end
//4分频和8分频代码:共同使用了div_cnt1计数器
//4分频就是对计数器在div_cnt1==2'b00或者div_cnt1==2'b10的时候对div4_o_r寄存器取反;
//而8分频是对div_cnt1==2'b00的时候对div8_o_r取反
reg [1:0] div_cnt1;
always@(posedge clk_i)begin
if(!rst_n_i)
div_cnt1 <= 2'b00;
else
div_cnt1 <= div_cnt1+1'b1;
end
reg div4_o_r;
reg div8_o_r;
always@(posedge clk_i)begin
if(!rst_n_i)
div4_o_r <= 1'b0;
else if(div_cnt1==2'b00 || div_cnt1==2'b10)
div4_o_r <= ~div4_o_r;
else
div4_o_r <= div4_o_r;
end
always@(posedge clk_i)begin
if(!rst_n_i)
div8_o_r <= 1'b0;
else if(div_cnt1==2'b00)
div8_o_r <= ~div8_o_r;
else
div8_o_r <= div8_o_r;
end
/*
3分频的本质是我们需要在每次1.5倍的时钟周期的时候实现3分频寄存器的翻转,但是我们无法直接实现1.5倍的分频。
因此采取分别采取2个计数器pos_cnt和neg_cnt,分别对上升沿和下降沿计数。计数周期是0-1-2,共计3个时钟周期。
我们取pos_cnt == 2'd1的时候div3_o_r0输出高电平,neg_cnt == 2'd1的时候div3_o_r1输出高电平。
由于div3_o_r0和div3_o_r1输出1个时钟的高电平,但是相位相差180°,因此只要执行div3_o = div3_o_r0 | div3_o_r1运算,
就能实现1.5倍周期的输出高电平,那么剩余的1.5倍源时钟周期就是输出低电平了。
*/
reg [1:0] pos_cnt;
reg [1:0] neg_cnt;
always@(posedge clk_i)begin
if(!rst_n_i)
pos_cnt <= 2'b00;
else if(pos_cnt == 2'd2)
pos_cnt <= 2'b00;
else
pos_cnt <= pos_cnt + 1'b1;
end
always@(negedge clk_i)begin
if(!rst_n_i)
neg_cnt <= 2'b00;
else if(neg_cnt == 2'd2)
neg_cnt <= 2'b00;
else
neg_cnt <= neg_cnt + 1'b1;
end
reg div3_o_r0;
reg div3_o_r1;
always@(posedge clk_i)begin
if(!rst_n_i)
div3_o_r0 <= 1'b0;
else if(pos_cnt < 2'd1)
div3_o_r0 <= 1'b1;
else
div3_o_r0 <= 1'b0;
end
always@(negedge clk_i)begin
if(!rst_n_i)
div3_o_r1 <= 1'b0;
else if(neg_cnt < 2'd1)
div3_o_r1 <= 1'b1;
else
div3_o_r1 <= 1'b0;
end
reg div2hz_o_r;
reg [25:0] div2hz_cnt;
wire ms250_en = (div2hz_cnt == REF_CLK/4 - 1'b1);
always@(posedge clk_i)
begin
if(!rst_n_i)
div2hz_cnt <= 0;
else if(div2hz_cnt < REF_CLK/4 - 1'b1)
div2hz_cnt <= div2hz_cnt + 1'b1;
else
div2hz_cnt <= 0;
end
always@(posedge clk_i)
begin
if(!rst_n_i)
div2hz_o_r <= 0;
else if(ms250_en)
div2hz_o_r <= ~div2hz_o_r;
else
div2hz_o_r <= div2hz_o_r;
end
assign div2_o = div2_o_r;
assign div3_o = div3_o_r0 | div3_o_r1;
assign div4_o = div4_o_r;
assign div8_o = div8_o_r;
assign div2hz_o = div2hz_o_r;
generate if(DEBUG_ENABLE == 1'b1) begin : debugcore
//添加ila IP ,Chipscope观察信号
ila_0 ila_0_0 (
.clk(clk_i), // input wire clk
.probe0(div2hz_o), // input wire [0:0] probe0
.probe1({div2_o,div4_o,div8_o}) // input wire [3:0] probe1
);
end
endgenerate
endmodule
代码解释:
2 分频代码:只要基于源时钟每个时钟的上升沿对 div2_o_r 寄存器取反
4 分频和 8 分频代码:共同使用了 div_cnt1 计数器,4 分频就是对计数器在 div_cnt12’b00 或者 div_cnt12’b10 的时候对 div4_o_r 寄存器取反;而 8 分频是对 div_cnt1==2’b00 的时候对 div8_o_r 取反
3 分频代码:3 分频的本质是我们需要在每次 1.5 倍的时钟周期的时候实现 3 分频寄存器的翻转,但是我们无法直接实现 1.5倍的分频。因此采取分别采取 2 个计数器 pos_cnt 和 neg_cnt,分别对上升沿和下降沿计数。计数周期是 0-1-2,共计 3 个时钟周期。我们取 pos_cnt == 2’d1 的时候 div3_o_r0 输出高电平,neg_cnt == 2’d1 的时候 div3_o_r1 输出高电平。由于 div3_o_r0 和 div3_o_r1 输出 1 个时钟的高电平,但是相位相差 180°,因此只要执行 div3_o = div3_o_r0 | div3_o_r1 运算,就能实现 1.5 倍周期的输出高电平,那么剩余的 1.5 倍源时钟周期就是输出低电平了。
3 RTL 仿真
Clk_Divider_Tb.v
module Clk_Divider_Tb();
// Inputs
reg clk_i;
reg rst_n_i;
// Outputs
wire div2_o;
//wire div3_o;
wire div4_o;
wire div8_o;
wire div2hz_o;
// Instantiate the Unit Under Test (UUT)
Clk_Divider#(
.DEBUG_ENABLE(1'b0),
.REF_CLK(100000000)
)
Clk_Divider_inst
(
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.div2_o(div2_o),
.div4_o(div4_o),
.div8_o(div8_o),
.div2hz_o(div2hz_o)
);
initial begin
// Initialize Inputs4
clk_i= 0;
rst_n_i = 0;
// Wait 100 ns for global reset to finish
#100;
rst_n_i=1;
end
always #5 clk_i =~clk_i;
endmodule
2 分频、3 分频、8 分频
2HZ 分频