杰瑞科技汇

Verilog HDL教程该怎么学?

Verilog HDL 综合教程

目录

  1. 第一部分:入门基础

    Verilog HDL教程该怎么学?-图1
    (图片来源网络,侵删)
    • 什么是 Verilog HDL?
    • 为什么学习 Verilog HDL?
    • 硬件描述语言 vs. 软件编程语言
    • 开发流程:从代码到硬件
    • 第一个程序:Hello, World! 的 Verilog 版本
  2. 第二部分:Verilog 基础语法

    • 模块
    • 端口
    • 数据类型
    • 操作符
    • 基本语句:assignalways
  3. 第三部分:行为建模与结构建模

    • 结构建模:像搭积木一样设计
    • 行为建模:像写 C 语言一样描述功能
    • always 块详解:@posedge, @negedge,
    • begin...endfork...join
    • 条件语句:if-elsecase
    • 循环语句:for, while, repeat, forever
  4. 第四部分:高级主题与关键概念

    • regwire 的深入理解
    • 时序逻辑 vs. 组合逻辑
    • 阻塞赋值 () vs. 非阻塞赋值 (<=)
    • initial 块:用于测试和仿真
    • 参数化设计:parameterlocalparam
    • 任务 (task) 和函数 (function)
  5. 第五部分:测试平台

    Verilog HDL教程该怎么学?-图2
    (图片来源网络,侵删)
    • 什么是测试平台?
    • 如何编写一个简单的测试平台
    • 生成激励信号
    • 使用 $display, $monitor 进行调试
  6. 第六部分:实践项目与进阶

    • 基本逻辑门
    • 组合逻辑电路 - 多路选择器
    • 时序逻辑电路 - D 触发器
    • 计数器
    • 状态机
    • 进阶方向:SystemVerilog, UVM, 时序分析
  7. 第七部分:学习资源与工具推荐


第一部分:入门基础

什么是 Verilog HDL?

Verilog HDL (Hardware Description Language) 是一种用于电子系统级 和寄存器传输级 设计的硬件描述语言,它允许设计师用文本代码来描述数字逻辑电路的结构和行为,就像用 C 语言描述软件算法一样。

为什么学习 Verilog HDL?

  • 抽象层次高:相比于手动绘制成千上万个逻辑门电路图,使用 HDL 可以在更高层次上进行设计,大大提高了设计效率和可读性。
  • 可移植性强:同一份 Verilog 代码可以被综合成不同厂商(如 Intel/Altera, Xilinx, Lattice)的 FPGA 器件,或者用于 ASIC 芯片流片。
  • 自动化设计流程:通过 EDA (Electronic Design Automation) 工具,可以自动完成从代码到电路网表、布局布图的复杂过程。
  • 行业标准:Verilog 和 VHDL 是数字 IC 设计和 FPGA 开发领域的两大主流语言,是相关岗位的必备技能。

硬件描述语言 vs. 软件编程语言

特性 Verilog HDL 软件编程语言 (如 C/C++)
执行模型 并行执行:所有语句在仿真时同时执行。 顺序执行:语句按顺序一行一行执行。
赋值操作 非阻塞赋值 (<=):更新发生在仿真时间步的末尾,影响的是“下一个”状态。 赋值 ():立即生效。
变量类型 wire (物理连接), reg (存储单元) int, char, float
目标 描述硬件电路的结构和时序。 描述算法软件行为

开发流程:从代码到硬件

一个典型的 Verilog 设计流程如下:

Verilog HDL教程该怎么学?-图3
(图片来源网络,侵删)
  1. 设计:使用 Verilog 编写代码,描述电路功能。
  2. 仿真:编写测试平台,验证代码功能是否正确,这一步称为功能仿真,不考虑硬件延迟。
  3. 综合:使用综合工具(如 Vivado, Quartus, Synopsys DC)将 Verilog 代码转换成由基本逻辑门(与、或、非、触发器等)构成的网表
  4. 实现:针对特定 FPGA 器件,进行布局布线,将网表映射到具体的硬件资源上。
  5. 时序仿真:考虑实际硬件的延迟,再次进行仿真,验证电路在高速下是否能正常工作。
  6. 下载/编程:将最终配置文件下载到 FPGA 芯片中,硬件开始工作。

第一个程序:Hello, World! 的 Verilog 版本

在软件中,"Hello, World!" 是打印一句话,在 Verilog 中,我们通过仿真在控制台打印一句话来验证环境。

// 这是一个注释,类似于 C++ 的 //
// `timescale 1ns / 1ps 定义仿真的时间尺度
// 1ns 为基本时间单位,1ps 为精度
`timescale 1ns / 1ps
module hello_world;
  // initial 块中的代码从仿真时间 0 开始执行一次
  initial begin
    // $display 是一个系统任务,用于在控制台打印信息
    // \n 表示换行
    $display("Hello, Verilog World!\n");
    // $finish 是一个系统任务,用于结束仿真
    $finish;
  end
endmodule

第二部分:Verilog 基础语法

模块

模块是 Verilog 的基本构成单元,它代表一个具有特定功能的硬件电路,比如一个加法器、一个 FIFO 或一个 CPU。

module module_name (
    // 端口列表
    input wire port1,
    output reg port2,
    inout port3
);
    // 内部信号和变量声明
    wire internal_signal;
    // 逻辑描述
    assign internal_signal = port1;
    always @(*) begin
        port2 = internal_signal;
    end
endmodule

端口

端口是模块与外部世界的接口,分为三种:

  • input:输入端口,信号从外部流入模块。
  • output:输出端口,信号从模块内部流向外部。
  • inout:双向端口,信号可以双向流动。

数据类型

  • wire:最常用的类型,表示物理上的连线,它不能存储值,只能被连续赋值语句(如 assign)或模块实例的输出驱动。wire 没有被驱动,其值为高阻态 z

    wire a, b, c;
    assign c = a & b; // c 是 a 和 b 的与
  • reg:听起来像寄存器,但它的含义是“可以保持值的变量”,它只能在 always 块或 initial 块中被赋值,它不一定是物理寄存器,综合工具会根据上下文判断它最终是组合逻辑还是时序逻辑。

    reg out;
    always @(*) begin
        out = in1 ^ in2; // out 是 in1 和 in2 的异或
    end

操作符

Verilog 提供了丰富的操作符,与 C 语言非常相似。

  • 算术操作符:, , , ,
  • 位操作符: (按位取反), & (按位与), (按位或), ^ (按位异或)
  • 逻辑操作符&& (逻辑与), (逻辑或), (逻辑非)
  • 关系操作符>, <, >=, <=
  • 相等操作符: (逻辑相等, 包含 x,z), (全等, 严格比较)
  • 移位操作符<< (左移), >> (右移)
  • 拼接操作符:,将多个信号拼接成一个宽信号。
    {a, b, c} // 将 a, b, c 拼接成一个更宽的信号

基本语句:assignalways

  • assign (连续赋值):用于描述组合逻辑,只要右边的表达式发生变化,左边的 wire 就会立即被重新赋值。

    assign out = in1 & in2; // 一个与门
  • always (过程块):用于描述更复杂的逻辑,包括组合逻辑和时序逻辑,它需要一个事件控制表达式(如 或 @posedge clk)来触发块内的代码执行。

    // 描述一个组合逻辑
    always @(*) begin // @(*) 表示对括号内所有信号的变化敏感
        out = in1 ^ in2;
    end
    // 描述一个时序逻辑 (D触发器)
    always @(posedge clk) begin // 只在 clk 的上升沿触发
        q <= d; // 非阻塞赋值,用于时序逻辑
    end

第三部分:行为建模与结构建模

结构建模

通过实例化已经存在的模块来构建更大的电路,就像搭乐高积木。

// 假设我们已经有一个 2 输入与门模块
module and_gate (input a, input b, output y);
  assign y = a & b;
endmodule
// 现在我们用两个 and_gate 模块搭建一个半加器
module half_adder (
    input a, b,
    output sum, cout
);
  wire w1;
  // 实例化第一个与门
  and_gate g1 (.a(a), .b(b), .y(w1));
  // 实例化第二个与门
  and_gate g2 (.a(a), .b(b), .y(cout));
  // ... 异或逻辑可以用 assign 实现
  assign sum = a ^ b;
endmodule

行为建模

通过高级语言结构(如 if-else, case, for 循环)来描述电路的功能,而不关心其内部的具体门级实现,综合工具会将其“翻译”成实际的硬件电路。

always 块详解

  • 隐式敏感列表,自动包含块内所有用到的输入信号,这是描述组合逻辑的标准方式。
  • @posedge clk:对时钟的上升沿敏感,这是描述时序逻辑(触发器、寄存器)的标准方式。
  • @negedge clk:对时钟的下降沿敏感。

begin...endfork...join

  • begin...end:顺序块,块内的语句按顺序执行(在仿真时间上)。
  • fork...join:并行块,块内的语句同时开始执行。
initial begin
  $display("Sequential block starts.");
  #10 $display("Time 10");
  #20 $display("Time 30"); // 总共在 30ns 时执行
end
initial fork
  $display("Parallel block starts.");
  #10 $display("Time 10");
  #20 $display("Time 20"); // 总共在 20ns 时执行,因为两个事件并行开始
join

条件语句:if-elsecase

  • if-else:用于描述多路选择逻辑。

    always @(*) begin
      if (sel == 2'b00)
        out = a;
      else if (sel == 2'b01)
        out = b;
      else
        out = c;
    end
  • case:当条件是多个互斥的值时,case 语句更清晰。

    always @(*) begin
      case (sel)
        2'b00: out = a;
        2'b01: out = b;
        2'b10: out = c;
        default: out = 1'b0; // default 是一个好习惯
      endcase
    end

循环语句:for, while, repeat, forever

这些语句主要用于测试平台,在 RTL 设计中很少使用(因为硬件中不存在真正的“循环”概念,而是通过流水线或状态机来实现类似功能)。

// 在测试平台中生成一个时钟
initial begin
  clk = 0;
  forever #5 clk = ~clk; // 每 5 个时间单位翻转一次 clk
end

第四部分:高级主题与关键概念

regwire 的深入理解

  • wire:代表物理连接,是数据流的通道,它必须被驱动(通过 assign 或另一个模块的输出),如果未被驱动,则为高阻 z
  • reg:代表一个可以存储值的存储单元,它的值只能在 alwaysinitial 块中改变,综合工具会根据 always 块的敏感列表来决定它最终是组合逻辑()还是时序逻辑(@posedge clk)。

时序逻辑 vs. 组合逻辑

  • 组合逻辑:输出只取决于当前的输入,没有记忆功能,与门、或门、多路选择器。
    • Verilog 描述assign 语句或 always @(*) 块。
  • 时序逻辑:输出不仅取决于当前的输入,还取决于电路之前的状态(即存储的值),有记忆功能,触发器、寄存器、计数器。
    • Verilog 描述always @(posedge clk)always @(negedge clk) 块。

阻塞赋值 () vs. 非阻塞赋值 (<=)

这是 Verilog 中最重要、最容易出错的概念。

特性 阻塞赋值 () 非阻塞赋值 (<=)
执行方式 顺序执行,先执行完当前 always 块内的所有 赋值,再执行下一句。 并行执行always 块内所有 <= 赋值语句的右侧表达式被同时计算,然后同时更新到左侧的寄存器。
用途 组合逻辑,描述组合逻辑的中间变量传递。 时序逻辑,描述在时钟边沿发生的、并行的状态更新。
比喻 C 语言的赋值。 多个工人同时根据同一张图纸(当前状态)建造房子(下一状态)。
规则 always @(*) 中使用。 always @(posedge clk) 中使用。

错误示例(混合使用):

// 这是一个错误的写法!会导致仿真和综合结果不一致
always @(posedge clk) begin
  q = d;        // 使用了阻塞赋值
  q_bar = ~q;   // 这里的 q 已经是 d 的值了,而不是旧的 q 值
end

正确示例(时序逻辑):

// 正确的 D 触发器写法
always @(posedge clk) begin
  q <= d;        // 非阻塞赋值
  q_bar <= ~d;   // d 是当前输入,q 和 q_bar 都基于 d 的当前值更新
end

initial

initial 块中的代码从仿真时间 0 开始执行一次,然后就结束了,它主要用于测试平台的初始化和产生激励信号,不能被综合成实际的硬件电路。

参数化设计

使用 parameter 可以让模块更具通用性。

module adder #(
    parameter WIDTH = 8 // 默认位宽为 8
) (
    input [WIDTH-1:0] a, b,
    output [WIDTH:0] sum // 和可能多一位
);
  assign sum = a + b;
endmodule
// 实例化一个 16 位的加法器
adder #(.WIDTH(16)) my_adder (...);

任务 (task) 和函数 (function)

用于将代码模块化,提高可读性和复用性。

  • task:可以有输入、输出和双向端口,可以消耗时间(包含 延迟或 事件控制)。
  • function:只有一个返回值,不能包含时间控制,不能调用 task

第五部分:测试平台

测试平台是验证 RTL 代码正确性的环境本身也是一个 Verilog 模块,但它通常不会被综合到硬件中。

// 待测试的模块
module simple_counter (
    input clk,
    input reset,
    output reg [3:0] count
);
  always @(posedge clk or posedge reset) begin
    if (reset)
      count <= 4'b0;
    else
      count <= count + 1;
  end
endmodule
// 测试平台
module tb_simple_counter;
  // 定义信号
  reg clk, reset;
  wire [3:0] count;
  // 实例化待测模块
  simple_counter uut (
    .clk(clk),
    .reset(reset),
    .count(count)
  );
  // 生成时钟
  initial begin
    clk = 0;
    forever #5 clk = ~clk; // 10ns 的时钟周期
  end
  // 生成激励
  initial begin
    // 初始化
    reset = 1;
    #20; // 等待 20ns
    // 释放复位
    reset = 0;
    // 运行一段时间观察结果
    #100;
    // 再次复位
    reset = 1;
    #20;
    // 结束仿真
    $finish;
  end
  // 监控信号变化
  initial begin
    $monitor("Time = %0t, Reset = %b, Count = %d", $time, reset, count);
  end
endmodule

第六部分:实践项目与进阶

基本逻辑门

  • 目标:实现与门、或门、非门、异或门。
  • 方法:使用 assign 语句。

组合逻辑电路 - 多路选择器

  • 目标:实现一个 4 选 1 的多路选择器。
  • 方法:使用 assign 和条件操作符 或 case 语句。

时序逻辑电路 - D 触发器

  • 目标:实现一个带异步复位的 D 触发器。
  • 方法:使用 always @(posedge clk or posedge reset) 块和非阻塞赋值

计数器

  • 目标:实现一个模 10 的计数器(0-9)。
  • 方法:在 D 触发器的基础上,增加反馈逻辑。

状态机

这是数字设计的核心。

  • 目标:实现一个简单的交通灯控制器。
  • 方法
    1. 定义状态:S_RED, S_GREEN, S_YELLOW
    2. 使用 parameterlocalparam 为状态编码。
    3. 使用 always @(posedge clk) 块描述状态寄存器(状态转移)。
    4. 使用 always @(*)assign 描述输出逻辑

进阶方向

  • SystemVerilog:Verilog 的超集,增加了面向对象编程、随机化、断言等强大功能,是现代 IC 和 FPGA 设计的主流语言。
  • UVM (Universal Verification Methodology):基于 SystemVerilog 的标准化验证方法学,用于构建大型、可复用的验证平台。
  • 时序分析:学习静态时序分析,理解建立时间、保持时间、时钟偏斜等概念,确保设计在目标频率下稳定工作。

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

工具推荐

  • 仿真器
    • 免费:IVERILOG (GTKWave 配合查看波形), ModelSim (Intel Quartus/Xilinx Vivado 自带免费版)。
    • 商业:VCS, ModelSim (高级版)。
  • FPGA 开发套件
    • Xilinx (AMD): Vivado (用于高端 Artix/Kintex 系列), Vitis (用于 Zynq MPSoC)。
    • Intel (Altera): Quartus Prime。
    • Lattice: Diamond。
    • 入门级 FPGA 板卡:Xilinx Basys 3, Digilent Arty Z7, Intel DE10-Lite。

书籍推荐

  1. 《Verilog HDL: A Guide to Digital Design and Synthesis》by Samir Palnitkar:经典入门书籍,讲解清晰,实例丰富。
  2. 《Digital Design and Computer Architecture》by David Harris and Sarah Harris:从数字逻辑基础讲到计算机体系结构,并用 Verilog 实现,非常适合系统学习。
  3. 《Advanced Chip Design, Practical Examples in Verilog》by Kishore Mishra:更偏向实践,包含了许多工业界的经验和技巧。

在线资源

  1. Nandland:提供大量关于 Verilog 和 FPGA 的入门教程和项目,非常适合初学者。
  2. FPGA4Student:网站上有丰富的 Verilog/VHDL 代码示例和教程。
  3. Coursera / edX:搜索 "Digital Logic Design" 或 "Computer Architecture" 等课程,通常会用 Verilog 作为实验语言。
  4. GitHub:搜索关键词如 "verilog projects", "verilog tutorial",可以找到大量开源项目和代码。

祝你学习顺利!从写一个简单的门电路开始,逐步挑战更复杂的项目,你很快就能掌握 Verilog HDL 的精髓。

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