个人理解CPU与FPGA的区别
- CPU 可以看作是一个功能固定、专门优化过的专用逻辑电路,而 FPGA 是一个可以重新配置的逻辑电路平台。理论上你可以用 FPGA 搭一个 CPU
以计算加法为例:
CPU:在计算过程中,厂商已经帮你造好了通用的“硬件计算器”,你用汇编指令告诉它怎么用。
add: mov eax, [esp+4] ; 取第一个参数 a add eax, [esp+8] ; 加第二个参数 b ret ; 返回结果(在eax中)
FPGA:你自己用逻辑搭出一个“硬件计算器”,然后数据用二进制电平进去就能直接出结果。
module adder ( input [3:0] a, // 输入a,4位宽 input [3:0] b, // 输入b,4位宽 output [4:0] sum // 输出sum,5位宽 ); assign sum = a + b; endmodule
- 也就是说CPU有专门的计算单元,相当于封装好的FPGA硬件电路计算器,就是 ALU(算术逻辑单元),汇编指令将数据放进去,内部把数据翻译成二进制实现和FPGA一样的门电路运算,写回寄存器就能拿到结果。
- FPGA是生成CPU内部那个硬件电路计算器、算术逻辑单元去计算数据,写的 Verilog/VHDL 会被综合器翻译成你需要的硬件电路(可能是加法器、乘法器、滤波器等等),这些电路是用 LUT、触发器、布线等资源“搭”出来的,编译完成后,这些电路就像 CPU 里的 ALU 一样是固定的硬件,但它是你自己定义的数据也用电平组成二进制表示
数学在硬件上的描述
- 另一种理解2的n次方方式,当不为0的时候。把2当作电平的高低变化,即1条线有两种状态
那么2的1次方就是2,一条线有两种变化
2的2次方就是4种变化关系
| 线1 | 0 | 1 | 0 | 1 |
| 线2 | 0 | 0 | 1 | 1 |
2的3次方就是8种变化关系
| 线1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
| 线2 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
| 线3 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 |
通过这个硬件线电平变化关系,我们也能看出来2的平方变化,不禁让人感叹数学和硬件的奇妙
数字与运算符
在Verilog 中的数字表示方式,最常用的格式是:<位宽>’<基数><数值>,如4’b1011
- 位宽:描述常量所含位数的十进制整数,是可选项。例如4’b1011中的4就是位宽,通俗理解
就是4根线。如果没有这一项可以通过常量的值进行推断。例如’b1011可知位宽是4,而’b10010可
推断出位宽为5。 - 基数:表示数值是多少进制。可以是b,B,d,D,o,O,h或者H,分别表示二进制、十进制、
八进制和十六进制。如果没有此项,则缺省默认为十进制数。例如,二进制的4’b1011可以写成十进
制的4’d11,也可以写成十六进制的4’hb或者八进制的4’o13,还可以不写基数直接写成11。综上所
述,只要二进数相同,无论写成十进制、八进制和十六进制都是同样的数字。 - 数值:是由基数所决定的表示常量真实值的一串ASCII码。如果基数定义为 b或B,数值可以
是0,1,x,X,z或Z。如果基数定义为 o或O,数值可以是2,3,4,5,6,7。如果基数定义为
h 或H,数值可以是8,9,a,b,c,d,e,f,A,B,C,D,E,F。对于基数为d或者D的情况,
数值符可以是任意的十进制数:0到9,但不可以是x或z。例如,4’b12是错误的,因为b表示二进
制,数值只能是0、1、x或者z,不包含2。32’h12等同于32’h00000012,即数值未写完整时,高
位补0。
二进制
4'b1011 // 4位宽,二进制,数值为 1011(十进制11)
'b10010 // 自动推断为5位宽,二进制,数值为 10010(十进制18)
8'b00001111 // 8位宽,二进制,数值为 00001111(十进制15)
十进制
4'd11 // 4位宽,十进制,数值为 11(即二进制1011)
'd25 // 自动推断所需位宽,十进制,数值为25(二进制11001,5位)
十六进制
8'hFF // 8位宽,十六进制,数值为 FF(十进制255,二进制11111111)
4'hB // 4位宽,十六进制,数值为 B(十进制11,二进制1011)
16'h1A2F // 16位宽,十六进制,数值为 1A2F
15 // 默认十进制,数值为15Verilog简介
Verilog是硬件描述语言,顾名思义,就是用代码的形式描述硬件的功能,最终在硬件电路上实现该功能。在Verilog描述出硬件功能后需要使用综合器对Verilog代码进行解释并将代码转化成实际的电路来表示,最终产生实际的电路,也被称为网表。这种将Verilog代码转成网表的工具就是综合器。
这个综合器就是各家FPGA给出的 开发工具软件,比如ISE和VIVADO
Verilog模块
Verilog模块例程
模块(module)是Verilog 的基本描述单位,是用于描述某个设计的功能或结构及与其他模块通信的外部端口。
模块有五个主要部分:端口定义、参数定义(可选)、 I/O说明、内部信号声明、功能定义。 模块总是以关键词module开始,以关键词endmodule结尾。它的一般语法结构如下所示:
module moduleName (
clk,
RST_N,
DOUT,
INPUT_IO,
A,
B
);
parameter Data_W = 7;
input clk;
input RST_N;
output [Data_W:0] DOUT;
input [Data_W:0] INPUT_IO;
reg A;
reg [Data_W:0] dout;
wire [Data_W:0] INPUT_cnt;
assign DOUT = dout;
always @(posedge clk or negedge RST_N) begin
if (!RST_N) begin
dout <= 0;
end else begin
dout <= dout + 1;
end
always @( A or B ) begin
A = 0
end
endmoduleVerilog例程分析
模块
module moduleName (
clk,
RST_N,
DOUT,
INPUT_IO,
A,
B
);如上述代码所示,模块是以module开始,以endmodule结束。模块名是模块唯一的标识符,一般建议模块名 尽量用能够描述其功能的名字来命名,并且模块名和文件名相同。 模块的端口表示的是模块的输入和输出口名,也是其与其他模块联系端口的标识。
参数
parameter Data_W = 7;参数定义是将常量用符号代替以增加代码可读性和可修改性。这是一个可选择的语句, 用不到的情况下可以省略
接口定义
input clk;
input RST_N;
output [Data_W:0] DOUT;
input [Data_W:0] INPUT_IO;模块的端口可以是输入端口、输出端口或双向端口
信号类型
reg A;
reg [Data_W:0] dout;
wire [Data_W:0] INPUT_cnt; reg是最常用的寄存器类型,寄存器类型通常用于对存储单元的描述。线网类型用于对结构化器件之间的物理连线的建模,如器件的管脚,芯片内部器件如与门的输出等。
实际上,reg表示一个存储单元,用来储存数据。wire表示连线,用来将module内部的接线端口连接到外部。
reg与wire类型定义语法如下
- reg A; // A为1位寄存器
- reg [Data_W:0] dout; // dout为7位寄存器
- wire [Data_W:0] INPUT_cnt; // INPUT_cnt为8线型信号
程序语句
assign
assign语句是连续赋值语句,一般是将一个变量的值不间断地赋值给另一变量,两个变量之间
就类似于被导线连在了一起,习惯上当做连线用。
assign 语句的功能属于组合逻辑的范畴,应用范围可以概括为一下几点
- 持续赋值
- 连线
- 对
wire型变量赋值,wire是线网,相当于实际的连接线,如果要用assign直接连接,就用wire型变量,wire型变量的值随时发生变化。
[!tip]
比如
wire,通常用assign进 行赋值,如assign A = B^C。
always
always 语句是条件循环语句,执行机制是通过对一个称为敏感变量表的事件驱动来实现的。always 是“一直、总是”的意思,@后面跟着事件。整个always的意思是:当敏感事件的条件 满足时,就执行一次“程序语句”。敏感事件每满足一次,就执行“程序语句”一次。
[!TIP]
需要注意,组合逻辑器件的赋值采用阻塞赋值
=,时序逻辑器件的赋值语句采用非阻塞赋值<=
always @(敏感事件)begin
程序语句
endalways @(posedge clk or negedge RST_N) begin
if (!RST_N) begin
dout <= 0;
end else begin
dout <= dout + 1;
end
always @( A or B ) begin
A = 0
end
always @( * ) begin
if (!A) begin
B <= 0;
end
end 第一段always的意思是当clk时钟变化,或者RST_N复位信号产生时,检测如果RST_N复位信号等于低电平(代表用户按下复位按键),执行一些操作。
第二段always的意思是当A或者B信号变化,就让A等于0
第三段always的意思的前面定义的信号任一发生变化,都会检测A信号是否变化,若变化将B置0
if与case
- if语句和case语句是Verilog里两个非常重要的语句,if和case语句有一定的相关性,也有一定的区别。相同的地方在于两者几乎可以实现一样的功能。
- 不同的是,if语句每个分支之间是有优先级的,综合得到的电路是类似级联的结构。case语句每个分支是平等的,综合得到的电路则是一个多路选择器。因此,多个if else-if语句综合得到的逻辑电路延时有可能会比case语句稍大。
if
module if_case(
input [1:0] en,
input [1:0] a,
output reg q
);
always@(*)begin
if(en[0]==1)
q=a[0];
else if(en[1]==1)
q=a[1];
else
q = 0;
end
endmodule- 其综合后的RTL视图如下所示
从上图中所示的RTL图可以看到,此电路中含有两个二选一多路选择器,并且右边的优先级要高于左边(因为q的值是直接与右边的二选一选择器连接),当en[0]不为1时才会继续判断en[1]。 也就是说,在if语句下综合出的电路含有优先级。
case
always @(*)begin
case(en)
2'b01: q = a[0];
2'b10: q = a[1];
default:q = 0;
endcase
end- 其综合后的RTL视图如下所示:
可以看出,使用case语句编写的逻辑代码综合出的电路是并行的,没有优先级,不影响电路的运行速度。
所以
- If 语句具有优先级,当if下的条件不满足时才执行else后面的部分。
- 而case语句是并行的,没有优先级,这在两者综合出来的RTL视图中可以明显的观察出来。
- 但由于现在的仿真和综合工具已经足够强大,最后综合后的结果if..else...与case...语句其实并无不同,只不过是两种不同的实现方式而已,因此基本上不用考虑这两者间的区别。在不影响功能的前提下设计师不需要做局部的优化工作, 例如不需要考虑if/case语句的资源耗费差异、不需要考虑优化电路。只有在影响功能(即时序约束报错)的前提下,根据提示对电路进行优化。
时序逻辑
:warning:需要注意,组合逻辑器件的赋值采用阻塞赋值=,时序逻辑器件的赋值语句采用非阻塞赋值<=
同步/异步复位的时序逻辑
- 时序逻辑的代码一般有两种:同步复位的时序逻辑和异步复位的时序逻辑。
- 在同步复位的时序逻辑中复位不是立即有效,而在时钟上升沿时复位才有效。其代码结构如下:
always@(posedge clk) begin
if(rst_n==1’b0)
代码语句;
else begin
代码语句;
end
end - 在异步复位的时序逻辑中复位立即有效,与时钟无关。其代码结构如下:
always@(posedge clk or negedge rst_n) begin
if(rst_n==1’b0)
代码语句;
else begin
代码语句;
end
endD触发器
- 数字电路中介绍了多种触发器,如JK触发器、D触发器、RS触发器、T触发器等。在FPGA中使用的是最简单的触发器——D触发器。
D触发器结构
- 可以将D触发器视为一个芯片,该芯片拥有4个管脚:
时钟clk、复位rst_n、输入信号d,输出管脚q。 该芯片的功能如下:
- 当给管脚rst_n给低电平(复位有效),即赋值为0时,输出管脚q处于低电平状态。
- 如果管脚rst_n为高电平,则观察管脚clk的状态,当clk信号由0变1即处于上升沿的时候,将此时d的值赋给q。若d是低电平,则q也是低电平;若d是高电平,则q也是高电平。
D触发器波形与代码
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
q <= 0;
end
else begin
q <= d;
end
end从语法上分析该段代码的功能为:
- 在“时钟clk上升沿或者复位rst_n下降沿”的 时候执行一次。
具体执行方式如下:
- 如果复位rst_n=0,则q的值为0;
- 如果复位rst_n=1,则将d的值赋给q




评论