LOADING

加载过慢请开启缓存 浏览器默认开启

Verilog-小数分频

2023/10/1 Verilog

Abstract: Verilog 小数分频

原理简述

单纯的 FPGA 硬件无法进行小数的运算,计数几分之几个时钟周期也是不现实的

因此,实现小数分频的方法是使用若干整数分频的组合来达到和小数分频一样的效果。小数可以化为最简分数的形式$M/N$,表示在输入时钟的 M 个周期内输出 N 个时钟周期,就相对于小数分频。需要注意的是,这样得到的分频时钟是不均匀的且占空比不为 $50\%$

以 8.7 分频为例,小数 8.7 可以化为分数 $87/10$,且这个分数不能化简,也就是需要在 87 个输入时钟内生成 10 个输出时钟。接下来需要取定组成分频时钟的整数分频系数,一般在小数分频系数周围取,这里是 8.7 分频,则取 8 和 9 为整数分频系数,因为 $7<8.7<8$

现在就可以确定,若干个 8 分频和若干个 9 分频组成输出的 10 个时钟周期,且这 10 个周期要在 87 个输入时钟周期内输出,由此得出以下方程组:

$$\begin{equation} \begin{cases} x+y=10\\ 8x+9y=87 \end{cases} \end{equation}$$

解出 x=3, y=7。就是说需要 3 个 8 分频时钟和 7 个 9 分频时钟,另外,如果先输出 8 分频时钟,则在 24 个输入时钟后输出 9 分频时钟

示例代码

题目见:牛客网 Verilog 专题 VL41

`timescale 1ns/1ns

module div_M_N(
 input  wire clk_in,
 input  wire rst,
 output wire clk_out
);
    parameter M_N = 8'd87; 
    parameter c89 = 8'd24; // 8/9时钟切换点
    parameter div_e = 5'd8; //偶数周期
    parameter div_o = 5'd9; //奇数周期
//*************code***********//

    reg [6:0] cyc_clk;  // 输入时钟计数
    reg [3:0] clk_cnt;  // 时钟分频计数
    reg div_flag;  // 分频标志,0为8分频,1为9分频
    reg clk_out_tmp;

    always @(posedge clk_in or negedge rst) begin  // 输入时钟计数
        if (~rst) begin
            cyc_clk <= 'b0;
        end
        else begin
            cyc_clk <= (cyc_clk==M_N-1)? 'b0 : cyc_clk+'b1;
        end
    end

    always @(posedge clk_in or negedge rst) begin  // 分频时钟计数器
        if (~rst) begin
            clk_cnt <= 'b0;
        end
        else begin
            if (~div_flag) begin
                clk_cnt <= (clk_cnt==div_e-1)? 'b0 : clk_cnt+'b1;
            end
            else begin
                clk_cnt <= (clk_cnt==div_o-1)? 'b0 : clk_cnt+'b1;
            end
        end
    end

    always @(posedge clk_in or negedge rst) begin  // 分频系数切换
        if (~rst) begin
            div_flag <= 'b0;
        end
        else begin
            div_flag <= (cyc_clk==(M_N-1) || cyc_clk==c89-1)? ~div_flag : div_flag;
        end
    end

    always @(posedge clk_in or negedge rst) begin  // 生成分频时钟
        if (~rst) begin
            clk_out_tmp <= 'b0;
        end
        else begin
            if (~div_flag) begin
                clk_out_tmp <= (clk_cnt<=((div_e>>2)+1));
            end
            else begin
                clk_out_tmp <= (clk_cnt<=((div_o>>2)+1));
            end
        end
    end

    assign clk_out = clk_out_tmp;

//*************code***********//
endmodule