FPGA在不相关的更改后开始工作,为什么?

电器工程 FPGA 验证日志 UART 石英 气旋
2022-01-14 10:52:31

我在 Verilog 中编写了一个 UART 模块。通过使用该模块,我通过 UART 从 PC 获取数据,然后通过此 UART 模块再次发送该数据。我把它上传到FPGA进行测试。无论我使用以下代码发送多少个字符,它都可以完美运行(实际上您可能不需要 TX 状态,但我将完整代码用于清除):

module uart_transceiver

 #(parameter [15:0] CLK_PERIOD = 434) //50000000/115200

(
    input sys_rst,
    input sys_clk,

    input uart_rx,
    output reg uart_tx,

    //input [15:0] divisor,

    output reg [7:0] rx_data,
    output reg rx_err,
    output [1:0] state_uart,
    output [7:0] clock_most8,
    output [7:0] clock_least8,

    input [7:0] tx_data,
    input tx_wr,
    output reg rx_busy,
    output reg rx_done,
    output reg tx_busy
);

reg [1:0]   state_tx, state_rx;

//-----------------------------------------------------------------
// UART RX Logic
//-----------------------------------------------------------------
reg [3:0]   rx_bitcount;
reg [15:0]  clk_count_rx = 16'h0001;
reg [7:0]   rx_reg;
reg             rx1;

assign state_uart = state_rx;
//assign clock_least8 = clk_count_rx[7:0];
//assign clock_most8 = clk_count_rx[15:8];


parameter   IDLERX      = 2'd0, 
                TAKESTART   = 2'd1,
                TAKE8BITS   = 2'd2,
                TAKESTOP    = 2'd3;

parameter   [15:0] FIRST_CHECK = CLK_PERIOD / 2;



always @(posedge sys_clk) begin

    if(sys_rst) begin

        state_rx <= IDLERX;
        rx_err <= 1'b0;
        rx_done <= 1'b0;

    end else begin

        case(state_rx)

            IDLERX: begin
                rx_bitcount <= 4'd0;
                rx_busy <= 1'b0;
                if(uart_rx == 1'b0 && rx_err != 1'b1) begin
                    clk_count_rx <= 16'h0001;
                    state_rx <= TAKESTART;
                end
            end

            // Take one start signal and check if it is 0
            TAKESTART: begin
                rx_busy <= 1'b1;
                rx_done <= 1'b0;
                clk_count_rx <= clk_count_rx + 16'h0001;

                if(clk_count_rx == FIRST_CHECK) 
                    begin
                    rx1 <= uart_rx;
//                  clk_count_rx <= clk_count_rx + 16'd1;
                    end 

                if(clk_count_rx == CLK_PERIOD && rx1 != 1'b0) 
                    begin
                    rx_err <= 1'b1;
                    state_rx <= IDLERX;
                    end 

                else if(clk_count_rx == CLK_PERIOD && rx1 == 1'b0) 
                    begin
                    clk_count_rx <= 16'h0001;
                    state_rx <= TAKE8BITS;
                    end 

            end


            // Take 8 bits of data but be careful to wait for 1 CLK_PERIOD for each bit
            TAKE8BITS: begin

                if(rx_bitcount < 4'd8) 
                    begin               
                    clk_count_rx <= clk_count_rx + 16'h0001;

                    if(clk_count_rx == FIRST_CHECK) 
                        begin
                        rx1 <= uart_rx;
                        end 

                    else if(clk_count_rx == CLK_PERIOD) 
                        begin
                        clk_count_rx <= 16'h0001;
                        rx_reg <= {rx1, rx_reg[7:1]};   
                        rx_bitcount <= rx_bitcount + 1'b1;                                                  
                        end

                    end 

                else                
                    begin               
                    clk_count_rx <= 16'h0001;
                    state_rx <= TAKESTOP;
                    end

            end

            // Take 1 stop bit in last CLK_PERIOD
            TAKESTOP: begin

                clk_count_rx <= clk_count_rx + 16'h0001;

                if(clk_count_rx == FIRST_CHECK) 
                    begin
                    rx1 <= uart_rx;
                    end 

                else if(clk_count_rx == CLK_PERIOD && rx1 != 1'b1) 
                    begin
                    rx_err <= 1'b1;
                    state_rx <= IDLERX;
                    end 

                else if(clk_count_rx == CLK_PERIOD && rx1 == 1'b1) 
                    begin   
                    rx_data <= rx_reg;
                    rx_done <= 1'b1;
                    clk_count_rx <= 16'h0001;
                    state_rx <= IDLERX;
                    end 
                else if(clk_count_rx > CLK_PERIOD)
                    begin
                    rx_err <= 1'b1;
                    state_rx <= IDLERX;
                    end
            end

        endcase
    end

end



//-----------------------------------------------------------------
// UART TX Logic
//-----------------------------------------------------------------
parameter   IDLE            = 2'd0, 
                SENDSTART   = 2'd1,
                SEND8BITS   = 2'd2,
                SENDSTOP    = 2'd3;


reg [3:0] tx_bitcount;
reg [15:0] clk_count;
reg [7:0] tx_reg;

always @(posedge sys_clk) begin

    if(sys_rst) begin

        state_tx <= IDLE;

    end else begin

        case(state_tx)

            IDLE: begin
                uart_tx <= 1'b1;
                //tx_reg <= tx_data;
                clk_count <= 16'd0;
                tx_bitcount <= 4'd0;
                tx_busy <= 1'b0;
                if(tx_wr) begin
                    tx_reg <= tx_data;
                    state_tx <= SENDSTART;
                end
            end

            // Send one start signal for one CLK_PERIOD
            SENDSTART: begin
                tx_busy <= 1'b1;
                uart_tx <= 1'b0;
                if(clk_count == CLK_PERIOD) begin
                    clk_count <= 16'd0;
                    //tx_reg <= tx_data;
                    state_tx <= SEND8BITS;
                end else begin
                    clk_count <= clk_count + 16'd1; 
                end

            end


            // Send 8 bits of data but be careful to wait for 1 CLK_PERIOD for each bit
            SEND8BITS: begin
                if(tx_bitcount == 4'd0) begin
                    tx_bitcount <= tx_bitcount + 4'd1;
                    uart_tx <= tx_reg[0];   
                    tx_reg <= {1'b0, tx_reg[7:1]};
                end 
                if(clk_count == CLK_PERIOD) begin
                    clk_count <= 16'd0;
                    if(tx_bitcount < 4'd8) begin
                        tx_bitcount <= tx_bitcount + 4'd1;
                        uart_tx <= tx_reg[0];   
                        tx_reg <= {1'b0, tx_reg[7:1]};
                    end 
                    else begin
                        clk_count <= 16'd0;
                        state_tx <= SENDSTOP;
                    end
                end 
                else begin
                        clk_count <= clk_count + 16'd1;
                end
            end

            // Send 1 stop bit for one CLK_PERIOD
            SENDSTOP: begin
                uart_tx <= 1'b1;        
                if(clk_count == CLK_PERIOD) begin
                    clk_count <= 16'd0;
                    tx_busy <= 1'b0;
                    state_tx <= IDLE;
                end else begin
                    clk_count <= clk_count + 16'd1; 
                end                     
            end

        endcase
    end

end

endmodule

但是,如果我从上面显示的 TAKESTOP 状态中删除下面的最后一个 else if 块,则代码在对一些随机数量的字符运行良好后停止工作,并卡在 TAKESTOP 状态。当我应用 sys_rst 时,它再次适用于某些字符,然后卡在相同的 TAKESTOP 状态。有趣的是,当这个 else if 块被放置并且代码完美运行时,它实际上从来没有进入这个 else if。我知道,否则它会卡在 IDLERX 状态,因为 rx_err 将为 1。:

else if(clk_count_rx > CLK_PERIOD)
        begin
        rx_err <= 1'b1;
        state_rx <= IDLERX;
        end

我对这些问题很生气。而不是插入 else if 块,当我通过从上面完整代码显示的这两行中删除注释将 clk_count_rx 分配给输出时,问题也消失了:

assign clock_least8 = clk_count_rx[7:0];
assign clock_most8 = clk_count_rx[15:8];

换句话说,如果我不将寄存器分配给输出,我的状态机将无法正常工作且稳定,尽管我不使用该输出。对于我不同的 Verilog 项目,这种情况又发生了几次。

这种不相关的更改如何修复我的代码的问题?我是怎么了?这件事似乎不合逻辑。我是否犯了结构性概念错误,而这种变化似乎以某种方式修复了它?我不知道。我使用 Quartus 16.1 和 Cyclone V GX Starter Board。

顺便说一句,这是一个工作的 UART 模块,欢迎任何人使用它。我一定会很高兴在这件事上帮助我的人。

1个回答

您的uart_rx信号与时钟异步。但是,您在代码中有一个地方可以直接在状态机中使用它。这很奇怪,因为在所有其他情况下,您都会小心地将其分配给rx1然后对其进行测试。

    IDLERX: begin
      rx_bitcount <= 4'd0;
      rx_busy <= 1'b0;
      if (uart_rx == 1'b0 && rx_err != 1'b1) begin
        clk_count_rx <= 16'h0001;
        state_rx <= TAKESTART;
      end
    end

这里的问题是这条if语句同时修改了状态寄存器和计数器寄存器,一共18个FF。这些 FF 中的每一个都有与之关联的逻辑,以确定其下一个状态是什么。当您向该逻辑提供异步输入并且该输入在接近时钟边沿时发生变化时,一些 FF 会看到它处于一种状态,而其他人会看到它处于不同的状态。在时钟沿之后,它们将处于与您的实际逻辑无关的不一致状态。1

当您修改代码时,会导致芯片内部的硬件资源和路由路径配置不同,这意味着这uart_rx18 个 FF 之间的延迟将发生变化。这会以不可预知的方式改变状态机的行为。

底线:状态机的所有输入必须与时钟同步!在你的情况下,最简单的方法是把作业

rx1 <= uart_rx;

在您的case声明之外,并rx1专门在其中使用。

顺便说一句,你不是第一个犯这个错误的人:UART 接收器故障……你不会是最后一个。


1请注意,这与亚稳态问题无关,亚稳态也是异步输入的问题。为获得最佳效果,您uart_rx在使用前至少要通过两个 FF。