Verilog 学习笔记

这篇文章仅记录了一些本人在修读课程 「数字逻辑与计算机组成实验」时自学 Verilog 留下的一些笔记和备忘,不是一则教程,也不能作为教程使用,因此不可避免的含有大量缺乏例子,难以理解甚至出错的段落,若读到请仅供参考。

另外,作者作为一名水平一般的计算机相关专业本科生,修读本课程使用 Verilog 的场景仅限于在 FPGA 上实现一个 riscv32 架构的流水线 CPU,因此涉及的设计思路和语言特性相当有限。

电路描述

数据类型

Verilog 的数据类型分为 regwire,其后还可以增加一个范围描述符,表示其位宽,如 wire [7:0] ch

  • wire 定义一个线网,它没有记忆,随时被其连接信号所驱动,当然驱动其的信号也只能是唯一的。
  • reg 定义一个有所谓 「含蓄记忆」的信号,这里所谓记忆的意义是行为级的(原意是令仿真器暂存此值)。

数据值

Verilog 的数据值基本上限于整数,其中整数用 [size]['radix]value 指定,其中 radix 可以用 ohbd 来指定,默认用十进制,高位补 0 。
特别地,Verilog 中分别用 xz 来表示数据中那些为不定值或高阻态的数据位,同时当数据最高位是这两种状态之一时,高位默认补对应的状态位。

Verilog 也可以用字符串为多位 reg 赋值,用大端方式存储。

操作符

Verilog 的操作符和 C 相差无几,需要特别注意的是带运算符的赋值符和自增自减操作并不受支持 (supported in SystemVerilog)

特别地,Verilog 支持一些缩简的运算符,诸如 |A&A~^A 等,它们的意义是对 A 的所有位之间执行逻辑运算并返回一位的值,对于四态值,其还支持全等(===)和不全等(!==)运算。

Verilog 还支持一种特殊的操作符:连接,形如 {expr, expr}{value expr}。它既可以被用于连接右值,也可以被用于连接左值,这样可以实现很多很方便的赋值逻辑(尤其是连接左值)。

数组和其他数据类型

在数据类型方面,Verilog 和 SystemVerilog 有很大的不同之处,下面分别讨论。

Verilog 的数组

对于 Verilog,定义在信号名称前的 range 只能有一维,它是这个数据类型特征的一部分。
因此,Verilog 的数组一般用如下方法定义:

reg [31:0] mem[31:0][31:0];

这实际上可以被理解为 reg [31:0] 的一个二维数组,而且它们的空间分布不是连续的,也不能被一起赋值。

SystemVerilog 的数组

SystemVerilog 提供打包数组 (packed array) ,也就是允许在信号名称前提供多维的位宽来形成一维或多维数组,这意味着它们的内存是连续的,如:

reg [31:0][31:0] mem[31:0][31:0];

这允许了对于打包维的连续赋值,同时名称后的维数和 Verilog 原义相同,这在 SystemVerilog 被称作非打包的 (unpacked array),而且也有对应的赋值语法值列表 '{} ,然而这种语法在 Verilog 中不受支持。

自定义数据类型

Verilog 不支持自定义数据类型,但 SystemVerilog 引入了 tydedef 支持了自定义数据类型,而且,后者还引入了 C 式的 unionstruct

局部电路

门级电路描述

Verilog 提供了一些逻辑门模块来进行逻辑门级别的电路描述,在此不多讨论。

硬连线

assign 描述了一种硬连线,它可以为一个 wire 指定一个唯一的驱动信号,这个信号可以是其他多个信号和操作符构成的表达式。

过程语句

always 描述了过程性的电路行为,它拥有一个关键事件列表,用 @(events) 标识,若 events = * 则表示其涉及的任意非左值改变时触发语句块。

过程语句有如下几种:

  • 赋值,包含阻塞赋值(=)与并行赋值(<=)操作符,前者之间是顺序执行的,而后者不关心顺序,并行执行,且后者都先于前者执行。
    (换句话说,并行赋值得到的右值都是事件触发前时那些右值的值,阻塞赋值得到的右值是上一条阻塞语句执行后右值的值)
  • if-else 分支,和 C 类似,一般综合为二选一多路选择/分配器,这里不展开。
  • case 分支,和 switch 类似,值得一提的是还提供 casezcasex 语句,能够将 zx 视为通配符 ?
  • forrepeat ,用于实现循环的逻辑,实际实现是被展开的,是否阻塞由内部语句而定。

描述过程性语句的还有 initial,但除了仿真和给一些存储单元赋初值外不建议使用。

用 Verilog 描述的电路一般都是可综合 (Synthesizable) 的,但有些可以仿真的电路不可综合,即使可以综合,也可能会产生不合理的连线。比如:

always @(*)
begin 
  if (rst)
    a <= 0;
  if (en)
    a <= 1;
end

这实际上指定了一种同时驱动 a 而且互相矛盾的连线,可以预见的是综合出的电路是不适合被实现 (Implementation) 的。由于 always 块之间其实是并行的,因此相似的错误也不允许发生。还有一种不可综合的情况发生在使用 initialjoin/fork 块时,这些块在很多时候,在很多特定的硬件上不可综合。

任务、函数

taskfunction 定义了一种组合逻辑的「子任务」或者说「子过程」,它们可以在 always 块内被使用。但是不能包含其它的 always ——因为它们是「子过程」。

时序逻辑

时序逻辑电路由时钟激励和次态逻辑两部分所基本决定,在 Verilog 中,可以使用 posedgenegedge 来利用时钟的上升下降沿来界定时钟激励的瞬间:

  always @(posedge clock)
    //some next state logic here

模块布局

连接模块

模块用 module 定义,其包含一些 inputoutput 接口,在被上层模块实例化时,inputwire 的形式与上级相连,output 也被上级以 wire 相连的方式接收。

为了在电路设计中提高接口的复用性(比如很多模块都有 enclkrst 等接口),SystemVerilog 提供了 interface 来封装一些通用的接口集合。

批量生成

generate 定义了一种用过程语句展开块的行为。具体来说,generate 的迭代是定界的,它以特定的命名域来将实例化语句、过程语句、硬连线语句重复若干确定次数。它适合用于需要包含和管理大量相对独立的相同子模块的模块。

作为电路描述语言的 Verilog

如果有着软件开发背景,新学习 Verilog 时可能会有着一种熟悉的陌生感,这种陌生感可能首先来自于对于 regwire 的区别的疑惑,但是其背后更深的原因大概是 Verilog 中过程语句(或称行为级描述语句)和连线描述语句(或称并行语句)的不同:前者旨在令你直接指定电路的连线,后者旨在让综合器替你设计电路,两者分野不同,利用的是两套系统,这也是 assignalways 的区别的根源。

同时,由于根植于硬件设计的特性,Verilog 描述的电路会出现不可综合,不可部署的情况。这本质上是因为,和高级语言向机器语言的编译不同,Verilog 代码的综合更受到实际可用可编程器件的限制,其中很多特性没有也不可能被抽象到 Verilog 的语言标准之内。

正是基于这样的理解,以及对于由于翻译问题产生的许多对于 Verilog 语言特性有歧义和各家不同的表述的困惑,本人才决定写这么一个小笔记来记录一下学习 Verilog 过程中对于 Verilog 语言特性和设计思路的一些想法。然而,Verilog 不仅可以用于电路描述,也可以用于电路仿真,在用于仿真时,其设计思路又与在用于描述时大有不同,下个部分将对其进行一些简要介绍。

电路仿真

尽管电路器件是可编程的,但实际电路千变万化,编程到器件上进行调试不仅可能耗时耗力,而且可能编程后器件根本不工作,更罔论调试了,因此利用软件的方法模拟仿真是必要的。

因此,Verilog 和各种 EDA 也提供了对于仿真的支持,可以料想的是,仿真的设计流程以过程语句为主,其设计思路更像高级语言,尤其是对于专门针对电路验证进行了拓展的 SystemVerilog 而言。

时间粒度

仿真器模拟一个实际的电路应用场景,但受硬件资源的限制,其必须规定一个相对于现实世界较大的时间粒度,这用 `timescale 来规定,其语法为:

`timescale <time_unit>/<time_precision>

它规定了仿真的时间粒度和使用的时间单位。

提供激励

仿真器需要知道其为模拟设备提供特定激励信号的时机,一般我们仍然使用由 initialalways 块包裹的过程语句来提供激励。

延时和 initial

initial 指定了从仿真开始时刻开始的一系列过程语句,#(time) 则指定了一种「延时」的过程,仿真器待延时超时后再执行后续的语句。

其它的过程语句格式和电路描述时使用的非常相似。

reg a, b, c, d;
wire e;
testee testee1(a, b, c, d, e);
initial
begin
  integer i;
  for (i = 0; i <= 4b'1111; i = i + 1)
    #5 {a, b, c, d} = i;
end

周期性激励

这一般由 always 块实现,例如,提供一个理想的时钟:

always #5
  clk = ~clk;

数据类型

由于并不直接面向硬件,出于便利,Verilog 提供了主要用于仿真的数据类型,若其被滥用于电路描述,则可能是不可综合的。

  • integer 指定一个 32 位整数。
  • real 指定一个浮点数。
  • timerealtime 指定一个整数或浮点的时间类型,前者是 64 位的。

功能语句

功能语句用于控制仿真流程或输出调试信息,以下列出一些语句。

流程控制

  • $finish
  • $stop

终端输出

  • $write
  • $display
  • $strobe
  • $monitor

波形输出

  • $dumpfile
    指定输出的波形文件名。
  • $dumpvars
    指定需要输出波形的型号和输出等级,用例:$dumpvars([level], [module_name])

开发环境

为便于学习(乱搞),可以安装 iverilog + gtkwave 套件进行仿真。

使用示例

为指定仿真模块,在仿真顶层模块内加入:

initial
begin
  $dumpfile("wave.vcd");
  $dumpvars;
end

将仿真源文件和设计源文件放在一起编译运行:

$ iverilog -o test test.v test_tb.v
$ ./test
$ gtkwave wave.vcd

参考链接

Verilog tutorial (1)

Verilog tutorial (2)

Icarus Verilog

posted @ 2022-10-12 19:22  臼邦庶民  阅读(318)  评论(2编辑  收藏  举报