上一个帖子中我做了固定参数的uart方案,如果模块的参数需要可设置该怎么做呢?奇偶校验位,停止位可变如何实现的呢?今天我们就来看看,使用状态机的写法如何实现这些。作者看过各种写法,对比来说,使用状态机的写法简单而又功能完善,易于学习和理解。话不多说,上代码。

uart字符帧结构
`define NONE 0
`define ODD 1
`define EVEN 2
`define MARK 3
`define SPACE 4
`define STOPBIT_ONE 0
`define STOPBIT_ONEHALF 1
`define STOPBIT_TWO 2
module uart_st #(
parameter BAUD = 9600, //波特率
parameter CLK_F = 50_000_000, //时钟频率
parameter PARITY = `ODD, //校验方式
parameter DATABITS = 8, //数据位长度
parameter STOPBIT = `STOPBIT_ONEHALF //停止位长度
)
(
input wire clk,
input wire rst,
input wire [ DATABITS - 1: 0 ] data,
input wire enable,
output reg tx,
output reg busy //指示外部调用模块,在忙状态时候,不能接受新的数据
);
localparam [31:0] CNT_MAX = CLK_F / BAUD;
// 【1】缓存外部数据 data
reg [ DATABITS - 1 : 0 ] data_r;
always@( posedge clk or posedge rst )
begin
if ( rst )
data_r <= 0;
else if ( enable && busy == 0 )
data_r <= data;
else
data_r <= data_r;
end
//【2】用状态机来完成发送
reg [ 31: 0 ] cnt;
reg [ 3: 0 ] num;
//[1] 定义状态空间
localparam FSM_IDLE = 0; //空闲状态
localparam FSM_START = 1; //起始位状态
localparam FSM_DATA = 2; //数据位状态
localparam FSM_PARITY = 3; //校验位状态
localparam FSM_STOP = 4; //停止位状态
wire baud_tick = (cnt == CNT_MAX - 1) ? 1'b1 : 1'b0;
//校验位
reg ifparity; //是否有检验位
reg parity_value; //校验位的值
//[2] 定义状态变量
reg [ 2: 0 ] cstate;
//[3] 完成状态机
//每一个状态里:状态转移和判断、根据状态输出
always@( posedge clk or posedge rst )
begin
if ( rst )
begin
cstate <= FSM_IDLE;
busy <= 0;
tx <= 1;
cnt <= 0;
num <= 0;
end
else
case ( cstate )
FSM_IDLE :
begin //空闲状态
tx <= 1; //空闲时发送1
if ( enable ) //需要发送数据
begin
cstate <= FSM_START;
busy <= 1;
cnt <= 0;
end
else
cstate <= FSM_IDLE;
end
FSM_START :
begin
tx <= 0; //发送起始位
if( cnt == CNT_MAX - 1 )
begin
cnt <= 0;
cstate <= FSM_DATA;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_START;
end
end
FSM_DATA :
begin
tx <= data_r[ num ]; //并串转换
if ( cnt == CNT_MAX - 1 && num == DATABITS - 1 ) //单bit位接受完成,并且接受到指定bit位长度
begin
cstate <= ifparity ? FSM_PARITY : FSM_STOP; //根据有无校验位来决定状态跳转
cnt <= 0;
num <= 0;
end
else if ( cnt == CNT_MAX - 1 && num < DATABITS - 1 ) //单bit位接受完成,但还没接收完所有的bit位
begin
num <= num + 1;
cnt <= 0;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_DATA;
num <= num;
end
end
FSM_PARITY :
begin
tx <= parity_value; //将计算出来的校验位发送出去
if ( cnt == CNT_MAX - 1 )
begin
cnt <= 0;
cstate <= FSM_STOP;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_PARITY;
end
end
FSM_STOP :
begin
tx <= 1; //停止位的值是1,下面是控制发送长度(时间)
case ( STOPBIT )
`STOPBIT_ONE :
begin
if ( cnt == CNT_MAX - 1 ) //1位停止位
begin
cnt <= 0;
cstate <= FSM_IDLE;
busy <= 0;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_STOP;
end
end
`STOPBIT_ONEHALF :
begin
if ( cnt == ( CNT_MAX[31:0] + CNT_MAX[31:1] ) - 1 ) //1.5位停止位,CNT_MAX[31:0] + CNT_MAX[31:1]
begin
$display("cnt = %d", cnt);
cnt <= 0;
cstate <= FSM_IDLE;
busy <= 0;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_STOP;
end
end
`STOPBIT_TWO :
begin
if ( cnt == ( ( CNT_MAX << 1 ) - 1 ) ) //2位停止位,CNT_MAX<<1
begin
cnt <= 0;
cstate <= FSM_IDLE;
busy <= 0;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_STOP;
end
end
default :
begin
if ( cnt == CNT_MAX - 1 )
begin
cnt <= 0;
cstate <= FSM_IDLE;
busy <= 0;
end
else
begin
cnt <= cnt + 1;
cstate <= FSM_STOP;
end
end
endcase
end
default :
begin
cstate <= FSM_IDLE;
busy <= 0;
tx <= 1;
cnt <= 0;
num <= 0;
end
endcase
end
always@( * )
begin
if ( rst )
begin
ifparity = 0 ;
parity_value = 0 ;
end
else
case ( PARITY )
`NONE :
begin
ifparity = 0 ;
parity_value = 0 ;
end
`ODD :
begin
ifparity = 1 ;
parity_value = ~( ^ data_r ) ;
end
`EVEN :
begin
ifparity = 1 ;
parity_value = ^ data_r ;
end
`MARK :
begin
ifparity = 1 ;
parity_value = 1 ;
end
`SPACE :
begin
ifparity = 1 ;
parity_value = 0 ;
end
default :
begin
ifparity = 0 ;
parity_value = 0 ;
end
endcase
end
endmodule
仿真程序:
module tb();
reg clk = 0;
always #10 clk = ~clk;
wire busy;
reg [7:0] data;
reg tx_en = 0;
reg rst = 0;
initial
begin
data = 0;
tx_en = 0;
rst = 1;
#20;
rst = 0;
#10;
@(posedge clk);
repeat(10)
begin
while(busy) @(posedge clk);
data = data + 1;
tx_en = 1;
@(posedge clk);
tx_en = 0;
@(posedge clk);
end
#200;
$stop;
end
wire tx;
`define NONE 0
`define ODD 1
`define EVEN 2
`define MARK 3
`define SPACE 4
`define STOPBIT_ONE 0
`define STOPBIT_ONEHALF 1
`define STOPBIT_TWO 2
uart_st #
(
.BAUD (115200),
.CLK_F (50_000_000),
.PARITY (`NONE),
.DATABITS (8),
.STOPBIT (`STOPBIT_TWO)
)
uart_st_inst
(
.clk (clk),
.rst (rst),
.data (data),
.enable (tx_en),
.tx (tx),
.busy (busy) // 指示外部调用模块,在忙的时候,不接收新的数据
);
endmodule
仿真波形:
