我在 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 模块,欢迎任何人使用它。我一定会很高兴在这件事上帮助我的人。