Verilog / SystemVerilog

技术栈
其他
verilogsystemveriloghdlfpgaasic数字设计

概览

Verilog / SystemVerilog 技术栈概览

Verilog 是最广泛使用的硬件描述语言(HDL),由 Gateway Design Automation 于 1984 年创建,1995 年成为 IEEE 1364 标准。SystemVerilog 是其超级增强版(IEEE 1800),融合了设计、验证、断言、覆盖率等能力,是当代 FPGA/ASIC 开发的主流语言。

解决什么问题

  • 数字电路描述:从门级到行为级,以代码而非原理图描述数字逻辑
  • 验证效率:SystemVerilog 的 UVM 验证方法学使验证代码复用率大幅提升
  • 综合优化:可综合子集映射到 FPGA LUT/FF 或 ASIC 标准单元
  • 协同仿真:与 C/C++ 通过 DPI(Direct Programming Interface)互操作

关键特性

  • 层次化建模:模块(module)实例化,parameter 参数化设计
  • 并发语义:assign、always_comb/ff/latch 块并行执行,真实反映硬件并发
  • 验证增强:约束随机、功能覆盖率、断言(SVA)、interface 接口封装
  • 时序模拟#delay 和时钟边沿建模,testbench 编写灵活
  • 工具链成熟:Synopsys VCS、Cadence Xcelium、Mentor Questa,开源有 Verilator/Icarus

安装

Verilog / SystemVerilog 工具链安装指南

1. 环境准备

Verilog 开发需要仿真器(simulator)和综合器(synthesizer)。以下按用途推荐:

用途 开源方案 商业方案
仿真 Icarus Verilog (iverilog) + GTKWave Synopsys VCS / Cadence Xcelium / Mentor Questa
综合 Yosys Synopsys Design Compiler / Vivado
FPGA 开发 Project IceStorm (Lattice) / prjtrellis (ECP5) Vivado (Xilinx) / Quartus (Intel/Altera)
Lint Verilator (仿真用) / svlint SpyGlass
波形查看 GTKWave / Surfer Verdi / DVE

2. 安装步骤

开源工具链(快速上手)

# Ubuntu / Debian
sudo apt update
sudo apt install iverilog gtkwave yosys verilator

# macOS
brew install icarus-verilog gtkwave yosys verilator

# 验证
iverilog -V        # Icarus Verilog 版本
yowasp-yosys --version  # Yosys 版本(或 yosys)
verilator --version

Verilator(高性能仿真/SystemVerilog 部分支持)

git clone https://github.com/verilator/verilator.git
cd verilator
autoconf
./configure
make -j$(nproc)
sudo make install

FPGA 厂商工具链

Xilinx Vivado(免费 WebPACK)

  1. 访问 https://www.xilinx.com/support/download.html
  2. 下载 Vivado ML Standard Edition(需注册 AMD 账户)
  3. 安装时选择 WebPACK 许可证(免费,支持中小规模 FPGA)
  4. Windows/Linux 下载约 30-50GB

Intel Quartus Prime Lite

  1. 访问 https://www.intel.com/content/www/us/en/software/programmable/quartus-prime/download.html
  2. 下载 Quartus Prime Lite Edition(免费)
  3. 安装 ModelSim Intel FPGA Starter Edition(含仿真许可)

3. 常见安装问题

问题 解决方案
iverilog "syntax error" 但代码正确 Icarus 仅支持 Verilog-2001 子集,SystemVerilog 特性用 Verilator 或 VCS
verilator 编译慢 正常行为,使用 -j 多核加速;首次运行缓存
Vivado 下载慢 / 安装失败 使用下载管理器;web installer 比完整 ISO 更小
GTKWave 波形无信号 检查 $dumpvars 是否正确;仿真生成了 .vcd / .fst 文件
Yosys 不支持 SystemVerilog Yosys 仅支持可综合 Verilog 子集;用 sv2v 转换后综合

示例

SystemVerilog 进阶:UART 发送器 + 接收器(可综合)

目标

用 SystemVerilog 实现完整的 UART 收发器(8N1, 115200bps),可直接综合到 FPGA。

UART 发送器

// uart_tx.sv
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 接收器

// uart_rx.sv
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

// uart_top.sv
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 开发板(本代码可直接综合)

一、七段显示译码器(组合逻辑)

// seven_seg_decoder.v — 4 位 BCD → 七段显示(共阳极,低电平点亮)
module seven_seg_decoder (
    input  wire [3:0] bcd,       // 4-bit BCD (0-15)
    output reg  [6:0] seg         // 七段 {a,b,c,d,e,f,g} — 共阳:0=亮
);

    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 位计数器(时序逻辑)

// counter_4bit.v — 同步复位 + 使能的 4 位计数器
module counter_4bit (
    input  wire       clk,          // 时钟
    input  wire       rst_n,        // 异步复位(低有效)
    input  wire       enable,       // 计数使能
    output reg  [3:0] count,        // 计数值
    output wire       overflow      // 溢出标志(15→0)
);

    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
        // enable 为低时保持
    end

endmodule

三、顶层模块(Top-Level)

// top_counter.v — 顶层:计数器 + 分频 + 七段显示
module top_counter (
    input  wire       clk_50mhz,    // 50MHz 板载时钟
    input  wire       rst_n,        // 按键复位(低有效)
    output wire [6:0] seg,          // 七段管
    output wire [3:0] an            // 位选(单管共阳直接用 1 位)
);

    wire [31:0] clk_div;
    wire       clk_slow;
    wire [3:0] bcd_count;

    // ── 时钟分频(50MHz → ~2Hz)──
    // 50,000,000 / 2 = 25,000,000 cycles → 分频系数
    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

// tb_counter.v — 4 位计数器仿真
`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)
    );

    // 100MHz 时钟生成
    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;       // 释放复位

        // 观察 20 个计数周期
        #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 仅用于仿真。

教程

Verilog / SystemVerilog 数字设计入门

本章目标

从零开始理解数字电路设计思想,掌握可综合 Verilog 编码规范,完成 FPGA 基础实验。


1. 硬件描述语言的思维方式

软件 vs 硬件

概念 软件 (C/Python) 硬件 (Verilog)
执行 顺序执行 并发执行
变量 按时间变化 代表物理连线
循环 重复执行指令 展开为重复电路
"运行" CPU 执行指令 电路持续工作

关键转变

不要写代码,要描述电路。你写的每行 Verilog 都应能在脑中想象出对应的逻辑门/触发器。


2. 组合逻辑 vs 时序逻辑

组合逻辑

// 纯组合:输出仅取决于当前输入
assign sum = a + b;

always @(*) begin
    case (sel)
        2'b00: out = a;
        2'b01: out = b;
        2'b10: out = c;
        default: out = 0;
    endcase
end

时序逻辑

// 时序:输出取决于时钟沿
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        count <= 0;
    else if (enable)
        count <= count + 1;
end

3. 可综合编码规范

✅ 可以综合

// 阻塞赋值用于组合逻辑
always @(*) y = a & b;

// 非阻塞赋值用于时序逻辑
always @(posedge clk) q <= d;

// 有限状态机
always @(posedge clk) state <= next_state;

❌ 不可综合

#10 clk = ~clk;           // 延时
$display("hello");        // 系统任务
fork ... join             // 不可综合的并发
initial begin ... end     // 仅仿真

4. 状态机设计

Moore 型(输出仅取决于状态)

typedef enum logic [1:0] {
    IDLE, WORKING, DONE
} state_t;

state_t state = IDLE, next_state;

// 状态转移
always_comb begin
    next_state = state;
    case (state)
        IDLE:    if (start) next_state = WORKING;
        WORKING: if (finish) next_state = DONE;
        DONE:    if (ack) next_state = IDLE;
    endcase
end

// 输出逻辑
always_comb begin
    busy = (state == WORKING);
    done = (state == DONE);
end

5. 仿真测试平台

module tb;
    reg clk = 0, rst_n = 0;
    reg start = 0;
    wire busy, done;

    // DUT 例化
    my_module uut (.clk, .rst_n, .start, .busy, .done);

    always #5 clk = ~clk;  // 100MHz

    initial begin
        $dumpfile("tb.vcd");
        $dumpvars(0, tb);

        #10 rst_n = 1;
        #10 start = 1;
        #10 start = 0;

        // 等待 done
        @(posedge done);
        $display("Test passed at t=%0t", $time);

        #50 $finish;
    end
endmodule

6. FPGA 开发流程

设计输入 (Verilog)
  → 行为仿真 (iverilog/Modelsim)
  → 综合 (Yosys/Vivado)
  → 布局布线 (nextpnr/Vivado)
  → 时序仿真
  → 比特流生成
  → FPGA 烧录
  → 板级验证

思考题

  1. 解释 blocking vs non-blocking 赋值在综合后的差异
  2. 什么是亚稳态?如何避免?
  3. 为什么 FPGA 设计中需要约束文件(.xdc/.sdc)?

参考资料

  1. [1] IEEE. IEEE Standard for SystemVerilog (1800-2017). 2017. https://ieeexplore.ieee.org/document/8299595
  2. [2] Verilator Project. Verilator User Guide. 2024. https://verilator.org/guide/latest/
  3. [3] David Harris, Sarah Harris. Digital Design and Computer Architecture. 2022.
  4. [4] Pong P. Chu. FPGA Prototyping by Verilog Examples. 2018.
  5. [5] Sutherland, Davidmann, Flake. SystemVerilog for Design and Verification. 2022.