minor update to post josh-verilog

This commit is contained in:
2023-07-09 10:52:38 +08:00
parent 5a9e7370ef
commit 8c57cf87b9
5 changed files with 871 additions and 287 deletions
+64 -64
View File
@@ -31,13 +31,13 @@ description: 本文从一个 Verilog 用例出发,对 Verilog 语言的语法
<!-- more -->
最近几年,随着 IP核 市场的逐渐兴起,许多设计者逐渐意识到**利用现有的 IP核可以帮助节约设计成本、减少设计周期**,有许多设计工程师,甚至希望所有的设计模块都使用现成的模块,自己仅仅开发一些简单的粘合逻辑。这就有点像电路板设计的过程,工程师将各种芯片集成到一块电路板上,自己完成这些芯片间的互连和一些简单的 CPLD 逻辑设计,以及对微处理器的编程。使用现有的 IP 来搭建系统实际上也是一种**自底向上**(Bottom-Up)的设计方法。
最近几年,随着 IP 核市场的逐渐兴起,许多设计者逐渐意识到**利用现有的 IP 核可以帮助节约设计成本、减少设计周期**,有许多设计工程师,甚至希望所有的设计模块都使用现成的模块,自己仅仅开发一些简单的粘合逻辑。这就有点像电路板设计的过程,工程师将各种芯片集成到一块电路板上,自己完成这些芯片间的互连和一些简单的 CPLD 逻辑设计,以及对微处理器的编程。使用现有的 IP 来搭建系统实际上也是一种**自底向上**(Bottom-Up)的设计方法。
从以上描述可以看出,设计硬件电路时,这两种设计方法都有可能采用。Verilog HDL 可以完全支持这两种设计方法。
- 在门级的设计中,用户可以直接实例化 Verilog 语言中的门级原语构建系统;
- 如果需要描述硬件的行为,可以使用 Verilog 的行为级描述功能;
- 如果要使用 IP核 ,只要在设计中直接实例化 IP核 即可。
- 如果要使用 IP 核,只要在设计中直接实例化 IP 核即可。
EDA 行业的先行者们发明了 Verilog 硬件描述语言,其最根本的目的就是用 Verilog 来描述硬件的行为,但是有的描述是不需要实现为硬件电路的。
@@ -63,13 +63,13 @@ EDA 行业的先行者们发明了 Verilog 硬件描述语言,其最根本的
**这些电路单元都是同时并行工作的,相互之间并没有先后顺序关系,这一点与软件设计程序很不一样。**
下面具体分析 [HelloVlog 模块](#HelloVlog) 的电路功能。源代码如下。
下面具体分析 [HelloVlog 模块](#HelloVlog) 的电路功能,代码如下。
<a id="HelloVlogCode"></a>
``` verilog HelloVlog.v
// 编译指令, 定义时间单位和时间精度
`timescale 1ns/100p // 每个时间单位是1ns, 仿真精度是100ps
`timescale 1ns / 100ps // 每个时间单位是1ns, 仿真精度是100ps
// module名 + 端口列表
module HelloVlog( clk, rst_n, A_in, B_in, sel, A_xor_out, B_xor_out );
@@ -144,60 +144,60 @@ endmodule
1. 二选一多路选择器
对于 `MUX21` 这样二选一的电路,将采用如下的数据流描述(assign语句):
对于 `MUX21` 这样二选一的电路,将采用如下的数据流描述(assign语句):
``` verilog HelloVlog.v
assign #3 result = sel ? B_in : A_in;
```
``` verilog HelloVlog.v
assign #3 result = sel ? B_in : A_in;
```
`assign` 是 Verilog 中的关键词,用它赋值的语言称为**连续赋值语句**。
`assign` 是 Verilog 中的关键词,用它赋值的语言称为**连续赋值语句**。
如果 `sel`为真(1),则选择 `B_in`,否则将选择 `A_in`,而 `#3` 表示经过 3 个延时单位,再将选择结果赋值给 `result`,这也模拟了组合逻辑的延时。由于在[代码](#HelloVlogCode)最开头已经使用 Verilog 的编译指令将延时单位定义为 1ns(`timescale 1ns/100ps`),因此这里的 `#3` 代表 3ns 的延时。
如果 `sel`为真(1),则选择 `B_in`,否则将选择 `A_in`,而 `#3` 表示经过 3 个延时单位,再将选择结果赋值给 `result`,这也模拟了组合逻辑的延时。由于在[代码](#HelloVlogCode)最开头已经使用 Verilog 的编译指令将延时单位定义为 1ns(`timescale 1ns/100ps`),因此这里的 `#3` 代表 3ns 的延时。
2. 2 - 4 译码器
关于实现 2 - 4 译码器,则采用了如下的描述:
关于实现 2 - 4 译码器,则采用了如下的描述:
``` verilog HelloVlog.v
always @(result) begin
case(result)
2'b00: begin
{eq3, eq2, eq1, eq0} = #2 4'b0001;
$display ("At time %t-", $time, "eq0 = 1");
end
// ...
default: ;
endcase
end
```
``` verilog HelloVlog.v
always @(result) begin
case(result)
2'b00: begin
{eq3, eq2, eq1, eq0} = #2 4'b0001;
$display ("At time %t-", $time, "eq0 = 1");
end
// ...
default: ;
endcase
end
```
这里采用了另一种描述方式:用 `always` 语句来描述电路的行为。通过 `case` 判断 `result` 的值来决定 `eq0` ~ `eq3` 的值。同样,也采用 `#2` 来模拟组合逻辑的延时。`always` 和 `case` 也是 Verilog 中的关键词。这样的描述方法称之为**行为描述**,它侧重于描述电路的行为。
这里采用了另一种描述方式:用 `always` 语句来描述电路的行为。通过 `case` 判断 `result` 的值来决定 `eq0` ~ `eq3` 的值。同样,也采用 `#2` 来模拟组合逻辑的延时。`always` 和 `case` 也是 Verilog 中的关键词。这样的描述方法称之为**行为描述**,它侧重于描述电路的行为。
``` verilog HelloVlog.v
{eq3, eq2, eq1, eq0} = #2 4'b0001;
```
``` verilog HelloVlog.v
{eq3, eq2, eq1, eq0} = #2 4'b0001;
```
该语句表示将 `4'b0001` 赋值给 `eq3` ~ `eq0` 合并成的 4 位变量。`{}` 是 Verilog 的合并符号。
该语句表示将 `4'b0001` 赋值给 `eq3` ~ `eq0` 合并成的 4 位变量。`{}` 是 Verilog 的合并符号。
3. 异或门
对于 `A_xor_wire` 和 `XOR_B` 的两个完全一样的异或门,也采用了两种不同的描述方式:
对于 `A_xor_wire` 和 `XOR_B` 的两个完全一样的异或门,也采用了两种不同的描述方式:
``` verilog HelloVlog.v
assign #1 A_xor_wire = eq0 ^ eq1;
```
``` verilog HelloVlog.v
assign #1 A_xor_wire = eq0 ^ eq1;
```
和
和
``` verilog HelloVlog.v
xor #1 XOR_B(B_xor_wire, eq2, eq3);
```
``` verilog HelloVlog.v
xor #1 XOR_B(B_xor_wire, eq2, eq3);
```
`xor` 是 Verilog 中自带的基本逻辑门原语。这里相当于调用了该 `xor` 门。而 `B_xor_wire`、`eq2` 和 `eq3` 是代入到 `xor` 门中的参数。`eq2` 和 `eq3` 是输入,`B_xor_wire` 是输出。这里 `B_xor_wire` 是 `eq2` 和 `eq3` 相异或的结果。
`xor` 是 Verilog 中自带的基本逻辑门原语。这里相当于调用了该 `xor` 门。而 `B_xor_wire`、`eq2` 和 `eq3` 是代入到 `xor` 门中的参数。`eq2` 和 `eq3` 是输入,`B_xor_wire` 是输出。这里 `B_xor_wire` 是 `eq2` 和 `eq3` 相异或的结果。
在 Verilog 中,**将调用其他功能模块(包括 Verilog 的内嵌基本逻辑门)称为“实例化(Instantiate)”**。实例化**类似**于软件设计中的调用,但不能简单理解为软件中的调用。软件调用过程是顺序执行的,而实例化的硬件电路在设计中是独立于其他功能块,并行运行的。
在 Verilog 中,**将调用其他功能模块(包括 Verilog 的内嵌基本逻辑门)称为“实例化(Instantiate)”**。实例化**类似**于软件设计中的调用,但不能简单理解为软件中的调用。软件调用过程是顺序执行的,而实例化的硬件电路在设计中是独立于其他功能块,并行运行的。
这种**在模块中实例化其他功能模块**的描述方式被称之为**结构化描述**。
这种**在模块中实例化其他功能模块**的描述方式被称之为**结构化描述**。
## 2.2. 3 种描述方法<a id="toc.2.2"></a>
@@ -217,44 +217,44 @@ endmodule
在 [Part 3——描述方式和设计层次][]中会详细介绍 Verilog 语言中的这 3 种描述方式。
# 3. Verilog 的基本词法<>
# 3. Verilog 的基本词法
1. Verilog HDL 是一种**大小写敏感**的语言,这一点与 VHDL 不同,因此在书写的时候要格外注意。在 Verilog 语言中,**所有的关键字(又叫保留字)都为小写**。Verilog 的内部信号名(或称为标识符)使用大写和小写都可以。标识符可以是字母、数字、`$`(美元符号)和 `_`(下划线)任意组合,只要**第一个字符是字母或者下划线**即可。
{% note warning %}
{% note warning %}
因为 Verilog HDL 对大小写敏感,因此养成良好的信号定义习惯非常重要,否则容易因为大小写不同造成信号的歧义。推荐的信号定义习惯为:**所有信号和网线名均小写,用下划线分割单词,而宏变量全部用大写字母。**
因为 Verilog HDL 对大小写敏感,因此养成良好的信号定义习惯非常重要,否则容易因为大小写不同造成信号的歧义。推荐的信号定义习惯为:**所有信号和网线名均小写,用下划线分割单词,而宏变量全部用大写字母。**
{% endnote %}
{% endnote %}
2. 在上述[代码](#HelloVlogCode)中,用双反斜线 `//` 表示注释。另外,还有一种注释方式,用 `/* ...... */` 来表示。所不同的是前者为单行注释,而后者将 `/*` 和 `*/` 之间的内容全部看作注释内容。
{% note info %}
{% note info %}
通常,注释的内容只是作者为了增强代码的可读性而增加的内容,对整个代码的功能没有任何影响。不过,在一些工具中,尤其是逻辑综合工具,定义了一些特殊的指令,用于控制工具编译过程。这些指令也是以注释的方式出现的。例如:
通常,注释的内容只是作者为了增强代码的可读性而增加的内容,对整个代码的功能没有任何影响。不过,在一些工具中,尤其是逻辑综合工具,定义了一些特殊的指令,用于控制工具编译过程。这些指令也是以注释的方式出现的。例如:
``` verilog
module bl_box(out, data, clk) /* synthesis syn_black_box */;
```
``` verilog
module bl_box(out, data, clk) /* synthesis syn_black_box */;
```
在 `module bl_box` 的声明处有一行注释,用 `/* ...... */` 表示。它看起来是一个注释,实际上,是综合工具 Synplify 中的一个指令,指示 Synplify 将该模块看作一个黑盒(black_box),不处理模块内部的描述。
在 `module bl_box` 的声明处有一行注释,用 `/* ...... */` 表示。它看起来是一个注释,实际上,是综合工具 Synplify 中的一个指令,指示 Synplify 将该模块看作一个黑盒(black_box),不处理模块内部的描述。
{% endnote %}
{% endnote %}
3. 在 Verilog 中,通常使用空格符、跳格符和换行符作为间隔。在书写代码的时候,适当运用间隔符可以提高代码的可读性。比如在声明 4 个 `reg` 型数据 `eq0~eq3` 时,可以采用上述[例子](#HelloVlogCode)中的方法:
``` verilog HelloVlog.v
reg eq0, eq1, eq2, eq3;
```
``` verilog HelloVlog.v
reg eq0, eq1, eq2, eq3;
```
也可以用换行符将其分开:
也可以用换行符将其分开:
``` verilog
reg eq0;
reg eq1;
reg eq2;
reg eq3;
```
``` verilog
reg eq0;
reg eq1;
reg eq2;
reg eq3;
```
4. 在verilog中,还有一些转义字符。比如 `\n` 表示换行符;`\t` 表示 Tab 键;防止引起歧义,就用 `\` 表示 `\` 符号本身等等。这里不再对 Verilog 的词法做过多的描述,更多可以参考 Verilog IEEE 国际标准([IEEE Std. 1364-2005][])。
@@ -345,7 +345,7 @@ Verilog 中的编译指令是从 C 语言中的“预处理指令”演变得来
在每一个 `module` 文件前面加上 `` ` `` 的编译指令,就可以保证该文件中的延时信息受其自身文件中的 `` `timescale`` 编译指令指导。**否则在编译过程中,该模块将沿用上一个** `` `timescale`` **的值,或者使用缺省值**。
在上述实例代码描述的 [HelloVlog](#HelloVlogCode) 模块中使用了一个`` `timescale 1ns/100ps`` 编译指令。其中 `1ns` 表示延时单位,`100ps` 表示时间精度,也就是编译器所能接收的最小仿真时间粒度。`` `timescale`` 编译指令在模块外部出现,并且影响后面模块中所有的时延值,直到遇到下一个 `` `timescale`` 或 `` `resetall``指令。
在上述实例代码描述的 [HelloVlog](#HelloVlogCode) 模块中使用了一个`` `timescale 1ns/100ps`` 编译指令。其中 `1ns` 表示延时单位,`100ps` 表示时间精度,也就是编译器所能接收的最小仿真时间粒度。`` `timescale`` 编译指令在模块外部出现,并且影响后面模块中所有的时延值,直到遇到下一个 `` `timescale`` 或 `` `resetall`` 指令。
比如语句:
@@ -357,7 +357,7 @@ assign #(1.16) A_xor_wire = eq0 ^ eq1;
## 5.2. `` `define`` 和 `` `undef``
`` `define`` 用于定义宏。例如,首先定义一个总线宽度的宏为 16,然后利用这个宏定义一个宽度为 16 的 `reg`类型数据 `Data`,方法如下:
`` `define`` 用于定义宏。例如,首先定义一个总线宽度的宏为 16,然后利用这个宏定义一个宽度为 16 的 `reg` 类型数据 `Data`,方法如下:
``` verilog
`define BUS_WIDTH 16
@@ -593,7 +593,7 @@ end
在下一小节中,将引入“驱动”和“赋值”两个概念,深入探讨两种变量的含义。
## 7.4. 驱动和赋值<a id>="toc.7.4"</a>
## 7.4. 驱动和赋值<a id="toc.7.4"></a>
为了更清楚地描述寄存器和线网变量的概念,将以上述[实例代码](#HelloVlogCode)中的语句来说明。首先,引入 Verilog 语言中两个重要的概念:驱动(Driving)和赋值(Assigning)。
@@ -687,7 +687,7 @@ parameter xor_delay = 4;
- `begin ... end`:顺序语句组;
- `fork ... join`:并行语句组。
在 `begin ... end` 中存在的语句,按照 Veilog 的语义,应该是顺序执行的。而在 `fork ... join` 中的语句,则是并行执行的。
在 `begin ... end` 中存在的语句,按照 Verilog 的语义,应该是顺序执行的。而在 `fork ... join` 中的语句,则是并行执行的。
相比较顺序运行的事物,并行的事物比较难以理解和建模。同时,用于仿真的计算机是串行执行的,而 Verilog 语言本身的语义是用计算机进行模拟的语义,是用一种串行的语义来模拟并行的硬件。