接前面Questa Intel FPGA Starter学习小记(一),Questa仿真环境应该可以用了,试着了解一下VHDL与Verilog的基本语法
先列个表格,看看历史:
年份 |
VHDL |
Verilog |
SystemVerilog |
SystemC |
1987 |
VHDL-87 (IEEE 1076-1987) |
|
|
|
1993 |
VHDL-93 (IEEE 1076-1993) |
|
|
|
1995 |
|
Verilog-95 |
|
|
2001 |
VHDL-2000 (IEEE 1076-2000) |
Verilog-2001 |
|
SystemC 1.0 |
2002 |
|
|
|
SystemC 2.0 |
2005 |
VHDL-2002 (IEEE 1076-2002) |
Verilog-2005 (IEEE 1364-2005) |
IEEE 1800-2005 |
|
2006 |
VHDL-2006 (IEEE 1076-2006) |
|
|
|
2008 |
|
|
|
SystemC 2.2 |
2009 |
|
|
IEEE 1800-2009 |
|
2011 |
VHDL-2008 (IEEE 1076-2008) |
|
|
|
2012 |
|
|
IEEE 1800-2012 |
|
2016 |
|
|
|
SystemC 2.3.1 |
2017 |
|
|
IEEE 1800-2017 |
|
2019 |
VHDL-2019 (IEEE 1076-2019) |
|
|
|
Verilog从2005年开始已经融入进SystemVerilog,后面正经学习应该直接看SystemVerilog。但是网络上资源似乎都很老旧,各种写法似乎都停留在Verilog标准化之前Verilog95风格,所以本文仍然Verilog为主。
VHDL与Verilog差异对比?
VHDL(VHSIC Hardware Description Language)和Verilog是两种广泛使用的硬件描述语言(HDL),用于电子设计自动化(EDA)领域,尤其是在集成电路(IC)设计中。
语言对比
特性 |
VHDL |
Verilog |
起源 |
由美国国防部于1980年代初发起 |
由Gateway Design Automation于1984年开发 |
设计哲学 |
更偏向于强类型和严格的语法 |
更灵活,语法较为宽松 |
类型系统 |
强类型系统,需要明确指定数据类型 |
弱类型系统,类型转换较自由 |
并发机制 |
使用进程(processes)来描述并发 |
使用always和initial块来描述并发 |
可读性 |
类似Ada,更加注重可读性和维护性 |
语法类似于C语言,较为简洁 |
调试和测试 |
提供强大的断言(assertions)和测试特性 |
测试和调试功能较为基础 |
应用领域 |
在欧洲及军事/航空航天领域更受欢迎 |
在美国及消费电子领域更普遍 |
学习曲线 |
相对陡峭,语法复杂 |
相对平缓,易于上手 |
一些术语差异
VHDL |
Verilog |
描述 |
Entity |
Module |
描述硬件的基本单位,用于定义输入输出端口。 |
Architecture |
Implementation Block |
描述实体或模块的功能实现。 |
Signal |
Wire/Reg |
用于在设计中连接不同部分的变量。Wire 是连续赋值,Reg 用于存储。 |
Process |
Always Block |
描述在特定条件下执行的代码块。 |
Component |
Module Instance |
用于实例化一个模块或实体,用于设计的结构化。 |
Configuration |
Configuration |
用来绑定特定的实体和架构,或指定模块的特定实现。 |
Function/Procedure |
Function/Task |
用于封装并重用代码,Function 返回单个值,Task 可以没有返回值。 |
Package |
Package/Library |
用于定义和封装一组相关的定义和实现,以便重用。 |
Generic |
Parameter |
用于在实例化模块或实体时设置模块属性的参数。 |
Constant |
Parameter/Localparam |
定义在编译时已知且不可变的值。 |
Variable |
Variable |
用于过程内部存储和操作数据的局部存储元素。 |
Attribute |
Attribute |
用于为实体或信号添加特定的属性。 |
Concurrent Statement |
Concurrent Assignment |
在架构或模块级别上同时执行的语句。 |
Sequential Statement |
Sequential Block |
在过程内部按顺序执行的语句。 |
数据类型差异
VHDL 数据类型 |
Verilog 数据类型 |
描述 |
STD_LOGIC |
wire , reg |
通用的单比特类型,表示逻辑值。 |
STD_LOGIC_VECTOR |
wire [n-1:0] , reg [n-1:0] |
多比特的向量类型,用于表示一组逻辑线。 |
BIT |
wire , reg |
单个二进制数(0或1)。 |
BIT_VECTOR |
wire [n-1:0] , reg [n-1:0] |
二进制数字的向量。 |
BOOLEAN |
N/A |
表示逻辑真或假。 |
INTEGER |
integer |
表示整数。 |
UNSIGNED , SIGNED |
N/A |
用于表示无符号和有符号整数。 |
REAL |
real |
用于表示浮点数。 |
ENUMERATION TYPE |
enum |
允许用户定义一组命名的常量。 |
ARRAY |
array |
用于定义具有多个元素的数据结构。 |
RECORD |
struct |
允许用户定义可以包含不同数据类型的复合数据结构。 |
ACCESS |
N/A |
指针类型,用于动态内存分配。 |
FILE |
N/A |
用于文件操作。 |
TIME |
time |
用于表示时间。 |
大小写
- VHDL不区分大小写。在 VHDL 中,标识符(例如变量名、信号名和模块名)的大小写不会影响其含义。例如,Signal, signal, 和 SIGNAL 在 VHDL 中被视为同一个标识符。
- Verilog 区分大小写的语言。在 Verilog 中,所有的标识符(包括变量名、模块名等)的大小写必须保持一致。例如,counter, Counter, 和 COUNTER 在 Verilog 中会被认为是三个不同的标识符。
示例对比
没时间系统学习东西,先用一些简单的例子找找感觉
使用注释
| -- 这是一个单行注释
/*
这是一个多行注释,
但请注意,标准VHDL不支持此种注释方式,
使用时需要确保工具链兼容。
*/
|
| // 这是一个单行注释
/*
这是一个多行注释
可以跨越多行
*/
|
VHDL标准仅官方支持单行注释,使用--
。Verilog支持类似C语言的单行(//
)和多行(/* ... */
)注释,这使得在代码中添加详细注释更灵活。
模块定义与端口声明
| library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity Counter is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
count : out STD_LOGIC_VECTOR(7 downto 0));
end Counter;
|
| module Counter(
input clk,
input reset,
output [7:0] count
);
|
在VHDL中,模块被称为“实体”(entity),并且需要明确地包括库的引用。每个端口的方向(输入或输出)和类型都需要显式声明。而在Verilog中,模块定义更为简洁,端口类型和方向直接在模块声明中定义。
注意在 VHDL 中,library 和 use 声明是用来引入外部定义的包和库,这些包和库提供了额外的数据类型、子程序和功能,以便在设计中使用。
ieee.std_logic_1164
和 ieee.numeric_std
是非常常用的包。
ieee.std_logic_1164
非常重要,它定义了 std_logic
类型,这是 VHDL 中最常用的数据类型之一,用于表示数字逻辑的单个位。
std_logic
类型是一个枚举类型,包含 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-' 等值,分别代表未初始化、强制未知、强制为0、强制为1、高阻态、弱未知、弱0、弱1 和不关心。此包还包括多位版本std_logic_vector
。
Verilog模块声明中的wire可省略,完整的写法如下:
| module Counter(
input wire clk,
input wire reset,
output reg [7:0] count
);
|
信号赋值
| process(clk)
begin
if rising_edge(clk) then
if reset = '1' then
count <= (others => '0');
else
count <= count + 1;
end if;
end if;
end process;
|
| always @(posedge clk) begin
if (reset)
count <= 0;
else
count <= count + 1;
end
|
在VHDL中,信号赋值通常发生在过程(process)块内,使用条件语句来检测时钟边沿和重置条件。Verilog使用always块来描述类似的行为,语法更接近于传统的编程语言如C。
在VHDL中,上升沿和下降沿 分别用 rising_edge
和falling_edge
检测;在Verilog中,posedge
和negedge
两个关键字直接在always块的敏感列表中使用。
位宽与向量操作
| signal my_vector : STD_LOGIC_VECTOR(7 downto 0);
my_vector <= "00001111";
|
| reg [7:0] my_vector;
my_vector = 8'b00001111;
|
VHDL和Verilog都支持向量操作,但是在VHDL中,向量的位宽和方向需要明确指定(例如7 downto 0表示从高位到低位)。Verilog中的位宽声明更加类似于C语言数组的声明方式。
对于常量,列个表格:
数据类型 |
VHDL 示例 |
Verilog 示例 |
整数 |
123 |
123 |
二进制 |
b"1010" |
4'b1010 |
八进制 |
o"12" |
3'o12 |
十六进制 |
x"1A" |
8'h1A |
逻辑向量 |
std_logic_vector'(b"1010") |
4'b1010 |
字符 |
'A' |
(不适用) |
字符串 |
"Hello" |
"Hello" |
实数 |
1.23 |
1.23 |
定义带基数的整数 |
16#1A# |
8'h1A |
在 Verilog 中,数值常量前的数字(如 3'o12 中的 3)表示该常量的位宽。3'o12 表示一个 3 位宽的八进制数 12。12在二进制下是1010,但由于这里指定了只有 3 位宽,所以会截取最低的三位,即 010。这样,3'o12 在二进制下实际上表示的是 010。
条件判断
| process(all)
begin
if (a = '1' and b = '0') then
result <= '1';
else
result <= '0';
end if;
end process;
|
| always @(*)
begin
if (a == 1'b1 && b == 1'b0)
result = 1'b1;
else
result = 1'b0;
end
|
VHDL使用=作为等于操作符,而Verilog使用==。此外,Verilog明确要求位宽和基数(如1'b1表示一个位的二进制1),而VHDL则相对更灵活。
循环控制
| process
begin
for i in 0 to 7 loop
my_array(i) <= i;
end loop;
wait;
end process;
|
| integer i;
always @(*)
begin
for (i = 0; i <= 7; i = i + 1)
my_array[i] = i;
end
|
在VHDL中,循环通常用于过程(process)中,loop语句用于迭代。Verilog的循环语法使用for循环。
注意while循环也是存在的,但是不适用于综合,只用于仿真。
函数和过程
| function add_two_numbers(a, b: integer) return integer is
begin
return a + b;
end function;
-- 使用
signal result: integer;
result <= add_two_numbers(5, 3);
|
| function integer add_two_numbers;
input integer a, b;
begin
add_two_numbers = a + b;
end
endfunction
// 使用
reg [31:0] result;
always @(*)
result = add_two_numbers(5, 3);
|
VHDL中的函数定义比较形式化,包括详细的参数和返回类型说明。Verilog的函数定义语法上更为紧凑,函数的使用和C语言类似。
模块实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | entity Adder is
Port ( A : in INTEGER;
B : in INTEGER;
Sum : out INTEGER);
end Adder;
architecture Behavioral of Adder is
begin
Sum <= A + B;
end Behavioral;
-- 实例化
signal a, b, sum : INTEGER;
begin
u_adder: entity work.Adder
port map (A => a, B => b, Sum => sum);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | module Adder(
input integer A,
input integer B,
output integer Sum
);
assign Sum = A + B;
endmodule
// 实例化
integer a, b, sum;
Adder u_adder (
.A(a),
.B(b),
.Sum(sum)
);
|
在VHDL中,模块实例化需要明确指出实体和架构,通过port map来映射端口。Verilog端口连接通过.
语法直接指定。
时序控制
| process(clk)
begin
if rising_edge(clk) then
if en = '1' then
reg <= data_in;
end if;
end if;
end process;
|
| always @(posedge clk) begin
if (en)
reg <= data_in;
end
|
VHDL使用rising_edge函数来明确指示时钟上升沿,而Verilog直接在always块的敏感列表中使用posedge。
使用数组
| type my_array_type is array (0 to 7) of INTEGER;
signal my_array : my_array_type;
begin
my_array(0) <= 10;
-- 其他元素初始化
|
| reg [31:0] my_array [0:7];
initial begin
my_array[0] = 10;
// 其他元素初始化
end
|
在VHDL中,定义数组类型需要先声明一个类型,然后使用该类型定义信号或变量。Verilog则直接在变量声明时指定数组大小和位宽。
描述复杂逻辑
| process(a, b, c)
begin
y <= (a and b) or c;
end process;
|
在描述复杂的逻辑运算时,VHDL往往需要更多的结构化代码,如process块和明确的条件语句。Verilog可以使用更简洁的表达式,如使用逻辑运算符(&
代表AND,|
代表OR)
verilog中,assign语句用于执行连续赋值。如果不用assign,就需要用always块。
| always @* begin
y = (a & b) | c;
end
|
参数化模块
| entity GenericAdder is
generic ( WIDTH : integer := 8 );
port ( A : in std_logic_vector(WIDTH-1 downto 0);
B : in std_logic_vector(WIDTH-1 downto 0);
Sum : out std_logic_vector(WIDTH-1 downto 0) );
end GenericAdder;
architecture Behavioral of GenericAdder is
begin
Sum <= A + B;
end Behavioral;
|
| module GenericAdder #(parameter WIDTH = 8) (
input [WIDTH-1:0] A,
input [WIDTH-1:0] B,
output [WIDTH-1:0] Sum
);
assign Sum = A + B;
endmodule
|
参数化模块(泛型)在VHDL中通过generic关键字实现,允许在实例化时定制属性。Verilog使用parameter关键字,语法更接近于C语言的模板或宏定义
状态机实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | type state_type is (IDLE, WORKING, DONE);
signal current_state, next_state: state_type;
process(clk)
begin
if rising_edge(clk) then
current_state <= next_state;
end if;
end process;
process(current_state, input_signal)
begin
case current_state is
when IDLE =>
if input_signal = '1' then
next_state <= WORKING;
else
next_state <= IDLE;
end if;
when WORKING =>
next_state <= DONE;
when DONE =>
next_state <= IDLE;
when others =>
next_state <= IDLE;
end case;
end process;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | typedef enum {IDLE, WORKING, DONE} state_type;
reg [1:0] current_state, next_state;
always @(posedge clk) begin
current_state <= next_state;
end
always @(*) begin
case (current_state)
IDLE: begin
if (input_signal)
next_state = WORKING;
else
next_state = IDLE;
end
WORKING: begin
next_state = DONE;
end
DONE: begin
next_state = IDLE;
end
default: begin
next_state = IDLE;
end
endcase
end
|
VHDL中使用type来定义状态类型,而Verilog使用enum。两者都使用条件语句来描述状态转移
寄存器组的定义和使用
| type reg_array is array (0 to 7) of std_logic_vector(7 downto 0);
signal registers: reg_array;
|
| reg [7:0] registers [0:7];
|
定义一组寄存器时,VHDL需要定义一个数组类型然后声明一个信号,而Verilog直接在寄存器声明中定义数组大小和位宽。
触发器实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity DFF is
Port ( D : in STD_LOGIC;
CLK : in STD_LOGIC;
Q : out STD_LOGIC);
end DFF;
architecture Behavioral of DFF is
begin
process(CLK)
begin
if rising_edge(CLK) then
Q <= D;
end if;
end process;
end Behavioral;
|
| module DFF(
input D,
input CLK,
output reg Q
);
always @(posedge CLK) begin
Q <= D;
end
endmodule
|
触发器是数字电路中的基本构件。在VHDL和Verilog中都使用了对应的时钟边缘检测机制来描述触发器的行为。Verilog的代码更简洁。
SystemVerilog
先列个表格,后面慢慢了解
特性 |
Verilog |
SystemVerilog |
语言范围 |
硬件描述语言(HDL) |
硬件描述语言 + 硬件验证语言(HVL) |
数据类型 |
reg , wire 等基本类型 |
增加logic , bit , enum , struct 等 |
接口 |
使用端口连接模块 |
引入 interface ,简化模块连接 |
抽象层次 |
RTL 和行为级建模 |
支持更高级的抽象如 TLM |
类和对象 |
不支持面向对象编程 |
支持类和对象,具备 OOP 特性 |
随机化 |
无内置随机化功能 |
提供随机化和约束机制 |
约束和断言 |
无直接支持 |
引入断言和约束用于验证 |
并行处理 |
使用 fork-join |
增强的并行处理,如 fork-join_any |
模块 |
支持模块(module) |
还引入了 program 块 |
覆盖 |
无覆盖率收集机制 |
支持代码和功能覆盖 |