杰瑞科技汇

Verilog数字系统设计教程核心是什么?

Verilog数字系统设计综合教程

前言:为什么学习Verilog?

Verilog是一种用于描述电子系统硬件功能的硬件描述语言,与C、Java等软件编程语言不同,Verilog描述的是电路的结构、行为和数据流,它是现代数字电路设计的基石,是成为一名合格的数字IC设计工程师或FPGA工程师的必备技能。

Verilog数字系统设计教程核心是什么?-图1
(图片来源网络,侵删)

本教程将分为以下几个部分:

  1. 第一部分:基础入门 - 了解Verilog是什么,如何搭建环境,编写最简单的模块。
  2. 第二部分:核心语法 - 深入学习Verilog的数据类型、运算符和结构。
  3. 第三部分:行为建模 - 掌握always块,这是Verilog描述时序逻辑的关键。
  4. 第四部分:结构化建模 - 学习如何实例化模块,构建复杂的系统。
  5. 第五部分:高级主题 - 探讨测试平台、有限状态机、同步设计原则等。
  6. 第六部分:实践项目 - 通过一个完整的项目巩固所学知识。
  7. 第七部分:学习资源与工具推荐

第一部分:基础入门

1 什么是Verilog?

Verilog是一种用于对电子系统进行抽象描述的语言,你可以把它想象成“电路的C语言”,它允许你从不同层次描述电路:

  • 行为级: 描述电路的功能和算法,不关心具体实现。
  • RTL级 (Register-Transfer Level): 描述数据在寄存器之间的流动和变换,这是最常用、最核心的设计层次。
  • 门级: 描述由基本逻辑门(与、或、非等)构成的电路。
  • 开关级: 描述晶体管级别的连接。

2 开发环境

要进行Verilog设计,你需要两个基本工具:

  1. 文本编辑器:
    • 专业IDE: Verilog-Mode (Emacs/Vim插件), Visual Studio Code (配合Verilog插件), Synopsys VCS (商业, 集成度高)。
    • 轻量级: Sublime Text, Notepad++。
  2. 仿真器:
    • 开源: Icarus Verilog (iverilog) + GTKWave (波形查看器),这是初学者首选的免费组合。
    • 商业: ModelSim/Questa Simulator ( Mentor/Siemens EDA ), Xcelium ( Synopsys ), VCS ( Synopsys ),工业界标准,功能强大。

3 你的第一个Verilog程序:半加器

一个半加器有两个输入 AB,两个输出 Sum (和) 和 Cout (进位)。

Verilog数字系统设计教程核心是什么?-图2
(图片来源网络,侵删)
  • Sum = A ^ B (异或)
  • Cout = A & B (与)

代码示例: half_adder.v

// 这是一个半加器的Verilog模块定义
// module <module_name> (<port_list>);
module half_adder(
    input  A,      // 输入端口A
    input  B,      // 输入端口B
    output Sum,    // 输出端口Sum
    output Cout    // 输出端口Cout
);
// 描述逻辑功能
// assign语句用于组合逻辑,将右侧表达式的值赋给左侧的线网
assign Sum  = A ^ B;
assign Cout = A & B;
// 模块定义结束
endmodule

4 模块的基本结构

一个Verilog模块由以下几部分组成:

module module_name (
    // 1. 端口声明
    input wire in1,
    output reg out1,
    inout tri inout1
);
    // 2. 内部信号/变量/寄存器声明
    wire internal_wire;
    reg [7:0] data_reg; // 8位寄存器
    // 3. 功能描述
    // 可以是组合逻辑 (assign)
    // 也可以是时序逻辑 (always块)
    assign out1 = in1 & internal_wire;
    always @(posedge clk) begin
        if (reset)
            data_reg <= 8'b0;
        else
            data_reg <= in1;
    end
endmodule

第二部分:核心语法

1 数据类型

Verilog中最核心的数据类型是线网寄存器

  • wire: 用于表示物理连接,如导线,它不能存储值,必须通过assign语句或模块实例化来驱动。

    Verilog数字系统设计教程核心是什么?-图3
    (图片来源网络,侵删)
    wire w1, w2;
    assign w1 = a & b;
  • reg: 用于在always块中赋值的变量,它并不一定代表硬件上的寄存器,只是表示其值在always块中被重新赋值,在综合时,只有被时钟边沿触发的reg才会被综合成寄存器。

    reg r1;
    always @(posedge clk) begin
        r1 <= a & b; // 这个r1会被综合成寄存器
    end
  • integer, real: 用于仿真,一般不用于综合。

  • parameter: 用于定义常量,提高代码可读性和可维护性。

    parameter DATA_WIDTH = 8;
    reg [DATA_WIDTH-1:0] data_bus;

2 运算符

Verilog的运算符与C语言非常相似。

类别 运算符 描述 示例
算术 , , , , 加、减、乘、除、取模 assign c = a + b;
逻辑 &&, \\|\\|, 逻辑与、或、非 assign y = enable && (a > b);
按位 &, \\|, ^, 按位与、或、异或、取反 assign c = a & b;
缩减 &, \\|, ^ 将向量缩减为一位(与、或、异或) assign flag = &data_vector; // 所有位都为1时为1
关系 >, <, >=, <= 大于、小于、大于等于、小于等于 assign y = (a > b);
相等 , , , 逻辑相等/不等,全等/不全等 注意: 和 会比较X和Z,推荐在测试平台使用
移位 <<, >> 左移、右移 assign shifted = data << 2;
条件 三元运算符 assign y = (sel) ? a : b;

3 向量

多位数据用向量表示。

// 定义一个8位向量,MSB是7,LSB是0
wire [7:0] my_byte;
// 定义一个16位向量,MSB是15,LSB是0
reg [15:0] my_word;
// 可以使用位选和部分选择
assign my_byte[7:4] = my_word[15:12]; // 高4位赋值
assign my_byte[3:0] = my_word[3:0];   // 低4位赋值
// 也可以用 `:` 简化写法
assign my_byte = my_word[15:8]; // 等同于 my_byte[7:0] = my_word[15:8]

第三部分:行为建模 (always块)

always块是Verilog描述时序逻辑和复杂组合逻辑的核心。

1 always块的语法

always @(event_expression) begin
    // 顺序执行的语句
    // ...
end

event_expression (事件表达式) 决定了always块何时被触发。

2 组合逻辑的always

如果用于组合逻辑,event_expression通常是输入信号的列表,敏感列表。

重要: 必须在always块内为所有输出的reg类型变量赋值,否则会生成锁存器,这通常是设计错误。

示例:一个2选1多路选择器

module mux2to1(
    input [7:0] a, b,
    input       sel,
    output reg [7:0] y
);
// 敏感列表包含所有输入
// @(*) 是一种简写,表示所有在块内使用的信号都是敏感信号
always @(*) begin
    if (sel == 1'b1)
        y = a;
    else
        y = b;
end
endmodule

3 时序逻辑的always

如果用于时序逻辑,event_expression通常是时钟边沿。

  • posedge clk: 上升沿触发
  • negedge clk: 下降沿触发

示例:一个带同步复位端的D触发器

module d_ff (
    input      clk,
    input      reset, // 同步复位,高电平有效
    input      d,
    output reg q
);
// 在时钟的上升沿执行
always @(posedge clk) begin
    if (reset) // 如果复位信号有效
        q <= 1'b0; // 复位
    else
        q <= d;    // 否则,将d的值赋给q
end
endmodule

注意: 在时序逻辑中,我们使用非阻塞赋值 <=,它表示在时钟边沿到来时,将右侧的值“计划”给左侧,所有右侧的计算在同一时间点完成,这能避免仿真时的竞争条件,符合硬件行为。


第四部分:结构化建模

结构化建模就是通过实例化已存在的模块来构建更复杂的系统,这就像搭积木。

1 模块实例化

语法格式:<实例名> <模块名> (.<端口连接>);

示例:用两个半加器和一个或门构建一个全加器

首先定义或门 or_gate.v

module or_gate(
    input  a, b,
    output y
);
assign y = a | b;
endmodule

然后实例化模块来构建全加器 full_adder.v

module full_adder(
    input  a, b, cin,
    output sum, cout
);
// 内部线网,用于连接半加器和或门
wire w1, w2, w3;
// 实例化第一个半加器
// 实例名: ha1
// 模块名: half_adder
// 端口连接: (a->a, b->b, sum->w1, cout->w2)
half_adder ha1 (
    .a(a),
    .b(b),
    .sum(w1),
    .cout(w2)
);
// 实例化第二个半加器
half_adder ha2 (
    .a(w1),
    .b(cin),
    .sum(sum),
    .cout(w3)
);
// 实例化或门
or_gate og (
    .a(w2),
    .b(w3),
    .y(cout)
);
endmodule

第五部分:高级主题

1 测试平台

测试平台是用于验证设计模块是否正确的另一个Verilog模块,它不综合成硬件,只在仿真时使用。

测试平台的关键点:

  1. 实例化被测模块:
    full_adder my_dut ( .a(a_in), .b(b_in), .cin(cin_in), .sum(sum_out), .cout(cout_out) );
  2. 生成激励信号: 使用initial块或always块来产生输入信号。
  3. 监视输出信号: 使用$display$monitor在控制台打印结果,或使用$dumpfile$dumpvars生成波形文件,用GTKWave查看。

示例:全加器的测试平台 tb_full_adder.v

`timescale 1ns / 1ps // 定义时间尺度
module tb_full_adder();
// 定义内部变量作为激励和观测点
reg  a_in, b_in, cin_in;
wire sum_out, cout_out;
// 实例化被测模块
full_adder my_dut (
    .a(a_in),
    .b(b_in),
    .cin(cin_in),
    .sum(sum_out),
    .cout(cout_out)
);
// 生成激励的initial块
initial begin
    // 打开波形文件
    $dumpfile("full_adder.vcd");
    $dumpvars(0, tb_full_adder);
    // 测试用例1
    a_in = 0; b_in = 0; cin_in = 0;
    #10; // 等待10个时间单位
    // 测试用例2
    a_in = 0; b_in = 0; cin_in = 1;
    #10;
    // 测试用例3
    a_in = 0; b_in = 1; cin_in = 0;
    #10;
    // ... 更多测试用例 ...
    $display("Simulation finished.");
    $finish; // 结束仿真
end
// 监视输出
initial begin
    $monitor("Time = %0t, a=%b, b=%b, cin=%b, sum=%b, cout=%b", 
             $time, a_in, b_in, cin_in, sum_out, cout_out);
end
endmodule

2 有限状态机

FSM是数字系统设计的核心,用于控制复杂的操作序列,它由状态状态寄存器组合逻辑(次态逻辑和输出逻辑)组成。

FSM有两种类型:

  • Moore型: 输出只取决于当前状态。
  • Mealy型: 输出取决于当前状态和输入。

设计步骤:

  1. 分析问题: 确定需要哪些状态。
  2. 画状态转移图: 清晰地表示状态转移条件和输出。
  3. 状态编码: 为每个状态分配一个唯一的二进制码。
  4. 写出Verilog代码:
    • 定义reg来存储当前状态和次态。
    • 用一个always块(在时钟边沿)来更新状态。
    • 用另一个always块(组合逻辑)或assign来计算次态和输出。

3 同步设计原则

为了设计稳定、可靠的数字系统,必须遵循同步设计原则:

  1. 全局时钟: 整个系统由一个主时钟驱动,所有时序逻辑都在该时钟的边沿触发。
  2. 单时钟沿: 尽量只使用时钟的单一沿(通常是上升沿)来触发所有寄存器。
  3. 避免异步信号: 外部输入的信号通常是异步的,必须同步到系统时钟域,最简单的方法是使用两级触发器同步器。
    reg [1:0] sync_reg;
    always @(posedge clk) begin
        sync_reg[0] <= async_in;
        sync_reg[1] <= sync_reg[0];
    end
    // 使用 sync_reg[1] 作为同步后的信号
  4. 注意建立和保持时间: 这是时序分析的基础,确保数据在时钟有效沿到来前稳定足够长时间(建立时间),并在沿到来后保持稳定足够长时间(保持时间)。

第六部分:实践项目

项目:设计一个简单的数字时钟

功能:

  1. 显示时、分、秒。
  2. 有一个复位键,可以将时钟复位到00:00:00。
  3. 有一个使能键,可以暂停/继续计时。

设计思路:

  1. 顶层模块 (digital_clock):

    • 输入: clk (1Hz时钟), reset, enable
    • 输出: hour[5:0], minute[6:0], second[6:0] (7位二进制可以表示0-99)。
    • 内部实例化三个计数器模块。
  2. 计数器模块 (counter):

    • 参数化,可以设置最大计数值。
    • 输入: clk, reset, enable
    • 输出: count
    • 功能: 当enable为高时,在clk的上升沿进行计数,当计数值达到最大值时清零并产生进位信号。
  3. 连接逻辑:

    • 秒计数器的进位连接到分计数器的enable
    • 分计数器的进位连接到时计数器的enable

这个项目能很好地练习模块化设计参数化模块时序逻辑


第七部分:学习资源与工具推荐

书籍

  • 入门经典:
    • 《Verilog HDL入门》 - J. Bhasker:非常适合初学者,语言通俗易懂,例子丰富。
  • 进阶圣经:
    • 《Verilog HDL高级数字设计》 - Clive "Max" Maxfield:非常有趣,从工程实践角度出发,讲解了很多设计技巧和陷阱。
    • 《Digital Design and Computer Architecture》 - Harris & Harris:结合了数字设计和计算机体系结构,讲解MIPS处理器设计,非常经典。

在线资源

  • Nandland: https://www.nandland.com/ - 提供非常棒的Verilog和FPGA入门教程,互动性强。
  • ASIC World: http://www.asic-world.com/verilog/ - 一个非常全面的Verilog语法参考手册。
  • Coursera / edX: 搜索 "Digital Design" 或 "Computer Architecture",有很多名校的优质课程。
  • FPGA厂商官方文档: Xilinx (AMD) 和 Intel (Altera) 提供了海量的官方教程、App Notes和示例代码,是解决实际问题的最佳资源。

工具

  • 仿真与波形查看:
    • Icarus Verilog + GTKWave: 免费,跨平台,命令行操作,适合学习底层原理。
    • Verilator: 开源,将Verilog转成C++模型,速度快,适合大型项目验证。
    • ModelSim/QuestaSim: 工业界标准,图形界面友好,调试功能强大(学生版免费但有功能限制)。
  • FPGA开发套件:
    • Xilinx Vivado / Vitis: 用于Xilinx FPGA开发。
    • Intel Quartus Prime: 用于Intel FPGA开发。
    • Lattice Diamond: 用于Lattice FPGA开发。

学习建议

  1. 动手实践: 不要只看不练,跟着教程敲代码,然后自己修改、扩展功能。
  2. 从简单开始: 先实现一个门,再做一个多路选择器,然后是一个计数器,最后再挑战复杂的FSM或CPU。
  3. 仿真先行: 每写一个模块,都为其编写一个测试平台,仿真验证是数字设计的核心环节。
  4. 理解综合: 思考你写的Verilog代码最终会生成什么样的硬件电路,使用综合工具(如Vivado)查看RTL视图和门级视图,这会让你对硬件有更直观的认识。
  5. 阅读优秀代码: 阅读开源项目(如RISC-V CPU核心)的代码,学习别人的设计风格和技巧。

祝你学习顺利,早日成为一名优秀的数字设计工程师!

分享:
扫描分享到社交APP
上一篇
下一篇