多数FPGA或Verilog,VHDL相关教程都强调学习的前提条件是至少学习了数字电路设计等先修课程,另外大部分教程都偏重数字电路底层逻辑分析,使得FPGA设计的学习门槛很高,让多数希望入门的同学望而却步。然而,现在的FPGA芯片资源越来越丰富,进行算法设计的抽象层次越来越高,对于底层逻辑并不需要过多理解也可以进行FPGA算法设计;当然,如果要成为真正的FPGA设计的高手,数字电路等硬件相关的知识还是必不可少的,但这应该是入门之后的继续修炼,而不应该是入门学习的门槛。
Verilog是进行FPGA设计的主流硬件描述语言(HDL,Hardware Description Language)之一,与软件设计语言C语言形式上有较多的相似之处,但本质上不同。软件设计的思维模式和硬件设计的思维模式不一样,有一个转换的过程。进行FPGA设计,除了传统的连线、芯片控制、IO端口控制等功能外,更主要的是将一些基于软件设计语言如C/C++,python等或仿真语言如Matlab等验证通过的算法转换为硬件模块,以期提高运算速度。
本文将一个用C语言描述的简单算术运算函数(function
)转换为用硬件描述语言Verilog描述的功能模块(module
),以此为例帮助同学们初步认识FPGA设计的主要语言Verilog,熟悉相关语法,了解软件设计思维和硬件设计思维的不同之处。
1. C语言函数
下面是一个很简单的C语言函数,功能是计算表达式((a+b) - c * 8)
的值。我们先分析这个函数,再将其转换为Verilog描述的硬件模块。
1.1 一个简单的C语言函数
//C语言函数,计算表达式((a+b) - c * 8)的值
int calc (int a, int b, int c)
{
int sum = 0;
sum = (a+b) - c * 8;
return sum;
}
先对这个函数calc()
进行分析,注意这几点:
- 函数名为
calc
; - 有两个输入参数
int a, int b
; - 花括号
{}
内部是函数体,实现具体功能计算,即计算表达式`(a+b) - c * 8``的值; - 计算结果
sum
以返回值的形式告知函数的调用者;
换一个角度来看这个问题,C语言函数calc()
就是一个如图1所示实现指定功能的模块,这个功能模块接收外部输入的数据a,b
,进行相应的运算,即计算表达式(a+b) - c * 8
得到结果sum
,最后将运算结果输出到外部模块。
从这个角度来看,硬件设计模块和软件设计模块的结构形式是一致的,都是接收外部输入,执行相关运算,对外输出结果三个部分。
图1. 功能模块结构示意图
1.2 修改函数的参数形式
下面我们用Verilog语言实现calc()
函数同样的功能。 因为Verilog语言没有类似C语言函数返回值这样的概念,我们使用指针对calc()
函数的参数传递形式稍作修改完成同样的功能,并将新函数命名为calc_v2
以示区别。
//使用指针的C语言函数,计算表达式(a+b) - c * 8的值
void calc_v2 (
int a,
int b,
int c,
int* sum)
{
int tmp = 0;
tmp = (a+b) - c * 8;
*sum = tmp;
}
可以看出,函数calc_v2
实现与calc
完全相同的功能,形式上将输入参数和输出参数都放在圆括号( )
内的参数列表中。本例中用int* sum
对外传递函数结果。函数返回值类型设为void
,不再用于传递函数结果,这样与我们即将编写的Verilog模块在形式上保持一致。
2. C语言函数转换为Verilog模块
实现上面的calc_v2()
模块相同功能,Verilog有两种主要的实现思路,一种是使用组合逻辑实现,另一种是使用时序逻辑实现。有关组合逻辑和时序逻辑的概念我们暂时不详细叙述,直接用示例代码进行解释。本例我们用组合逻辑实现与calc_v2()
模块相同的功能,我们将Verilog描述的功能模块命名为calc_wire
以示区别。
2.1 Verilog模块代码实现
//用组合逻辑实现与calc_v2()函数相同的功能
module calc_wire(
input wire [31:0] a,
input wire [31:0] b,
input wire [31:0] c,
output wire [31:0] sum
);
wire [31:0] tmp;
assign tmp = (a+b) - c * 8;
assign sum = tmp;
endmodule
2.2 Verilog模块代码分析
上面的Verilog模块calc_wire
实现了与C语言函数calc_v2()
完全相同的功能,接收a,b
两个输入数据,对其进行相关运算(assign tmp = (a+b) - c * 8
),最后将结果输出到外部模块(assign sum = tmp
)。
接下来我们对calc_wire
代码进行详细分析:
-
第1行用双斜杠
//
开头的一段文字是注释,这与C语言相同。实际上,同C语言一样,Verilog也可以用/*...*/
进行一整段注释; -
第2行用关键词
module
开头,紧跟其后的是模块名calc_wire
,类似C语言的函数名,命名规则也与C语言基本相同; -
第3行到第5行包含在圆括号
()
内部,是模块的参数列表,与C语言相似,a,b
是输入参数,sum
是输出参数; -
第6行圆括号
()
后紧跟一个分号;
表示模块的参数列表到此为止,后面就是具体的功能代码了; -
第7行声明了一个
wire
型的变量tmp
,它的数据宽度是32比特。回顾一下,C语言的int
类型是不是也是32比特啊; -
第8行执行我们的运算,即求输入参数
a
和b
的和,并赋值给我们已经声明的变量tmp
; -
第9行将运算结果
tmp
赋值给接口的输出参数sum
,也就是将运算结果传递到模块之外; -
第10行以关键词
endmodule
结束模块代码
3. C语言函数与Verilog语言模块的联系与区别
从上面的分析可以看出,Verilog版的calc_wire()
模块和C语言版的calc_v2
函数形式上比较相似,同时也可以有不少不同之处。实际上Verilog在语法上源于C语言,借鉴了很多C语言的做法。但本质上,由于Verilog面向的是资源需求相对紧张的硬件模块设计,所以也有很多不同的地方。
3.1 C语言函数与Verilog语言模块的相似处
Verilog在语法上源于C语言,借鉴了很多C语言的做法,我们总结一下。
- C语言函数和Verilog模块在结构形式上一致的,都是参数列表+运算代码的形式;
- C语言运算符和Verilog模块运算符大部分都是相似的,比如本例的
+, - , *
等; - C语言表达式和Verilog模块表达式的形式上也大部分相似,比如本例就完全一致;
- C语言和Verilog的注释形式是完全一致的;
- C语言和Verilog的很多关键词与控制结构都是相似的。
3.2 C语言函数与Verilog语言模块的不同之处
-
结构形式的区别
- C语言用函数名
calc_v2
加花括号{...}
表示; - Verilog用关键词
module
加模块名calc_wire
表示功能模块,并以关键词endmodule
结尾;
- C语言用函数名
-
输入输出参数表达形式的区别
- C语言函数的参数列表没有明确的方向说明,一个参数是输入还是输出依靠另外的函数说明进行约定;
- Verilog模块的所用参数都有明确的方向,这里
input
表示该参数是外部输入数据,output
表示该参数是对外输出的运算结果。
-
变量声明形式的区别
-
C语言用
int,char,short
之类关键词声明变量。例如int tmp
表示声明一个整型变量; -
Verilog使用
wire [31:0]
这种位宽形式声明变量。例如wire [31:0] tmp
表示声明一个位宽为32bit的wire
型变量。 -
Verilog有两种主要的变量类型,
wire
(线网类型)和reg
(寄存器类型),其中wire
类型主要用于组合逻辑中,reg
主要用于时序逻辑中。 -
为了节省硬件资源,Verilog使用位宽形式
[msb:lsb]
来精准声明变量所占比特数。wire [31:0] tmp
声明一个位宽为32bit的wire
型变量,而wire [3:0] dat
声明一个位宽为4bit的wire
型变量dat
,表达的值的范围在0~15之间。
-
-
赋值形式有区别
- C语言使用赋值符
=
将右边表达式(a+b)-c*8
的值赋给左边的变量tmp
;
tmp = (a+b) - c * 8;
- Verilog使用关键词
assign
和赋值符=
将右边表达式(a+b)-c*8
的值赋给左边的变量tmp
;
assign tmp = (a + b) - c * 8;
- C语言使用赋值符
从上面的分析可以看出,Verilog语言的写法与C语言写法在结构上有很多相似之处,但在细节上也有较多区别。当然从编程的思维看,C语言是软件设计语言,依序串行执行每条语句,而Verilog是硬件描述语言(HDL),用于指导生成FPGA的硬件模块。FPGA的硬件模块都是并行执行的,这是软件思维和硬件思维最大的区别,以后我们会逐步体会。
4 知识点总结
我们用Verilog语言实现与C语言函数相同功能的一个模块,以此为例给大家揭开了FPGA设计的重要工具–硬件描述语言Verilog的神秘面纱。只要你有一定的C语言基础,大致理解上面的示例代码是不太困难的。
Verilog在语法上源于C语言,借鉴了很多C语言的做法。但本质上,由于Verilog面向的是资源需求相对紧张的硬件模块设计,所以也有很多不同的地方。特别强调Verilog语言几个要点:
- Verilog用关键词
module
和endmodule
表示模块结构; - Verilog用精准的位宽形式声明变量以便节省硬件资源,类似
wire [7:0] dat
表示声明一个8比特位宽的wire
型变量dat
; - Verilog使用
assign
对wire
型变量赋值,形如assign dat = (a + b)
表示将a+b
的结果值赋给变量dat
; - Verilog模块的输入和输出参数都有明确的方向,
input
表示输入,output
表示输出。
本篇是[0基础学会Verilog]专栏的一部分。本专栏以一系列0基础FPGA相关教程致力于帮助有一定C语言软件设计基础的同学走进FPGA设计的大门,包括Verilog语言教程,FPGA设计基础,FPGA设计案例分析等。