杰瑞科技汇

Verilog语言入门难吗?如何快速掌握?

Verilog 语言入门教程

第一章:初识 Verilog

1 什么是 Verilog?

想象一下,传统电路设计是工程师拿着铅笔和图纸,在面包板上焊接成千上万个电子元件,这个过程非常耗时且容易出错。

Verilog语言入门难吗?如何快速掌握?-图1
(图片来源网络,侵删)

Verilog 是一种用于硬件描述语言,你可以把它理解为“电路的编程语言”,它不是用来写软件程序(比如计算器、游戏),而是用来描述数字电路的结构和行为

  • 类比
    • C/C++ 语言:描述计算机的软件行为(执行计算、控制流程)。
    • Verilog 语言:描述数字电路的硬件行为(信号如何传输、逻辑如何运算)。

通过 Verilog,我们可以用代码的形式来设计一个复杂的芯片(CPU、内存控制器),然后通过工具(EDA工具)自动将代码转换为实际的电路图,并最终制造出芯片,这个过程被称为RTL(Register-Transfer Level,寄存器传输级)设计

2 Verilog 的应用领域

Verilog 广泛应用于:

  • ASIC (专用集成电路) 设计:为特定功能定制的芯片。
  • FPGA (现场可编程门阵列) 开发:可反复编程的逻辑芯片,常用于原型验证、通信、图像处理等领域。
  • 数字电路教学:是学习数字逻辑和计算机体系结构的必备工具。

第二章:第一个 Verilog 程序:与门

让我们从一个最简单的数字逻辑元件——与门 开始。

Verilog语言入门难吗?如何快速掌握?-图2
(图片来源网络,侵删)

1 与门的行为

与门有两个输入和一个输出,其逻辑功能是:只有当两个输入都为 1 时,输出才为 1

输入 A 输入 B 输出 Y
0 0 0
0 1 0
1 0 0
1 1 1

2 Verilog 代码实现

下面是用 Verilog 描述的与门代码:

// 文件名: and_gate.v
// 1. 模块定义:这是 Verilog 的基本构建块
// module <模块名> (<端口列表>);
module and_gate (
    input  a,  // 定义一个名为 a 的输入端口
    input  b,  // 定义一个名为 b 的输入端口
    output y   // 定义一个名为 y 的输出端口
);
// 2. 逻辑描述:这里使用 assign 语句来持续地描述输入和输出的关系
// assign <输出信号> = <逻辑表达式>;
assign y = a & b; // & 是 Verilog 中的按位与操作符
// 3. 模块定义结束
endmodule

3 代码解析

  1. module ... endmodule:所有的 Verilog 代码都必须包含在一个 module 块中。module 就像是一个“黑盒子”,它定义了电路的对外接口(输入、输出)。
  2. input a, b:声明 ab 是输入端口,它们可以接收外部的信号。
  3. output y:声明 y 是输出端口,它将向外部输出信号。
  4. assign y = a & b;:这是最核心的部分。
    • assign 关键字表示这是一个持续赋值语句,这意味着只要 ab 发生变化,y 的值就会立即重新计算并更新,这完美地模拟了硬件中导线的连接关系。
    • & 是 Verilog 的按位与操作符。a & b 就实现了与门的逻辑功能。

第三章:Verilog 的核心语法要素

1 模块

我们已经见过模块了,它是 Verilog 的基本单元。

module my_circuit (
    input  in1, in2,
    output out1
);
    // 内部逻辑描述
endmodule

2 信号类型

除了 inputoutput,还有一种在模块内部使用的信号,叫做 wire(线网)。

Verilog语言入门难吗?如何快速掌握?-图3
(图片来源网络,侵删)
  • wire:它代表物理上的“一根导线”,它不能存储值,只能传递信号。assign 语句的输出默认就是 wire 类型。
    wire temp_signal; // 定义一个 wire 类型的内部信号
    assign temp_signal = a ^ b; // temp_signal 的值由 a 和 b 的异或结果决定
  • reg:代表“寄存器”,它可以存储值,在 always 块(后面会讲)中赋值的变量必须是 reg 类型,它不一定对应于物理上的触发器,但通常用于时序逻辑。
    reg counter; // 定义一个 reg 类型的变量,可以用来计数

3 操作符

Verilog 提供了丰富的操作符,类似于 C 语言。

类别 操作符 描述 示例
算术 , , , 加、减、乘、除 assign c = a + b;
逻辑 &&, \\|\\|, 逻辑与、或、非 assign y = enable && (data == 8'hFF);
按位 &, \\|, ^, 按位与、或、异或、取反 assign y = a & b; (与门)
关系 >, <, >=, <= 大于、小于等 assign flag = (a > b);
相等 , 相等、不等 assign match = (data == pattern);
缩减 &, \\|, ^ 将向量缩减为一位(与、或、异或) assign all_ones = &{4'b1111}; // 结果为 1

4 常量和变量

  • 常量
    • 二进制:4'b1010 (4位二进制数 1010)
    • 十进制:16 (默认是32位)
    • 十六进制:8'hFF (8位十六进制数 FF)
  • 变量
    • reg [7:0] my_byte; // 定义一个8位的 reg 类型变量
    • wire [3:0] four_bits; // 定义一个4位的 wire 类型变量

第四章:更复杂的结构:always

assign 语句非常适合组合逻辑(门电路),但对于时序逻辑(如触发器、寄存器),我们需要使用 always 块。

always 块描述的是在特定条件下发生的行为

1 D 触发器

D 触发器是最基本的时序元件,它在时钟的上升沿,将输入 D 的值“锁存”到输出 Q

时钟 Clk 输入 D 输出 Q (上一时钟沿的值)
0 (保持)
1 (保持)

Verilog 实现:

// 文件名: d_flip_flop.v
module d_flip_flop (
    input      clk,    // 时钟信号
    input      rst_n,  // 异步复位信号,低电平有效
    input      d,      // 数据输入
    output reg q       // 数据输出,必须是 reg 类型,因为它在 always 块中被赋值
);
    // always @(<敏感事件列表>):当列表中的事件发生时,块内的代码被执行
    always @(posedge clk or negedge rst_n) begin
        // @posedge clk:在 clk 的上升沿触发
        // @negedge rst_n:在 rst_n 的下降沿触发(异步复位优先级更高)
        if (!rst_n) begin
            // 如果复位信号有效 (rst_n == 0)
            q <= 1'b0; // 将 q 复位为 0
        end else begin
            // 否则,在时钟上升沿
            q <= d;     // 将 d 的值赋给 q
        end
    end
endmodule

2 代码解析

  1. always @(posedge clk or negedge rst_n):这是 always 块的触发条件。
    • posedge clk:表示当 clk 信号从 0 变为 1(上升沿)时,执行 begin...end 中的代码。
    • negedge rst_n:表示当 rst_n 信号从 1 变为 0(下降沿)时,也执行代码,这种“或”关系意味着两个事件任意一个发生都会触发。
  2. if (!rst_n)rst_n 是低电平有效复位,所以当 rst_n 为 0 时,!rst_n 为真,执行复位操作。
  3. q <= d;<=非阻塞赋值操作符,这是在 always 块中描述时序逻辑的标准方式。
    • 阻塞赋值 :立即执行,像 C 语言的赋值。
    • 非阻塞赋值 <=:在当前时间步结束时才执行赋值,这确保了所有右侧表达式先计算完毕,然后左侧的变量才同时更新,避免了仿真时的竞争问题,是编写可综合时序逻辑的关键。

第五章:模块实例化

当你的设计变得复杂时,你会将大功能拆分成多个小模块,然后将这些小模块像搭积木一样组合起来,这个过程就是模块实例化

1 实例化一个与门

假设我们有一个 and_gate 模块,我们想在另一个模块 top_module 中使用它。

// 文件名: top_module.v
// 假设我们已经定义了 and_gate.v
module top_module (
    input  in1, in2, in3,
    output out
);
    // 1. 声明内部 wire
    wire w1;
    // 2. 实例化 and_gate 模块
    // <实例名> <模块名> ( .<端口名>(<连接信号>), ... );
    and_gate u_and_gate1 (
        .a(in1),    // 将 u_and_gate1 的 a 端口连接到 in1
        .b(in2),    // 将 u_and_gate1 的 b 端口连接到 in2
        .y(w1)      // 将 u_and_gate1 的 y 端口连接到内部信号 w1
    );
    // 3. 再实例化一个 and_gate
    and_gate u_and_gate2 (
        .a(w1),     // 将第一个与门的输出作为第二个与门的输入
        .b(in3),
        .y(out)     // 最终输出
    );
endmodule

这段代码描述了一个电路:out = (in1 & in2) & in3,通过实例化,我们轻松地复用了 and_gate 模块。


第六章:测试平台 - 验证你的设计

写好了代码,怎么知道它是对是错?我们需要一个测试平台来模拟输入信号,并观察输出是否符合预期。

测试平台本身也是一个 Verilog 模块,但它通常不会被综合成硬件,只用于仿真。

1 为与门创建测试平台

// 文件名: tb_and_gate.v
`timescale 1ns / 1ps // 定义时间尺度,1纳秒为基本单位,精度1皮秒
module tb_and_gate();
    // 1. 定义内部信号,用于连接被测模块
    reg  a_in, b_in;
    wire y_out;
    // 2. 实例化被测模块
    // 因为是测试,我们不需要输入输出端口,所以直接连接内部信号
    and_gate uut ( // uut 是 Unit Under Test 的缩写
        .a(a_in),
        .b(b_in),
        .y(y_out)
    );
    // 3. 生成激励信号
    initial begin // initial 块中的代码从仿真开始时执行一次
        // 打印仿真开始信息
        $display("Simulation Start");
        // 测试用例 1: a=0, b=0
        a_in = 0; b_in = 0;
        #10; // 延时 10 个时间单位 (10ns)
        $display("a=0, b=0, y=%b", y_out); // 打印输出结果
        // 测试用例 2: a=0, b=1
        a_in = 0; b_in = 1;
        #10;
        $display("a=0, b=1, y=%b", y_out);
        // 测试用例 3: a=1, b=0
        a_in = 1; b_in = 0;
        #10;
        $display("a=1, b=0, y=%b", y_out);
        // 测试用例 4: a=1, b=1
        a_in = 1; b_in = 1;
        #10;
        $display("a=1, b=1, y=%b", y_out);
        // 结束仿真
        $display("Simulation End");
        $finish; // 调用 $finish 结束仿真
    end
endmodule

2 测试平台代码解析

  1. initialinitial 块在仿真开始时执行一次,非常适合用于生成测试激励。
  2. #10:表示延时 10 个时间单位(根据 timescale 定义,这里是 10ns)。
  3. $display:类似于 C 语言的 printf,用于在仿真控制台打印信息。
  4. $finish:结束当前仿真。

运行这个测试平台,你会在控制台看到与门真值表对应的输出,从而验证你的设计是正确的。


第七章:总结与下一步

1 本章要点回顾

  • Verilog 是硬件描述语言,用于设计数字电路。
  • module 是基本单元,定义了电路的接口。
  • assign 用于描述组合逻辑(如门电路)。
  • always 块用于描述时序逻辑(如触发器),使用 posedge/negedge 检测时钟边沿。
  • 非阻塞赋值 <= 是编写时序逻辑的标准。
  • 模块实例化 是模块化设计的基础。
  • 测试平台 是验证设计正确性的必要工具。

2 推荐学习路径

  1. 掌握基础语法:熟练使用 assign, always, wire, reg, 各种操作符。
  2. 学习常用电路:用 Verilog 实现各种组合逻辑(多路选择器、编码器、译码器)和时序逻辑(计数器、移位寄存器、状态机)。
  3. 学习仿真工具:学会使用 ModelSim、Vivado Simulator 或 Quartus Prime 等工具进行代码仿真和波形查看。波形调试是数字设计的核心技能。
  4. 学习综合工具:了解 Synplify、Vivado、Quartus 等综合工具如何将 Verilog 代码转换为门级网表。
  5. 学习 FPGA 开发:选择一块开发板(如 Xilinx Artix-7 或 Intel Cyclone IV),学习如何将综合后的设计下载到 FPGA 中,并使用板载资源(LED、按键、七段管)进行验证。

3 推荐资源

  • 书籍
    • 《Verilog HDL高级数字设计》 - Ashenden (经典,内容深入)
    • 《数字设计和计算机体系结构》 - Harris (结合了 Verilog 和计算机体系结构,非常适合入门)
  • 在线课程
    • Coursera / edX:搜索 "Digital Logic Design" 或 "Computer Architecture" 相关课程。
    • YouTube:有很多优秀的 Verilog 教学频道。
  • 工具
    • 仿真器:ModelSim (工业标准), Xilinx Vivado Simulator (免费)
    • FPGA 开发套件:Xilinx Vivado (免费版), Intel Quartus Prime (免费版)

希望这份入门教程能帮助你顺利开启 Verilog 的学习之旅!祝你学习愉快!

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