一、基础入门
1、整体理解
简单来说,FPGA 就是“可反复编程的逻辑器件”。
ASIC 和 FPGA 就如同印刷品和白纸
ARM 虽然有很多外设,DSP 虽然具备强大的信号运算能力,但是,ARM 能做的,DSP 能做的,FPGA 一定也都能做;而 FPGA 可以做的,ARM 不一定行,DSP 也不一定行。
FPGA 固然强大,它高高在上的成本、功耗和开发复杂性还是会让很多潜在的目标客户望而却步。
2、学习方向
“熟练”:不仅要初步了解 FPGA 是什么、能做什么等基本的理论;更重要的是要学会 HDL语言( Verilog 或 VHDL), 能够使用 EDA 工具完成 FPGA 的代码设计、 仿真验证、 时序设计(这一步相对较难一些,往往需要结合实际应用,所以往往也可以属于下一阶段)、综合和映射,能够在开发板上下载并跑例程,这可以说是完成了入门阶段。
“精通”:如何用合适的 HDL 语法风格设计出最优化的电路;对 EDA 工具的使用,也不是仅仅会了就好,而应该让 EDA 工具的不同设置功能服务于具体的设计优化;同时也应该掌握不同的板级调试手段;
“专业”:参与一个新项目,开发一款新产品。
二、逻辑设计
1、简单晶体管实现基本逻辑门电路
2、在实际工程实践中
FPGA 必须是可编程的(Programmable),意味着我们至少必须要设计一个 2 输入、1输出的可编程的电路才能够名副其实。
前面的晶体管结构的逻辑门电路中,需要 6 个晶体管实现与门和非门电路。但是用 LUT
来实现,同样只是 4 个简单的地址和数据结构就能实现。
以 Xilinx Artix7 系列 FPGA 器件为例,它使用的是 6 输入 LUT 结构。这 6 个独立的输入(称为 A1~A6)可以配置为单输出(O6)模式和双输出(O5 和 O6)模式。如图 2.29 所示,6 输入 1 输出的 LUT 内部对应着 1 个 64 个地址的存储单元。
如图 2.30 所示,5 输入 2 输出的 LUT 内部对应着 2 个 32 个地址的存储单元,输出 O5
和 O6 都有其对应的独立的 32 个地址单元。它的应用场景是一组最多 5 输入同时对应 2 个输出的情况。
3、可配置逻辑块
一颗 FPGA 内部通常都会有丰富的可配置逻辑块(Configurable Logic Block,简称 CLB),根据不同的器件规模,CLB 的数量从数千到数十万不等。如图 2.31 所示,呈矩阵排布的 CLB 就构成了最基本的 FPGA 逻辑资源的架构。
从微观角度看,如图 2.32 所示,CLB 内部主要由 2 个更小的单位 Slice 所组成,每个 Slice都有独立的高速进位链以及独立的布线通道连接到矩阵开关,通过矩阵开关可以实现 Slice与 FPGA 大布线池之间的灵活编程
所有的 slice 的这些功能块都可以用于支持逻辑、运算或 ROM 功能,此时我们称之为
SLICEL;而某些 slice 则可以支持最大 256 位数据存储的分布式 RAM 或最大 32 个 8 位宽的移位寄存器,此时我们称之为 SLICEM。这种逻辑功能和存储器功能之间的转变其实很好理解,由于 slice 内部的 4 个 LUT 本身就是一种存储器实现的结构,所以它也就理所当然的可以被轻易的设计成 ROM、RAM 或移位寄存器功能了。
线(Wires):用于连接各个不同的模块单元,FPGA 内部通常有非常丰富的预连线资源,这些连线也都是根据实际应用设计“可编程”的;
输入/输出端口(Input/Output pads):FPGA 器件与外部芯片互联的引脚。
4、Xilinx 的 Vivado 软件上的编译步骤
首先编写“HDL代码”来描述自己需要实现的电路功能;
然后在 EDA 工具中对其进行“RTL 综合”和“综合”,
“RTL 综合”将 HDL 代码转换为逻辑电路,就如前面提到过的与、或、非等一大堆门电路的各种组合;
“综合”这一步将经过“RTL 综合”后的门电路映射为 FPGA 器件的物理结构;
“实现”这一步将对 FPGA 器件进行最终的布局布线;
最后这些结果将会被转换为可以最终运行在 FPGA 器件上的二级制烧录文件。
三、Verilog语法
1、基础
1、模块声明类语法:module…endmodule
2、端口声明:input, output, inout(inout 的用法比较特殊,需要注意)
3、参数定义:parameter
4、信号类型:wire,reg 等
如图 3.1 所示,在这个简单的电路中,分别定义两个寄存器(reg)锁存当前的输入 din。
每个时钟 clk 上升沿到来时,reg 都会锁存到最新的输入数据,而 wire 就是这两个 reg 之间直接的连线。作为 input 或 inout 的信号端口只能是 wire 型,而 output 则可以是 wire 也可以是 reg。
需要特别说明的是,虽然在代码中我们可以定义信号为 wire 或 reg 类型,但是实际的电路实现是否和我们预先的一致还要看综合工具的表现。例如 reg 定义的信号通常会被综合为一个寄存器(register),但这有一个前提,就是这个 reg 信号必须是在某个由特定信号边沿敏感触发的 always 语句中被赋值。
// 定义一个 wire 信号
wire <wire 变量名>;
// 给一个定义的 wire 信号直接连接赋值
// 该定义等同于分别定义一个 wire 信号和使用 assign 语句进行赋值
wire <wire 变量名> = <常量或变量赋值>;
// 定义一个多 bit 的 wire 信号
wire [<最高位>:<最低位>] <wire 变量名>;
// 定义一个 reg 信号
reg <reg 变量名>;
// 定义一个赋初值的 reg 信号
reg <reg 变量名> = <初始值>;
// 定义一个多 bit 的 reg 信号
reg [<最高位>:<最低位>] <reg 变量名>;
// 定义一个赋初值的多 bit 的 reg 信号
reg [<最高位>:<最低位>] <reg 变量名> = <初始值>;
// 定义一个二维的多 bit 的 reg 信号
reg [<最高位>:<最低位>] <reg 变量名> [<最高位>:<最低位>];
5、多语句定义:begin…end
6、比较判断:if…else, case…default…endcase
7、循环语句:for
8、任务定义:task…endtask
Task 更像是 C 语言中的子函数,task 中可以有 input、output 和 inout 端口作为出入
口参数,它可以用于实现一个时序控制。task 没有返回值,因此不可以用在表达式中。
task <task 命名>;
// 可选申明部分,如本地变量申明
begin
// 具体逻辑
end
endtask
9、连续赋值:assign,问号表达式(?:)
10、always 模块:(敏感表可以为电平、沿信号 posedge/negedge;通常和@连用)。
11、运算操作符:各种逻辑操作符、移位操作符、算术操作符大多是可综合的。
+ // 加
- // 减
! // 逻辑非
~ // 取反
& // 与11
~& // 与非
| // 或
~| // 或非
^ // 异或
^~ // 同或
~^ // 同或
* // 乘,是否可综合看综合工具
/ // 除,是否可综合看综合工具
% // 取模
<< // 逻辑左移
>> // 逻辑右移
< // 小于
<= // 小等于
> // 大于
>= // 大等于
== // 逻辑相等
!= // 逻辑不等于
&& // 逻辑与
|| // 逻辑或
12、赋值符号:= 和<=
13、Verilog 关键词
always endmodule medium reg tranif0 and end primitive module release
tranif1 assign endspecify nand repeat tri attribute endtable negedge rnmos tri0
begin endtask nmos rpmos tri1 buf event nor rtran triand bufif0 for not
rtranif0 trior bufif1 force notif0 rtranif1 trireg case forever notif1 scalared
unsigned casex fork or signed vectored casez function output small wait
cmos highz0 parameter specify wand deassign highz1 pmos specparam weak0
default if posedge strength weak1 defparam ifnone primitive strong0 while
disable initial pull0 strong1 wire edge inout pull1 supply0 wor else input
pulldown supply1 xnor end integer pullup table xor endattribute join remos
task endcase large real time endfunction macromodule realtime tran
VHDL 关键词
abs downto library postponed subtype access else linkage procedure then
after elsif literal process to alias end loop pure transport all entity map range
type and exit mod record unaffected architecture file nand register units
array for new reject until assert function next rem use attribute generate
nor report variable begin generic not return wait block group null rol when
body guarded of ror while buffer if on select with bus impure open
severity xnor case in or shared xor component inertial others signal
configuration inout out sla constant is package sra disconnect label port srl
对于低电平有效的信号或端口,通常加后缀“_n”表示。
2、例:寄存器电路的Verilog设计方式
① 简单的寄存器输入输出的模型如图 3.2 所示。每个时钟信号 clk 的有效沿(通常是上
升沿),输入端数据 din 将被锁存到输出端 dout。
// Verilog 例程
module dff(clk, din, dout);
input clk;
input din;
output dout;
reg dout;
always @ (posedge clk) begin
dout <= din;
end
endmodule
② 带异步复位的寄存器输入输出的模型如图 3.3 所示。每个时钟信号 clk 的有效沿(通常是上升沿),输入端数据 din 将被锁存到输出端 dout;而异步复位信号 clr 的下降沿(低电平有效复位)将强制给输出数据 dout 赋值为 0(不论此时的输入数据 din 取值),此输出状态将一直保持到 clr 拉高后的下一个 clk 有效触发沿。
// Verilog 例程
module dff(clk, rst_n, din, dout);
input clk;
input rst_n;
input din;
output dout;
reg dout;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) dout <= 1'b0;
else dout <= din;
end
endmodule
③ 带异步置位的寄存器输入输出的模型如图 3.4 所示。每个时钟信号 clk 的有效沿(通常是上升沿),输入端数据 din 将被锁存到输出端 dout;而异步置位信号 set 的上升沿(高电平有效置位)将强制给输出数据 dout 赋值为 1(不论此时的输入数据 din 取值),此输出状态将一直保持到 set 拉低后的下一个 clk 有效触发沿。
// Verilog 例程
module dff(clk, set, din, dout);
input clk;
input din;
input set;
output dout;
reg dout;
always @ (posedge clk or posedge set) begin
if(set) dout <= 1'b1;
else dout <= din;
end
endmodule
④ 既带异步复位,又带异步置位的寄存器则如图 3.5 所示。既带异步复位,又带异步置位的寄存器其实是个很矛盾的模型,我们可以简单的分析一下,如果 set 和 clr 都处于无效状态(set=0,clr=1),那么寄存器正常工作;如果 set 有效(set=1)且 clr 无效(clr=1),那么 dout=1 没有异议;同理,如果 set 无效(set=0)且 clr 有效(clr=0),那么 dout=0 也没有异议;但是如果 set 和 clr 同时有效(set=1,clr=0),输出 dout 咋办?到底是 1 还是 0?
其实这个问题也不难,设置一个优先级不就好了。当然了,图 3.5 的理想寄存器模型通常只是作为电路的一部分来实现。如果我们期望这种既带异步复位,又带异步置位的寄存器在复位和置位同时出现时,异步复位的优先级高一些,那么代码书写方式可以如下:
// Verilog 例程
module dff(clk, rst_n, set, din, dout);
input clk;
input din;18
input rst_n;
input set;
output dout;
reg dout;
always @ (posedge clk or negedge rst_n posedge set) begin
if(!rst_n) dout <= 1’b0;
else if(set) dout <= 1'b1;
else dout <= din;
end
endmodule
⑤ 如图 3.7 所示,这是一种很常见的带同步使能功能的寄存器。每个时钟 clk 的有效沿(通常是上升沿),判断使能信号 ena 是否有效(我们取高电平为有效),在 ena 信号有效的情况下 din 的值才会输出到 dout 信号上。
// Verilog 例程
module dff(clk, ena, din, dout);
input clk;
input din;
input ena;
output dout;
reg dout;
always @ (posedge clk) begin
if(ena) dout <= din;
end
endmodule
3、时序逻辑电路
在一个时序逻辑中,时钟信号掌控着所有输入和输出信号的进出。在每个时钟有效沿(通常是上升沿),寄存器的输入数据将会被采样并传送到输出端,此后输出信号可能会在经历长途跋涉般的“旅途”中经过各种组合逻辑电路并会随着信号的传播延时而处于各种“摇摆晃荡”之中,直到所有相关的信号都到达下一级寄存器的输入端。这个输入端的信号将会一直保持直到下一个时钟有效沿的来临。每一级寄存器都在不断的重复着这样的数据流采集和传输。
4、提升系统性能的代码风格
① 减少关键路径的逻辑等级
②逻辑复制(减少重载信号的散出)与资源共享
③ 消除组合逻辑的毛刺