https://miyo.github.io/learning_fpga/docs/book01/languages/
两种基本处理方法——并发处理语句和顺序处理语句
重复一下,在硬件编程中,需要处理独立运行的模块。也就是说,用于描述硬件的语言需要能够描述独立运行的同时并行处理。然而,根据想要实现的处理,有时需要描述条件分支等具有依赖关系的处理。为了满足这些要求,VHDL
和 Verilog HDL
都支持两种称为同时处理语句和顺序处理语句的描述方式。具体的描述方法将在后面说明,但请先记住这两种方式的思路。
并行语句
同时处理语句是指不受周围处理影响而独立运行的处理。多个同时处理语句会在某个特定的时间点同时处理。因此,描述顺序或各处理语句之间不存在语法顺序,而是根据“某个时间点”输入的值生成输出。输出确定之前的时间取决于物理上在设备中电流流动的速度或信号延迟。
顺序处理语句
顺序处理语句是指根据语法等规定多个处理之间顺序的处理。例如,软件中必不可少的分支等控制语句的表达需要顺序
在VHDL 和 Verilog HDL 中可以使用加减算、逻辑运算、比较等运算符。在软件编程中,使用运算符编写的处理会被转换为给处理器的指令,但在 HDL 中,这些运算会被合成成相应的硬件逻辑,如 LUT 或 FF 的组合。
在 FPGA 中,有些包含小型数字信号处理器或乘法器,如果条件合适,可以使用它们与软件编程类似,HDL中也可以使用赋值运算将运算结果赋值给其他(或相同)变量。
HDL 的赋值有阻塞赋值和非阻塞赋值两种类型。阻塞赋值是立即在该点赋值并继续的赋值。另一方面,非阻塞赋规定了多个赋值语句中的同时执行。C 语言等单线程软件编程中的赋值相当于 HDL 中的阻塞赋值。
语法注意点
变量可以具有字母数字名称:它们必须以字母或 “_”
开头,并且不能以 “_”
结尾。 Verilog HDL 区分大小写。
值的基本类型 — '0','1','Z','X'
常常使用的数值 包括 0
1
z / Z
(高阻关断) x X(0/1)
硬件的值取 '0'
和 '1'
的值。此外,硬件还存在高阻态,表示“电阻无限大”的状态,在 VHDL 或 Verilog HDL 中用 'Z'
表示。值“电阻无限大”可能有些难以理解。物理上,可以想象开关断开状态。当多个信号合并到一个时,'Z'
表示“不影响其他值”。
在 HDL 中可以使用高阻抗来表示开关关闭
另外,'0' 或 '1' 都可以作为不定值的概念存在,这用 'X' 表示。
;
非常重要
模块构成
VHDL 编写的模块的概要。在 VHDL 中,将目标模块大致分为 entity
和 architecture
来编写。 entity
定义了连接到外部的输入输出端口等电路的外部框架, architecture
定义了使用的函数定义和处理内容等电路的内部
library and package
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_1164.ALL;
entity
entity
定义电路的轮廓,例如连接到外部的 input/output 端口的声明
architecture
architecture
定义电路的内部,例如要使用的函数的定义和处理内容
VHDL 中可以描述的常数的例子
Untitled
说明 | 例子 |
---|---|
1 条信号线所取的信号值 | '1' ,'0' ,'Z' ,'X' |
多条信号线所取的信号值 | "111" ,"0100" ,X"10" |
整数 | 32 ,8 ,1000 |
布尔值 | true ,false |
VHDL 中的变量都有类型
定义了很多种类型,也可以定义自己的类型。常用的五种类型。
std_logic
、 std_logic_vector
对应硬件信号类型,
signed
和 unsigned
用于表示可以进行加减运算的值,
integer
可以表示一般数值
Untitled
说明 | 类型名 |
---|---|
std_logic | 1 位信号线 |
std_logic_vector(n-1 downto 0) | n 位信号线 |
unsigned(n-1 downto 0) | n 位无符号的算术可操作值 |
signed(n-1 downto 0) | n 位有符号的算术可操作值 |
integer n to m | 从 n 到 m 的整数 |
1-bitの信号
std_logic
是 VHDL 的基本 1 Bit 信号类型。 '0'
, '1'
,以及高阻态的 'Z'
,不定值的 'X'
都可以作为其值。这些值直接对应硬件。
n 位信号
std_logic_vector(n downto 0)
是 std_logic
个 std_logi_vector
并排组成的 n 位信号线类型,称为 n 位 std_logi_vector
类型。
std_logic_vector
类型的变量 a
中的元素可以取出 a(3)
, a(4 downto 2)
等。前者是 std_logic
类型,后者是 3 位的 std_logic_vector
类型。
downto
表示 std_logic
的序列,从 MSB 降序编号。也就是说,对于 std_logic_vector(n-1 downto 0)
的位序列,MSB 是 std_logic_vector(n-1)
,LSB 是 std_logic_vector(0)
。
也可以使用 to
逆序排列,这时可以像 std_logic_vector(n to 0)
这样表示。
模块的外部描述 — entity
entity
代表模块的外框,通过模块名称和输入输出信号来定义。例如,以下描述相当于定义了一个名为 test
,输入信号为 pClk
和 pReset
,输出信号为 Q
的模块的外框。
entity test is
port (
pClk : in std_logic;
Q : out std_logic;
pReset : in std_logic -- 在最后一个之后不加
);
end entity;
定义端口
端口是硬件模块的输入输出。在 entity
的 port(~);
中,通过指定信号的方向和类型来定义。
信号方向有 in
(输入)、 out
(输出)和 inout
(输入输出)三种。
每个端口通过“名称 : 方向 类型”来定义。例如
pClk : in std_logic
-- or together definate
pR, pG, pB : in std_logic
这样的描述相当于定义了 pClk
的 std_logic
输入端口( in
)。相同方向和类型的多个端口名也可以用“,”来并列定义。例如,
可以将 3 个输入信号 pR
, pG
, pB
一起定义。
定义常量
可以在 entity
中定义模块内使用的常量
entity test is
generic (
width : integer := 640;
height : integer := 480
);
port (
pClk : in std_logic;
Q : out std_logic;
pReset : in std_logic
);
end test;
这个以 width
的名称定义了一个值为 640 的 integer
类型,即整数的常量。这个常量可以在 entity
内部,以及内部处理描述的 architecture
中使用
内部处理描述 — architecture
在 VHDL 中, architecture
用于描述模块的处理内容。描述的基本流程如下:
architecture RTL of test is
(在这里写变量的定义等。)
begin
(在这里描述处理内容。)
end RTL;
上述是用于描述名为 test
的模块内容的块。
变量的定义
变量的定义
signal 名前 : 型 := 初期値;
-- second
signal counter : std_logic_vector(9 downto 0); -- 10 bit
--third
signal counter : std_logic_vector(width-1 downto 0); --width
例如,10 位的数组可以这样定义。
此外,可以使用 generic
定义的值 width
来定义宽度为 width 的 std_logic_vector
型的信号。这里 width−1
的值在综合时确定.
运算符和运算
将典型的运算符汇总在表 3 中。如果将逻辑运算符定义为 std_logic_vector
类型,则会对对应的每个比特的值之间应用逻辑运算并返回结果。例如,“ "10" and "11"
”会变成“ "10"
”,“ "10" or "11"
”会变成“ "11"
”。比较运算的结果是 true
或 false
的真值。可以看出它具备了许多常见的编程语言所拥有的运算功能。但是,表 3 的说明中标注了(*1)的算术运算或比较数值大小的运算,只能用于 unsigned
类型、 signed
类型或 integer
的变量或常量,或者相当于数值的立即数
运算符汇总
Name | Operate | Description |
---|---|---|
逻辑运算 | a AND b |
逻辑乘积。a 和 b 都为 '1' 则 '1' ,否则 '0' |
a OR b | 逻辑和。a 和 b 中有一个或两个为 '1' 则 '1' ,否则 '0' | |
a XOR b | 排他的逻辑乘积。a 和 b 中只有一个为 '1' 则 '1' ,否则 '0' | |
NOT a | 否定。a 为 '0' 则 '1' 。 '1' 则 '0' | |
比较运算 | a = b | 如果 a 和 b 相等 true 否则 false |
a =/ b | 如果 a 和 b 不相等,则为 true ,否则为 false | |
a > b | 如果 a 比 b 大,则为 true ,否则为 false .(*1) | |
a < b | 如果 a 和 b 更小,则 true ,否则 false 。(*1) | |
a >= b | 如果 a 和 b 大于等于,则 true ,否则 false 。(*1) | |
a <= b | 如果 a 和 b 小于等于,则 true ,否则 false 。(*1) | |
算术运算 | a+b | a 和 b 相加 (*1) |
a-b | a 减 b (*1) | |
a * b | a乘b | |
a / b | a 除以 b | |
a ** b | a 的 b 次方 | |
数组操作 | a & b | 将 a 和 b 按照这个顺序排列的信号线束 |
a(b) | 从 a 中取出第 b 个信号 | |
a(b downto c) | 从 a 中取出第 b 个到第 c 个信号线的束 |
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
• 对于std_logic_vector
类型,如果要进行算术运算,通常需要先转换为signed
或unsigned
类型,再进行运算,运算完后再转换为std_logic_vector
类型。
signal B : std_logic_vector(15 downto 0);
-- 取 B 的高 8 位
signal high_byte : std_logic_vector(7 downto 0);
high_byte <= B(15 downto 8);
逻辑移位 :左移逻辑 (sll
):将位向量左移指定的位数,右侧补 0。右移逻辑 (srl
):将位向量右移指定的位数,左侧补 0。 算术移位 :左移算术 (sla
):与sll
类似,但通常用于有符号数时。右移算术 (sra
):将位向量右移时保留符号位(最左边的比特)。 旋转(循环移位)操作 :左旋 (rol
):将最高有效位送到最低有效位,实现循环左移。右旋 (ror
):实现循环右移。
计算结果的代入
计算结果可以通过赋值语句赋值给其他(或相同)变量。赋值有阻塞赋值和非阻塞赋值两种。
:= 阻塞赋值
<= 非阻塞赋值
Q <= counter(width-1);
这种描述相当于取出 std_logic_vector
型变量 counter
的 (width-1)
个,并赋值给 Q
的硬件描述。在 VHDL 中, signal
变量在初始化之外不能使用阻塞赋值
类型转换
a <= "00000000" & b; -- 正在用8位的0(即=00000000)填充不足的8位
b <= a(7 downto 0); -- 只将a的下位8位赋值给b。
类型转换需要使用专用函数。例如,将 std_logic_vector
转换为 unsigned
类型或 signed
类型时,分别使用 unsigned
函数或 signed
函数。
unsigned(c);
如果这样写, std_logic_vector
类型的变量 c
可以转换为 unsigned
类型。
反之,将 unsigned
类型或 singed
类型的变量转换为 std_logic_vector
类型时,使用 std_logic_vector
函数
std_logic_vector(d);
如果这样写, unsigned
类型的变量 d
可以转换为 std_logic_vector
类型。
当需要将 integer
类型的变量转换为 unsigned
类型或 signed
类型时,可以使用 to_unsigned
或 to_signed
。例如,将 integer
类型的变量 k
转换为 n-bit 的 unsigned
类型时,需要在第二个参数中指定位数 n
,
to_unsigned(k, n);
通常情况下,在 VHDL 中,不能对 std_logic_vector
类型的变量进行算术运算。因此,当需要与常量进行加减运算或比较运算时,必须先将 std_logic_vector
转换为 unsigned
类型再进行运算。例如,当对 n 位宽的 std_logic_vector
类型的变量 counter 加上常量 1
时,需要先将其转换为 unsigned
类型进行运算,然后再转换回 std_logic_vector
类型
counter <= std_logic_vector(unsigned(counter) + 1);
VHDL 中,没有像软件编程语言中常见的移位运算符。VHDL 使用数组操作的运算来实现类似的操作。例如,要右移一位宽度为 n 位的 std_logic_vector
型变量 counter,可以这样写:
VHDL 中,没有像软件编程语言中常见的移位运算符。VHDL 使用数组操作的运算来实现类似的操作。例如,要右移一位宽度为 n 位的 std_logic_vector
型变量 counter,可以这样写:
counter<='0'& counter(n-1downto1); --1
counter <= counter(n-3 downto 0) & "00"; --2
并行语句
c <= a and b;
e <= c and d;
e <= c and d;
c <= a and b;
即使写成这样,电路也会像一样被综合
并行处理语句,与描述顺序无关,会被解析和综合
a --- |===== + c
| + --|----------------c
b --- |===== + |
|_---|==== +
| +----e
d ---------------------|==== +
顺序处理语句 — process 语句
顺序处理语句会按照所写的顺序进行意义解析,并合成电路。因此,可以使用复杂的控制结构。在 VHDL 中,可以在 architecture
中使用 process
来创建一个用于编写顺序处理语句的块。 process
的基本语法如下所示
process(a, b)
begin
c <= a and b;
d <= a or b;
end process;
这里, process()
中的 ()
内的变量列表称为敏感列表。列出的变量的信号发生变化时, process
中的电路开始工作并改变输出值。非阻塞赋值是在 process 内的描述按顺序解释后,信号同时确定。这个 process
语句会生成如图 7 所示的电路。需要注意的是,仅仅是文按顺序解释,并不一定能生成按顺序处理的电路
当文中的输入变量(出现在式右边的变量)全部列在敏感度列表中时,每当输入变化时,电路就会动作,输出变量(式左边)的值会改变。也就是说,由该 process
语句生成的电路中,不需要保存任何状态。因此,会构成如图 8 所示的无需记忆元件的组合电路
不在敏感列表中的变量被用于右端。考虑如下 process
语句
process(a)
begin
c <= a and b;
d <= a or b;
end process;
这里,敏感列表中没有“b”
,输入变量没有被全部列出。在这个电路中,即使
b 的值发生变化,输出端 c 和 d 的值也不会变化。也就是说,如图 8 所示,输入 a 和 b 不能直接连接到输出 c 和 d,需要保存 c 和 d 值的机制,即存储元件,因此不能作为组合电路合成。“即使你认为写的是一样的,结果也可能不同”这一点请记住.
在 VHDL 的 process
语句中,可以使用可阻塞赋值的 variable
变量。 variable
变量是用于顺序处理语句中暂时存储值的东西。在进行长时间复杂的运算时,可以提高源代码的可读性。例如,可以这样使用.
process(a,b)
variable tmp0 : std_logic;
variable tmp1 : std_logic;
begin
tmp0 := a and b;
tmp1 := a or b;
c <= tmp0 xor tmp1;
end process;
这里,tmp0 和 tmp1 是变量。在阻塞赋值中,运算结果被分别赋值。由于阻塞赋值,合成工具在遇到这种语法时,tmp0 的值会立即替换为(a and b),tmp1 的值会立即替换为(a or b)。也就是说,这将被合成为一个 c <= (a and b) xor (a or b)
电路。
控制结构
在 VHDL 中,就像编写软件一样,可以使用条件分支和 C 的 switch
语句这样的控制结构。许多控制结构只能在 process
语句中使用。这里介绍一些典型的控制结构。
WHILE ELSE
可以在并行处理语句中编写的控制结构。可以根据条件选择输出的值。下面是一个例子:如果 a
和 b
的值相等,则将 X
赋值给 c
,如果不相等,则将 Y
赋值给 c
。
c <= X when a = b else Y;
这与其说是控制结构,不如说是类似于 C 语言中用 c = (a==b) ? X : Y
表示的三元运算符
if then elsif else end if — 条件分支语句
process
仅在文档中使用条件分支语句。接下来,如果 a
大于 b
,则处理语句 X
有效,否则处理语句 Y
有效。此外,在处理语句中也可以嵌套 if
if a > b then
语句 X
else
语句 Y
end if;
---另外,使用 elsif 可以在 else 节中叠加编写下一个条件。
if a > b then
语句 X
elsif a < b then
语句 Y
else
语句 Z
end if;
在 VHDL 中,除了可以编写基于信号值的条件外,还可以使用信号变化的时间条件。例如,变量 clk
的变化时间可以表示为 clk'event
,因此可以使用它
if clk'event and clk = '1' then
语句
end if;
可以创建这样的条件语句。在这个例子中,当“clk 变化,并且 clk 为 1”时,执行处理语句。在硬件上,这相当于 clk 信号上升的瞬间。在其它时间点,处理语句不会执行,值会继续保存。这是设计固定时序执行的处理顺序同步电路不可或缺的表达方式。
但是,在 2018 年目前,大多数情况下,更倾向于使用表示“时钟上升沿”的 rising_edge
函数,
if rising_edge(clk) then
语句
end if;
C 或 Java 中的 switch
语法。在下面的例子中,根据 std_logic_vector
类型的变量 a 的值,会执行处理语句 X、Y、Z 中的某个。通过使用 to_integer
将 a
转换为 integer
类型,使其与 0、1、2 这些整数相匹配。在这里,VHDL 的 case ~ when
语法中,必须注意确保至少有一个 when
与之对应。可以用“ others
”来表示匹配所有剩余的所有条件,而不是列出所有 when
case to_integer(unsigned(a))
when 0 =>
语句 X
when 1 =>
语句 Y
when 2 =>
语句 Z
when others => --在其他所有情况下
语句 W
end case;
for in loop — 循环
在 VHDL 中,循环结构是为了简化重复相似处理的语法。实际上,在综合时会产生与循环次数相等的硬件电路。
例如,当定义了 5 位的 std_logic_vector
型变量 a
和 std_logic
型变量 b
时
process
variable i : integer := 0;
variable tmp : std_logic;
begin
tmp := '0'
for i in 0 to 4 loop
tmp := tmp or a(i);
end loop;
b <= tmp
end process;
这里的描述相当于简化了下面的描述
process
variable i : integer := 0;
variable tmp : std_logic;
begin
tmp := '0'
tmp := tmp or a(0);
tmp := tmp or a(1);
tmp := tmp or a(2);
tmp := tmp or a(3);
tmp := tmp or a(4);
b <= tmp
end process; -- 请注意,在电路运行时而不是创建电路时,循环处理会被解释。
组合电路的子模块
在 VHDL 中,为了生成复杂的组合电路,可以编写 function
这样的子模块。 function
是顺序处理语句,可以编写 if
或 case
等条件语句,但不能使用非阻塞赋值。 function
定义如下
这是名为 f
的 function
的定义,它是一个输入为 a
和 b
的 1 位变量,当输入的两个值相等时输出 1,不相等时输出 0 的函数。
function f (a : in std_logic; b : in std_logic)
return std_logic is
variable Q : std_logic;
begin
if (a = b) then
Q := '1';
else
Q := '0';
end if;
return Q;
end f;
--- 在调用端,
x <= f(x, y) ---x 和 y 是函数调用时的实际参数。
--- 此外,还可以定义输入或输出为数组类型变量的函数
function g (x : in std_logic_vector(1 downto 0))
return std_logic_vector is
variable Q : std_logic_vector(1 downto 0);
begin
Q(0) := x(1);
Q(1) := x(0);
return Q;
end g;
与 C 等软件编程语言中的函数不同,函数计算完成后调用方不会等待。这是一种能够清晰地描述复杂组合电路的写法。
禁忌咒语
实际上,要使用之前解释的 std_logic
等功能,需要加载实现了这些功能的库等。在 VHDL 源代码的开头写下以下内容:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
Verilog HDL 的基本语法规则
注释
/* 〜 */
包围的部分或从 //
到行尾都是注释
模块构成
值的表达方法
在 Verilog HDL 中,需要指定位宽( w
)和基数( f
),以“ w'f値
”的形式来表示立即数。位宽以十进制表示。如果省略位宽和基数,则默认为十进制、32 位的值。展示了基数、符号和表示数值之间的关系
进制数 | 进制符号(f) | 例 | 十进制表示的值
2 | b | 8'b10
| 2
10 | d or 10 | 10'd10
/10 | 10
16 | h | 8'h10
| 16
-
- 两种基本处理方法——并发处理语句和顺序处理语句
- 并行语句
- 顺序处理语句
- 语法注意点
- 值的基本类型 — '0','1','Z','X'
- 模块构成
- library and package
- entity
- architecture
- VHDL 中的变量都有类型
- 1-bitの信号
- n 位信号
- 模块的外部描述 — entity
- 定义端口
- 定义常量
- 内部处理描述 — architecture
- 变量的定义
- 运算符和运算
- 计算结果的代入
- 类型转换
- 顺序处理语句 — process 语句
- 控制结构
- WHILE ELSE
- if then elsif else end if — 条件分支语句
- for in loop — 循环
- 组合电路的子模块
- Verilog HDL 的基本语法规则