You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Verilog UART仿真与FPGA硬件实现的停止位行为不一致问题排查

FPGA UART发送模块仿真与硬件行为不一致问题
  • 实现目标:在FPGA上用Verilog实现UART发送功能,循环发送byte_array_com_message_1至byte_array_com_message_4中的预定义数据,通过out_cross_com1输出,严格遵循UART标准:空闲时信号保持高电平,拉低输出起始位,发送8位数据,拉高输出停止位,随后进入下一帧。
  • 使用工具链:Verilog、vvp、gtkwave,通过IceStudio为Nandland GO Board进行综合。
  • 问题现象:gtkwave仿真中停止位仅持续1个时钟周期,随后立即进入下一个起始位;但示波器显示硬件输出的停止位持续2个时钟周期。前两个发送字节0xA5和0x33在仿真与硬件中结果一致。
  • 背景:Verilog和FPGA新手,需要系统性的调试方法。

待测代码

wire out_cross_com1;
wire state_out_0;
wire state_out_1;

wire [7:0] byte_array_com_message_1 [0:31];
wire [7:0] byte_array_com_message_2 [0:31];
wire [7:0] byte_array_com_message_3 [0:31];
wire [7:0] byte_array_com_message_4 [0:31];
reg [2:0] bit_position;
reg [4:0] byte_position;
reg [1:0] message_index;
reg [1:0] uart_statemachine;
reg [1:0] uart_statemachine_next;
reg signal_out_cross_com1;

assign byte_array_com_message_1[0] = 8'hA5;
assign byte_array_com_message_1[1] = 8'h33;
//assign byte_array_com_message_1[2} = ....;

localparam
    uart_idle = 2'b00,
    uart_start = 2'b01,
    uart_transmit = 2'b10,
    uart_stop = 2'b11;

localparam
    message_1 = 2'b00,
    message_2 = 2'b01,
    message_3 = 2'b10,
    message_4 = 2'b11;

assign clk_out = clk;
assign out_cross_com1 = signal_out_cross_com1;
assign state_out_0 = uart_statemachine[0];
assign state_out_1 = uart_statemachine[1];

// output
always @(posedge clk)
begin
    if(rst == 1) begin
        signal_out_cross_com1 <= 1;
    end else begin
        case(uart_statemachine)
            uart_idle:
            begin
                signal_out_cross_com1 <= 1;
            end
            uart_start:
            begin
                signal_out_cross_com1 <= 0;
            end
            uart_transmit:
                case(message_index)
                    message_1:
                    begin
                        signal_out_cross_com1 <= byte_array_com_message_1[byte_position][bit_position];
                    end
                    message_2:
                    begin
                        signal_out_cross_com1 <= byte_array_com_message_2[byte_position][bit_position];
                    end
                    message_3:
                    begin
                        signal_out_cross_com1 <= byte_array_com_message_3[byte_position][bit_position];
                    end
                    message_4:
                    begin
                        signal_out_cross_com1 <= byte_array_com_message_4[byte_position][bit_position];
                    end
                endcase
            uart_stop:
            begin
                signal_out_cross_com1 <= 1;
            end
        endcase
    end

end

// update bit position
always @(posedge clk)
begin
    if(rst == 1) begin
        bit_position <= 3'b000;
        byte_position <= 5'b00000;
        message_index <= 2'b00;
    end else begin
        case(uart_statemachine)
            uart_idle:
            begin
                bit_position <= 3'b000;
                byte_position <= 5'b00000;
                message_index <= 2'b00;
            end
            uart_start:
            begin
                bit_position <= 3'b000;
                byte_position <= byte_position;
                message_index <= message_index;
            end
            uart_transmit:
            begin
                if(bit_position == 7) begin
                    bit_position <= 3'b000;
                    if(byte_position == 31) begin
                        byte_position <= 5'b00000;
                        if(message_index == 3) begin
                            message_index <= 2'b00;
                        end else begin
                            message_index <= message_index + 1;
                        end
                    end else begin
                        byte_position <= byte_position + 1;
                    end
                end else begin
                    bit_position <= (bit_position + 1);
                end
            end
            uart_stop:
            begin
                bit_position <= 3'b000;
                byte_position <= byte_position;
                message_index <= message_index;
            end
        endcase
    end
end

// Statemachine
always @(posedge clk)
begin
    if(rst == 1) begin
        uart_statemachine <= uart_idle;
    end else begin
        uart_statemachine <= uart_statemachine_next;
    end
end

// Determine next state
always @(posedge clk)
begin
    if(rst == 1) begin
        uart_statemachine_next <= uart_idle;
    end else begin
        case(uart_statemachine_next)
            uart_idle:
            begin
                uart_statemachine_next <= uart_start;
            end
            uart_start:
            begin
                uart_statemachine_next <= uart_transmit;
            end
            uart_transmit:
            begin
                // end of byte. Stop bit
                if(bit_position == 7) begin
                    uart_statemachine_next <= uart_stop;
                // stay until end of byte
                end else begin
                    uart_statemachine_next <= uart_transmit;
                end
            end
            uart_stop:
            begin
                uart_statemachine_next <= uart_start;
            end
        endcase
    end
end

测试平台代码

`include "cross_com_sim.v"
`timescale 1ns/100ps

module cross_com_sim_tb;
    reg clk;
    reg rst;
    wire out_cross_com1;
    wire out_cross_com2;
    wire clk_out;
    cross_com_sim DUT(.clk(clk), .rst(rst), .out_cross_com1(out_cross_com1));

    initial
    begin
        $dumpfile("cross_com_sim_dump.vcd");
        $dumpvars;
    end

    initial
    begin
        clk = 1;
        forever #1 clk = ~clk;
    end

    initial
    begin
        rst = 1;
        #6;
        rst = 0;
        #4000;
        $finish;
    end
endmodule

问题根源与调试方案

1. 核心问题定位

  • 状态机逻辑错误:当前状态转移判断是基于uart_statemachine_next自身的状态,而非当前的uart_statemachine状态,这会导致仿真与硬件的时序行为出现偏差——仿真中状态跳转是理想的即时响应,而硬件中综合后的组合逻辑延迟会让状态跳转多停留一个周期。
  • 停止位时长无控制:代码中没有对停止位的持续时钟周期数做计数,进入uart_stop状态后立即跳转,理论上仅持续1个时钟周期,但硬件的延迟让它多了一个周期。

2. 针对性修正步骤

(1)修复状态机转移逻辑

将状态转移判断的依据改为当前的uart_statemachine状态,确保状态跳转逻辑同步:

// Determine next state
always @(posedge clk)
begin
    if(rst == 1) begin
        uart_statemachine_next <= uart_idle;
    end else begin
        case(uart_statemachine)  // 替换为当前状态,而非next状态
            uart_idle:
            begin
                uart_statemachine_next <= uart_start;
            end
            uart_start:
            begin
                uart_statemachine_next <= uart_transmit;
            end
            uart_transmit:
            begin
                if(bit_position == 7) begin
                    uart_statemachine_next <= uart_stop;
                end else begin
                    uart_statemachine_next <= uart_transmit;
                end
            end
            uart_stop:
            begin
                uart_statemachine_next <= uart_start;
            end
        endcase
    end
end

(2)添加停止位时钟计数

为了严格控制停止位时长(需根据系统时钟和目标波特率计算所需时钟周期数,比如9600波特率+1MHz时钟对应约104个时钟周期),新增计数器并修改状态跳转逻辑:

// 新增停止位计数器,位宽根据所需计数调整
reg [6:0] stop_bit_cnt;  // 示例位宽对应最大计数127
localparam STOP_BIT_CYCLES = 7'd104;  // 替换为实际需要的周期数

// 计数器更新逻辑
always @(posedge clk)
begin
    if(rst == 1) begin
        stop_bit_cnt <= 7'd0;
    end else begin
        case(uart_statemachine)
            uart_stop:
            begin
                if(stop_bit_cnt == STOP_BIT_CYCLES) begin
                    stop_bit_cnt <= 7'd0;
                end else begin
                    stop_bit_cnt <= stop_bit_cnt + 1'd1;
                end
            end
            default:
                stop_bit_cnt <= 7'd0;
        endcase
    end
end

// 修改uart_stop状态的跳转逻辑
always @(posedge clk)
begin
    if(rst == 1) begin
        uart_statemachine_next <= uart_idle;
    end else begin
        case(uart_statemachine)
            // 其他状态逻辑不变
            uart_stop:
            begin
                // 计数达标后再跳转至下一帧起始位
                if(stop_bit_cnt == STOP_BIT_CYCLES) begin
                    uart_statemachine_next <= uart_start;
                end else begin
                    uart_statemachine_next <= uart_stop;
                end
            end
        endcase
    end
end

3. 仿真与硬件验证

  • 重新编译测试平台,用gtkwave观察uart_statemachine、stop_bit_cnt和out_cross_com1的波形,确认停止位时长符合预期。
  • 将state_out_0和state_out_1连接到FPGA的IO引脚,用示波器同时观察状态信号与out_cross_com1,对比硬件中的状态跳转时序与仿真结果。
  • 查看IceStudio的综合报告,确认状态机被正确综合为同步逻辑,无意外的优化导致行为偏差。

内容的提问来源于stack exchange,提问作者user44791

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 23:55:31