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
相关产品推荐
相关产品推荐

