SystemVerilog 进阶:UART 发送器 + 接收器(可综合)
目标
用 SystemVerilog 实现完整的 UART 收发器(8N1, 115200bps),可直接综合到 FPGA。
UART 发送器
module uart_tx #(parameter CLKS_PER_BIT = 434) (
input logic clk, rst_n,
input logic [7:0] tx_data,
input logic tx_valid,
output logic tx_ready,
output logic tx
);
typedef enum logic [2:0] {S_IDLE, S_START, S_DATA, S_STOP} state_t;
state_t state = S_IDLE;
logic [15:0] clk_cnt = 0;
logic [2:0] bit_idx = 0;
logic [7:0] data_reg = 0;
assign tx_ready = (state == S_IDLE);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE; tx <= 1'b1;
clk_cnt <= 0; bit_idx <= 0;
end else begin
case (state)
S_IDLE: begin
tx <= 1'b1;
if (tx_valid) begin
data_reg <= tx_data;
state <= S_START;
clk_cnt <= 0;
end
end
S_START: begin
tx <= 1'b0; // 起始位 = 低
if (clk_cnt >= CLKS_PER_BIT - 1) begin
clk_cnt <= 0; bit_idx <= 0;
state <= S_DATA;
end else clk_cnt <= clk_cnt + 1;
end
S_DATA: begin
tx <= data_reg[bit_idx];
if (clk_cnt >= CLKS_PER_BIT - 1) begin
clk_cnt <= 0;
if (bit_idx >= 7) state <= S_STOP;
else bit_idx <= bit_idx + 1;
end else clk_cnt <= clk_cnt + 1;
end
S_STOP: begin
tx <= 1'b1; // 停止位 = 高
if (clk_cnt >= CLKS_PER_BIT - 1) begin
state <= S_IDLE; clk_cnt <= 0;
end else clk_cnt <= clk_cnt + 1;
end
endcase
end
end
endmodule
UART 接收器
module uart_rx #(parameter CLKS_PER_BIT = 434) (
input logic clk, rst_n,
input logic rx,
output logic [7:0] rx_data,
output logic rx_valid,
output logic rx_error
);
typedef enum logic [1:0] {S_IDLE, S_START, S_DATA, S_STOP} state_t;
state_t state = S_IDLE;
logic [15:0] clk_cnt = 0;
logic [2:0] bit_idx = 0;
logic [7:0] data_reg = 0;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE; rx_valid <= 0; rx_error <= 0;
clk_cnt <= 0; bit_idx <= 0;
end else begin
rx_valid <= 0; // 默认脉冲
case (state)
S_IDLE: begin
rx_error <= 0;
if (rx == 1'b0) begin // 检测起始位
clk_cnt <= 0;
state <= S_START;
end
end
S_START: begin
// 半位后采样确认起始位
if (clk_cnt >= (CLKS_PER_BIT/2) - 1) begin
if (rx == 1'b0) begin
clk_cnt <= 0; bit_idx <= 0;
state <= S_DATA;
end else state <= S_IDLE; // 假起始位
end else clk_cnt <= clk_cnt + 1;
end
S_DATA: begin
if (clk_cnt >= CLKS_PER_BIT - 1) begin
clk_cnt <= 0;
data_reg[bit_idx] <= rx; // 中点采样
if (bit_idx >= 7) state <= S_STOP;
else bit_idx <= bit_idx + 1;
end else clk_cnt <= clk_cnt + 1;
end
S_STOP: begin
if (clk_cnt >= CLKS_PER_BIT - 1) begin
rx_valid <= 1'b1;
rx_error <= ~rx; // 停止位应为高
state <= S_IDLE;
end else clk_cnt <= clk_cnt + 1;
end
endcase
end
end
assign rx_data = data_reg;
endmodule
顶层集成带 FIFO
module uart_top (
input logic clk, rst_n,
input logic rx,
output logic tx,
output logic [7:0] led
);
logic [7:0] rx_data, tx_data;
logic rx_valid, tx_valid, tx_ready;
uart_rx #(.CLKS_PER_BIT(434)) u_rx (
.clk, .rst_n, .rx, .rx_data, .rx_valid, .rx_error()
);
uart_tx #(.CLKS_PER_BIT(434)) u_tx (
.clk, .rst_n, .tx_data(rx_data),
.tx_valid(rx_valid),
.tx_ready(tx_ready), .tx
);
always_ff @(posedge clk) begin
if (rx_valid) led <= rx_data;
end
endmodule
仿真验证
iverilog -g2012 -o uart_tb uart_tx.sv uart_rx.sv tb_uart.sv
vvp uart_tb
gtkwave uart.vcd
关键点
- 过采样:接收器在每位中点采样(起始位半位后开始),提高抗噪能力
- 假起始位过滤:检测到下降沿后半位再确认
- tx_ready 握手:发送前检查 ready,避免覆盖正在发送的数据
- 115200 @ 50MHz = 434 时钟/位,实际偏差 < 0.02%,可靠通信
Verilog 组合逻辑与时序逻辑:4 位计数器 + 七段显示
目标
用 Verilog 编写一个带使能和复位的 4 位计数器,配合七段数码管译码器,在 FPGA 上显示 0-F。
工具
- Icarus Verilog (仿真) + GTKWave (波形)
- 或任意 FPGA 开发板(本代码可直接综合)
一、七段显示译码器(组合逻辑)
module seven_seg_decoder (
input wire [3:0] bcd,
output reg [6:0] seg
);
always @(*) begin
case (bcd)
4'h0: seg = 7'b1000000; // "0"
4'h1: seg = 7'b1111001; // "1"
4'h2: seg = 7'b0100100; // "2"
4'h3: seg = 7'b0110000; // "3"
4'h4: seg = 7'b0011001; // "4"
4'h5: seg = 7'b0010010; // "5"
4'h6: seg = 7'b0000010; // "6"
4'h7: seg = 7'b1111000; // "7"
4'h8: seg = 7'b0000000; // "8"
4'h9: seg = 7'b0010000; // "9"
4'hA: seg = 7'b0001000; // "A"
4'hB: seg = 7'b0000011; // "b"
4'hC: seg = 7'b1000110; // "C"
4'hD: seg = 7'b0100001; // "d"
4'hE: seg = 7'b0000110; // "E"
4'hF: seg = 7'b0001110; // "F"
default: seg = 7'b1111111; // 全灭
endcase
end
endmodule
二、带使能的 4 位计数器(时序逻辑)
module counter_4bit (
input wire clk,
input wire rst_n,
input wire enable,
output reg [3:0] count,
output wire overflow
);
assign overflow = (count == 4'hF) && enable;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 4'h0;
end else if (enable) begin
count <= count + 4'h1;
end
end
endmodule
三、顶层模块(Top-Level)
module top_counter (
input wire clk_50mhz,
input wire rst_n,
output wire [6:0] seg,
output wire [3:0] an
);
wire [31:0] clk_div;
wire clk_slow;
wire [3:0] bcd_count;
parameter DIV = 25_000_000;
reg [24:0] div_counter = 0;
reg clk_slow_reg = 0;
always @(posedge clk_50mhz or negedge rst_n) begin
if (!rst_n) begin
div_counter <= 0;
clk_slow_reg <= 0;
end else if (div_counter >= DIV - 1) begin
div_counter <= 0;
clk_slow_reg <= ~clk_slow_reg;
end else begin
div_counter <= div_counter + 1;
end
end
assign clk_slow = clk_slow_reg;
counter_4bit u_counter (
.clk (clk_slow),
.rst_n (rst_n),
.enable (1'b1), // 始终计数
.count (bcd_count),
.overflow()
);
// ── 七段译码例化 ──
seven_seg_decoder u_decoder (
.bcd (bcd_count),
.seg (seg)
);
// 单管显示:位选常有效(共阳极时拉低)
assign an = 4'b0000;
endmodule
四、仿真 Testbench
`timescale 1ns / 1ps
module tb_counter;
reg clk;
reg rst_n;
reg enable;
wire [3:0] count;
wire overflow;
counter_4bit uut (
.clk (clk),
.rst_n (rst_n),
.enable (enable),
.count (count),
.overflow(overflow)
);
always #5 clk = ~clk; // 10ns 周期
initial begin
$dumpfile("counter.vcd");
$dumpvars(0, tb_counter);
clk = 0;
rst_n = 0;
enable = 1;
#20 rst_n = 1; // 释放复位
#200;
enable = 0;
#40;
$display("使能关闭:count = %d(应保持)", count);
enable = 1;
#200;
rst_n = 0;
#10 rst_n = 1;
$display("复位后:count = %d(应为 0)", count);
#100 $finish;
end
always @(posedge clk) begin
$display("t=%0tns | count=%d | overflow=%b", $time, count, overflow);
end
endmodule
五、运行步骤
iverilog -o counter_tb counter_4bit.v tb_counter.v
vvp counter_tb
gtkwave counter.vcd
六、关键点
| 概念 |
说明 |
| 组合逻辑 |
always @(*) / assign,输出仅取决于当前输入(七段译码器) |
| 时序逻辑 |
always @(posedge clk),输出取决于时钟沿(计数器) |
阻塞赋值 = |
用于组合逻辑,立即生效 |
非阻塞赋值 <= |
用于时序逻辑,同时更新,避免竞争 |
| 复位 |
negedge rst_n 实现异步复位 |
| 参数化 |
parameter 使模块可复用(如分频系数) |
⚠️ Verilog 的可综合子集有限:for 循环只在 generate 块中可综合,#delay 仅用于仿真。