Menu Close

uart通信tx模块在FPGA实现的一种Verilog状态机写法(二)

上一个帖子中我做了固定参数的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

仿真波形:

 

 

Posted in FPGA, FPGA 教材教案, Verilog, Verilog

发表回复

相关链接