1

[verilog] 对于FSM状态机的进一步思考 - 生成支持小数分频的UART Baud16信号

前言

uart 的 baudrate 公式如下:

$$ baudrate = \frac{UART\_CLK}{16 \times Divisor} $$

baudrate generator需要产生一组脉冲,16组脉冲的宽度就是baud rate, 也就是rx和tx的最小边沿宽度

思路

如果只考虑分频系数为整数的情况,那么我们只需要用一个计数器从divisor-1倒数到0就行了
代码如下:

reg [15:0] dl;
reg [15:0] dlc;

reg baud16;

always @(posedge dclk or negedge rstn)
begin
        if (~rstn)
        begin
                dlc    <= 0;
        end else begin
                if (~(|dlc) && |dl)
                        dlc  <= dl - 1;               // preset counter
                else if(|dlc)
                        dlc  <= dlc - 1;              // decrement counter
        end
end

always @(posedge dclk or negedge rstn) begin
    if (~rstn)
    begin
        baud16 <= #1 1'b0;
    end else begin
        if (|dl & ~(|dlc))     // dl>0 & dlc==0
            baud16 <= #1 1'b1;
        else
            baud16 <= #1 1'b0;
    end
end

ARM pl011允许分频系数为小数, 所以一个baud rate可能包含任意N个uart clk。
比如分频系数是3.25, 那么一个baud rate包含3.25*16=52个uart clk.

对于上面的情况, baud16是需要在52个uart clk内产生16个脉冲。
我们的思路是前面15个脉冲都按照3来做分频,这样前面占掉3*15=45个cycle
最后一个脉冲占掉3+0.25*16=7个周期
图片.png

编码准备

状态机

因为要产生16个脉冲,所以我们自然可以把每个脉冲以及后面为低的周期当做一个状态。状态机如下:
Code_499sq6tBtgrub.png

把分频系数的整数部分称为dli, 小数部分乘以16后得到的整数称为dlf.
需要定义一个计数器称为rcnt, 每当进入一个状态就开始从0计数,当计数到阈值时就切换到下一个状态。

每个状态

在正常情况下,假设分频系数的整数部分为3, 那么high占1个固定周期,而low占剩下的3-1=2个周期。最后一个周期比较特殊,high占是占1个周期,而low占dli+dlf-1个周期。

边界条件

当分频系数的整数部分为1时,我们可以看到dli-1为0(low占的周期), 在这种状态下,每个状态只维持一个周期高就进入下一个状态,所以没有为低的时候。这是一个特殊情况,需要特殊处理。

产生测试激励

唯一的输入变化来自于分频系数,所以我们可以建一个类,分别产生分频系数的整数和小数分部。因为0,1,2属于比较敏感区,所以我们在这个区别的权重设置高一些。

总结

相较于通常的状态机,会有一个外界输入来控制状态切换。但我们需求的是当一个状态的事做完了就切换到下一个状态,如果在状态里加一个寄存器来表征本状态完成,代码如下:

case(rstate)
R1:
    ...
    if(rcnt == (dli=1)) begin
        rdone <= 1;
    end
...
endcase

上面这种实现会造成一个周期的延迟,整个时序就会出问题,特别是在dli为1时,根本没有一个周期来偷,所以就无法实现。

为了解决上在的问题,一种方法就是不使用三段式,也就是不用下面这样的代码。

always @(posedge dclk or negedge rstn)
begin
    if(~rstn) begin
        rstate <= 0;
        next_rstate <= 0;
    end else begin
        rstate <= next_rstate;
    end
end

直接使用state, 当一个状态做完,直接把state赋值成下一个状态。

教训

一开始的时候没有理清状态切换的判断条件,使用的是实现二中的idnum来做状态切换条件,而idnum是在上一个完成时就会切换成下一个状态的值,所以next_rstate就会变成下一个状态,但如果当前状态需要多个周期时,就会无法保留在当前状态上,被强制切换到下一个状态。

代码

实现一

正面的代码可以简化成两个状态r0, r15, r0代码非r15状态,r15代码第16个脉冲,但为了代码清晰,我们还是保留16个状态。(其实是17个状态,第0个状态为空闲状态)

//////////////////////////////////////////////////////////////////////////////////
//=======================================================================
// Created by         :  Harris Zhu
// Filename           :  baud16gen.sv
// Author             :  Harris Zhu
// Created On         :  2018-08-12 22:30:53
// Last Modified      :  2018-09-11 05:54:24
// Update Count       :  7
// Tags               :  
// Description        : 
// Conclusion         : 
//=======================================================================
//////////////////////////////////////////////////////////////////////////////////

`timescale 1ns/1ns
class c_dl #(
    DIVINT_WIDTH=16,
    DIVFRAC_WIDTH=6
);
    rand bit [DIVINT_WIDTH-1:0] dll;
    rand bit [DIVFRAC_WIDTH-1:0] dlh;

    constraint c_dll {
        dll dist {1:=3, [2:4]:=2, [5:20]:/1};
    }

    constraint c_dlh {
        dlh dist {[0:1]:/3, [2:4]:=2, [5:20]:/1};
    }

endclass


module baud16gen();

parameter ID=0;
parameter DIVFRAC_WIDTH=6; 

localparam DIVINT_WIDTH=16;
localparam DIV_WIDTH=DIVFRAC_WIDTH+DIVINT_WIDTH;
localparam ISGREATER4 = (DIVFRAC_WIDTH>4)?1:0;
localparam SUB4 = (ISGREATER4==1)?(DIVFRAC_WIDTH-4):(4-DIVFRAC_WIDTH);

localparam r0 =0;
localparam r1 =1;
localparam r2 =2;
localparam r3 =3;
localparam r4 =4;
localparam r5 =5;
localparam r6 =6;
localparam r7 =7;
localparam r8 =8;
localparam r9 =9;
localparam r10=10;
localparam r11=11;
localparam r12=12;
localparam r13=13;
localparam r14=14;
localparam r15=15;
localparam r16=16;

localparam low=0;
localparam high=1;


reg         rstn=0;  // low active,  really in the real UART interface, there is no reset ...
reg         dclk=0; // Maybe a free running cake clock for PD hybrid mode, else tie to tclk
reg  [DIV_WIDTH-1:0] dl={6'h4, 16'h1}; // Divisor for baudrate (x16)

always #1 dclk=~dclk;

c_dl #(DIVINT_WIDTH, DIVFRAC_WIDTH) div;

initial
begin
    rstn = 0;
    #100;
    rstn = 1;
    div = new();
    forever begin
        div.randomize();
        dl = {div.dlh, div.dll};
        #1000;
    end
end

reg enable; // this is the needed baud16

reg [15:0] rcnt;
reg clk_phase=1;
reg [15:0] p0cnt=0;

reg [4:0] rstate, next_rstate;

wire [DIVINT_WIDTH-1:0] dli=dl[DIVINT_WIDTH-1:0];
wire [DIVFRAC_WIDTH-1:0] dlh=dl[DIV_WIDTH-1:DIVINT_WIDTH];
reg [DIVFRAC_WIDTH-1:0] dlf;

always @(*) begin
    if(ISGREATER4) begin
        dlf = (dlh>>SUB4) + dlh[SUB4-1];
    end else begin
        dlf = (dlh<<SUB4);
    end
end

always @(posedge dclk or negedge rstn)
begin
    if(~rstn) begin
        rcnt <= 0;
        rstate <= 0;
        next_rstate <= 0;
    end else begin
        rstate <= next_rstate;
    end
end

always @(*)
begin
    case(rstate)
        r0:
            next_rstate = r1;
        r1:
            if(rcnt>=(dli-1)) begin
                next_rstate = r2;
            end
        r2:
            if(rcnt>=(dli-1)) begin
                next_rstate = r3;
            end
        r3:
            if(rcnt>=(dli-1)) begin
                next_rstate = r4;
            end
        r4:
            if(rcnt>=(dli-1)) begin
                next_rstate = r5;
            end
        r5:
            if(rcnt>=(dli-1)) begin
                next_rstate = r6;
            end
        r6:
            if(rcnt>=(dli-1)) begin
                next_rstate = r7;
            end
        r7:
            if(rcnt>=(dli-1)) begin
                next_rstate = r8;
            end
        r8:
            if(rcnt>=(dli-1)) begin
                next_rstate = r9;
            end
        r9:
            if(rcnt>=(dli-1)) begin
                next_rstate = r10;
            end
        r10:
            if(rcnt>=(dli-1)) begin
                next_rstate = r11;
            end
        r11:
            if(rcnt>=(dli-1)) begin
                next_rstate = r12;
            end
        r12:
            if(rcnt>=(dli-1)) begin
                next_rstate = r13;
            end
        r13:
            if(rcnt>=(dli-1)) begin
                next_rstate = r14;
            end
        r14:
            if(rcnt>=(dli-1)) begin
                next_rstate = r15;
            end
        r15:
            if(rcnt>=(dli-1)) begin
                next_rstate = r16;
            end
        r16:
            if(rcnt>=(dli+dlf-1)) begin
                next_rstate = r1;
            end
        default:
            next_rstate = r0;
    endcase
end

always @(posedge dclk or negedge rstn)
begin
    if (~rstn) begin
        clk_phase <= 1;
        p0cnt <= 0;
        enable <= 0;
        rstate <= r0;
    end else begin
        case(rstate)
            r0:
            begin
            end
            r1, r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15:
            begin
                if(dli==1) begin
                    rcnt <= 0;
                    enable <= 1;
                    clk_phase <= 1;
                end else begin
                    if(rcnt >= (dli-1)) begin
                        rcnt <= 0;
                    end else begin
                        rcnt <= rcnt + 1;
                    end
                    if(clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            clk_phase <= high;
                        end
                    end
                end
            end
            r16:
            begin
                if((dli==1)&&(dlf==0)) begin
                    rcnt <= 0;
                end else begin
                    if(rcnt >= (dli+dlf-1)) begin
                        rcnt <= 0;
                    end else begin
                        rcnt <= rcnt + 1;
                    end
                end
                if(dli==1) begin
                    if(dlf==0) begin
                        enable <= 1;
                    end else begin
                        if(clk_phase) begin
                            enable <= 1;
                            clk_phase <= low;
                            p0cnt <= 0;
                        end else begin
                            enable <= 0;
                            if(p0cnt < (dlf-1)) begin
                                p0cnt <= p0cnt + 1;
                            end else begin
                                clk_phase <= high;
                            end
                        end
                    end
                end else begin //dli>1
                    if(clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli+dlf-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            clk_phase <= high;
                        end
                    end
                end // dli>1
            end // r15
            default:
            begin
                enable <= 1;
            end
        endcase
    end
end

endmodule

实现二

下面的代码比上面的更简洁,但中间的always块的代码更长,更难于理解。

//////////////////////////////////////////////////////////////////////////////////
//=======================================================================
// Created by         :  Harris Zhu
// Filename           :  baud16gen.sv
// Author             :  Harris Zhu
// Created On         :  2018-08-12 22:30:53
// Last Modified      :  2018-09-11 05:54:24
// Update Count       :  7
// Tags               :  
// Description        : 
// Conclusion         : 
//=======================================================================
//////////////////////////////////////////////////////////////////////////////////

`timescale 1ns/1ns
class c_dl #(
    DIVINT_WIDTH=16,
    DIVFRAC_WIDTH=6
);
    rand bit [DIVINT_WIDTH-1:0] dll;
    rand bit [DIVFRAC_WIDTH-1:0] dlh;

    constraint c_dll {
        dll dist {1:=3, [2:4]:=2, [5:20]:/1};
    }

    constraint c_dlh {
        dlh dist {[0:1]:/3, [2:4]:=2, [5:20]:/1};
    }

endclass


module baud16gen ();

parameter ID=0;
parameter DIVFRAC_WIDTH=6; 

localparam DIVINT_WIDTH=16;
localparam DIV_WIDTH=DIVFRAC_WIDTH+DIVINT_WIDTH;
localparam ISGREATER4 = (DIVFRAC_WIDTH>4)?1:0;
localparam SUB4 = (ISGREATER4==1)?(DIVFRAC_WIDTH-4):(4-DIVFRAC_WIDTH);

localparam r0=0;
localparam r15=1;
localparam low=0;
localparam high=1;

reg         rstn=0;  // low active,  really in the real UART interface, there is no reset ...
reg         dclk=0; // Maybe a free running cake clock for PD hybrid mode, else tie to tclk
reg  [DIV_WIDTH-1:0] dl={6'h4, 16'h1}; // Divisor for baudrate (x16)
reg  [7:0]  lcr=3;

always #1 dclk=~dclk;

c_dl #(DIVINT_WIDTH, DIVFRAC_WIDTH) div;

initial
begin
    rstn = 0;
    #100;
    rstn = 1;
    div = new();
    forever begin
        div.randomize();
        dl = {div.dlh, div.dll};
        #1000;
    end
end

// pragma protect
// pragma protect begin
// quickturn not_protect_output
`protect

reg        enable;

reg clk_phase=1;
reg [3:0] idnum=0;
reg [15:0] p0cnt=0;

reg [3:0] state;

wire [DIVINT_WIDTH-1:0] dli=dl[DIVINT_WIDTH-1:0];
wire [DIVFRAC_WIDTH-1:0] dlh=dl[DIV_WIDTH-1:DIVINT_WIDTH];
reg [DIVFRAC_WIDTH-1:0] dlf;

always @(*) begin
    if(ISGREATER4) begin
        dlf = (dlh>>SUB4) + dlh[SUB4-1];
    end else begin
        dlf = (dlh<<SUB4);
    end
end

function bit [3:0] next_idnum(bit [3:0] id, bit [3:0] max);
    if(id<max) begin
        next_idnum = id+1;
    end else begin
        next_idnum = 0;
    end
endfunction

always @(posedge dclk or negedge rstn)
begin
    if (~rstn) begin
        clk_phase <= 1;
        p0cnt <= 0;
        idnum <= 0;
        enable <= 0;
        state <= r0;
    end else begin
        case(state)
            r0:
                if (dli==1) begin
                    enable <= 1;
                    clk_phase <= 1;
                    if(idnum == 15) begin
                        idnum <= 0;
                        state <= r15;
                    end else begin
                        idnum <= idnum + 1;
                    end
                end else begin
                    if (clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            p0cnt <= 0;
                            clk_phase <= high;
                            if(idnum == 15) begin
                                idnum <= 0;
                                state <= r15;
                            end else begin
                                idnum <= idnum + 1;
                            end
                        end
                    end
                end
            r15:
            begin
                if (dli==1) begin
                    if(dlf==0) begin
                        enable <= 1;
                        idnum <= next_idnum(idnum, 15);
                        state <= r0;
                    end else begin
                        if (clk_phase) begin
                            enable <= 1;
                            clk_phase <= low;
                            p0cnt <= 0;
                        end else begin
                            enable <= 0;
                            if(p0cnt < (dlf-1)) begin
                                p0cnt <= p0cnt + 1;
                            end else begin
                                p0cnt <= 0;
                                clk_phase <= high;
                                idnum <= next_idnum(idnum, 15);
                                state <= r0;
                            end
                        end
                    end
                end else begin
                    if (clk_phase) begin
                        enable <= 1;
                        clk_phase <= low;
                        p0cnt <= 0;
                    end else begin
                        enable <= 0;
                        if(p0cnt < (dli+dlf-2)) begin
                            p0cnt <= p0cnt + 1;
                        end else begin
                            p0cnt <= 0;
                            clk_phase <= high;
                            idnum <= next_idnum(idnum, 15);
                            state <= r0;
                        end
                    end
                end
            end
            default:
            begin
            end
        endcase
    end
end

endmodule

harriszh
338 声望131 粉丝

做些有趣的事,留些有用的存在


引用和评论

0 条评论