[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个周期
编码准备
状态机
因为要产生16个脉冲,所以我们自然可以把每个脉冲以及后面为低的周期当做一个状态。状态机如下:
把分频系数的整数部分称为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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。