目录
Altera FPGA 基本要素
FPGA 开发流程和适用范围
设计和实施规范
顶层设计的要点
Verilog HDL
语法规范
编写规范
设计技巧
编辑整理 by Staok,始于 2021.2 且无终稿。转载请注明作者及出处。整理不易,请多支持。
本文件是“瞰百易”计划的一部分,尽量遵循“二项玻”定则,致力于与网络上碎片化严重的现象泾渭分明!
本文系广泛撷取、借鉴和整理,适合刚入门的人阅读和遵守,已经有较多经验的人看一看图个乐,如有错误恭谢指出!本文已经是长期积累和堆叠而形成一定规模,不必按照从前到后的顺序去看,可以挑感兴趣的章节去看。
本文为简述风格,本意即记录要点和便于快速拾起。
本文对应的 Github/Gitee 仓库地址,本文最新的原文 和 一些源码、备查手册等等 均放在里面。
Altera FPGA 基本要素
p.s 过于基础的概念不提,这不是入门帖。入门可以跳到 “O.0 值得跟着的学习网站” 章节进行摄入。
p.s 以下以 Cyclone IV E 系列 FPGA 为例。
-
FPGA基础资源选择:逻辑单元(LE)数量,内嵌存储器(M9K)数量(总 RAM Bits 数),乘法器数量,PLL 数量,I/O 数量,全局时钟网络数量等。
-
板级电路组成:电源,时钟,复位,JTAG,固化配置信息 FLASH,外设。具体连接形式参考一些开发板和开源板子的原理图和 PCB。
- 电源:核心电源(标识 VCCINT,低压版本 1.0V,非低压 1.2V),IO BANK(标识 VCCIOx(x = 1 到 8),电压 1.2V 到 3.3V),PLL(模拟 PLL 标识 VCCAx(x = 1、2 或 4),其地标识 GNDAx(x 同前),电压 2.5V;数字 PLL 标识 VCCD_PLLx(x = 1、2 或 4),电压 1.2V),外设供电。不同系列 FPGA 的供电措施不同,具体要看电器参数等手册,尽量使用推荐值。
- 复位:上电后,FPGA 器件开始加载外部 FLASH 芯片的固化信息,加载完毕之后(最多 0.3s)再进行复位(低电平有效),阻容 RC 复位电路可选:R = 47kΩ,C = 10uF,3.3V 的 IO 标准下,充电到 1.7V 时间为 340ms。
-
全局时钟网络:专用时钟网络走线,同一时钟到达不同寄存器的时间差可以被控制到很小的范围内。外部输入时钟信号要连接到 “全局时钟专用引脚” 上。FPGA 的综合工具会自动识别和分配。
-
I/O:输入和输出时钟信号尽量分配到专用引脚上。差分信号对儿必须分配到支持差分的专用引脚上。高速信号分配到支持高速传输的专用引脚上(如 DDR 的专用 IO 接口)。一些硬核使用的引脚可能是固定的要注意。总线信号尽量分配到同一个 BANK。一些产生噪声干扰的信号(如时钟信号)尽量远离器件的配置喜欢和其它敏感的信号。
-
调试和固化:
更多详细参考:
- FPGA配置方式。
- FPGA的各种功能管脚。
- Altera特殊管脚的使用。
- 官方手册里是最全的、最准的,多看!
具体看官网手册 “Cyclone IV Device Handbook Volume 1” 的 “Configuration Process” 章节和 “Configuring Altera FPGAs.pdf” 手册。
-
调试为通过 JTAG 接口用 Blaster 下载器把编译生成的 .sof 文件下载到 FPGA 内掉电易失的 SRAM 中。
-
固化是通过 JTAG 接口用 Blaster 下载器把编译并转化生成的 .jic 文件下载到 FPGA 对于的外部 FLASH 器件中。FPGA 上电从 FLASH 中获取配置信息,分为几种不同的配置模式,根据 [3:0]MSEL 四个引脚上电时的电平状态确定,而具体的 [3:0]MSEL 与 启动方式的关系 看对应 FPGA 芯片系列型号的手册。配置模式分为以下几种:
AS(主动串行),适用于小容量。由 FPGA 器件引导配置过程,EPCS 系列 FLASH 专供 AS 模式。一般用此模式。
AP(主动并行),速度快,占 I/O 更多,适用于大容量 FPGA 器件。EPC 系列 FLASH 用于此。
PS(被动串行),需要外部 MCU 或 CPLD(如 MAX II 系列)控制 FLASH 的数据打入 FPGA,此方式最灵活,对于多个 FPGA 或者要自动更换固件用此模式。
等其他。
FPGA 开发流程和适用范围
-
开发流程:需求分析,模块划分,实现,前仿真,分配 IO,时钟信号约束 + 其他信号时序分析和约束,后仿真,下载验证和调试,固化代码(注意是有顺序的)。1.4 Verilog 设计方法 | 菜鸟教程 (runoob.com)。
-
FPGA 固有灵活性和并行性。FPGA 应用领域列举:逻辑粘合,实时控制,高速信号采集和处理,协议实现,仿真验证系统,片上系统 SoC。
-
处理器和 FPGA 分工:MCU、MPU 适合做管理、协调,FPGA 的数字逻辑适合做专用的、复杂的、结构和功能固定下来的算法实现。
-
推荐多去读读 FPGA 原厂(Altera 或 Xilinx)的官方文档,在它们的一些文档手册中有各种常见的电路的参考实现实例和代码风格。
-
板级 PCB 走线遵循 “PCB走线规范”。
-
…
设计和实施规范
这里的规范仅为初级,另有 “HuaWei Verilog 规范” 等规范可供参考。
顶层设计的要点
- 单个模块尽量使用一个时钟源;对于多个模块要认真、清楚的划分时钟域;跨时钟域的信号一定做同步处理(D触发器同步);片内的 PLL / DLL 资源尽量利用起来;至少要对所有时钟信号加上简单的时序约束,不能没有。
- 数据传递的两边速率不一致要在中间加 缓存机制,常见的如 FIFO 和 乒乓缓存,后者详见 “设计技巧” 小节里的 “乒乓操作” 部分。
- 复杂逻辑/时序逻辑要使用 FSM (有限状态机)方式来写,在下面的 “模块收集” 里面有状态机的例子。
- 条件逻辑/状态图等一定要遍历所有状态,一定,防止不可预料的错误综合结果,对于 if 要有 else,对于 case 要有 default。
- 对于仿真:先对每一个单个模块仿真,要求代码覆盖率、条件分支覆盖率、表达式覆盖率必须达到 100%,这三个可以通过 Modelsim 查看;子系统仿真,将多个模块组合在一起进行仿真,覆盖率尽量高;系统级仿真,软硬件整板联调。仔细设计仿真激励文件。
- 通常采用自顶向下的设计方式。先确定系统有哪些输入和输出,把系统划分成多个子功能模块(框图模块),每个功能模块再划分下一层的子模块(HDL 意义上的模块),最后的每个模块的设计对应一个 module ,可以一个 module 设计成一个 verilog HDL 文件。
- 在 FPGA 逻辑全编译之前,尽量将全部 顶层 IO 分配给 实体芯片的引脚 而 不要空置,没用到的输入信号也要 assign 到 确定的 0 或 1,这样不但保证 确定的逻辑行为,而且如果不做那么全编译时 Timing 时序 无法保证 从而 亮红。
- 工程文件夹划分规范:prj 为工程文件存放目录; rtl 为 verilog 可综合代码存放目录; testbench 为测试文件存放目录; img 为设计相关图片存放目录; doc 为设计相关文档存放目录; prj 文件夹下还建立了子文件夹 ip,用于存放 Quartus Prime 中生成的 IP 核文件。
Verilog HDL
语法规范
-
No.1,层次化设计,IP 化设计。自写小 IP 尽量参数化、可重用,方便日后搭建数字积木。
-
顶层文件名与顶层模块名一致。
-
模块的定义名加尾缀"_ module",输入输出的信号名各加后缀"_ in"和"_ out",低电平有效的信号加尾缀"_ n"或“#”,时钟信号使用"clk _“或"Clk _“前缀,复位信号使用"rst _“前缀,使能信号使用"en"或者"Enable"标识等。
-
定义模块的时候,定义输入输出信号时就带好 “input”/“in” 、 “output”/“out” 和 “reg” 等的标识修饰。
-
一个 tab 四个空格。
-
用 tab 划分清晰的语句层次,用 tab 对齐多行同层次语句等。
-
begin 和 end 语句块修饰词在竖方向对齐。
-
操作符等前后用一个空格做间隔。
-
注释齐全,对自己和别人负责。
-
以下用一例子包含 verilog 常用语法。
/* 这里是注释 */
// 还是注释/*Verilog 保留字always and assign begin buf bufif0 bufif1 case casex casez cmosdeassign default defparam disable edge else end endcase endmoduleendfunction endprimitive endspecify endtable endtask eventfor force forever fork function highz0 highz1 if ifnoneinitial inout input integer join large macrmodule medium modulenand negedge nmos nor not notif0 notif1 or outputparameter pmos posedge primitive pull0 pull1 pullup pulldownrcmos real realtime reg release repeat rnmos rpmos rtran rtranif0rtranif1 scalared small specify specparam strong0 strong1 supply0 supply1table task time trantranif0 tranif1 tri tri0 tri1 triand triortrireg vectored wait wand weak0 weak1 while wire wor xnor xor
*//* 引用自 https://blog.csdn.net/luxinwylive/article/details/99827766(1)所有综合工具都支持的结构:always,assign,begin,end,case,wire,tri,aupply0,supply1,reg,integer,default,for,function,and,nand,or,nor,xor,xnor,buf,not,bufif0,bufif1,notif0,notif1,if,inout,input,instantitation,module,negedge,posedge,operators,output,parameter。(2)所有综合工具都不支持的结构:time,defparam,$finish,fork,join,initial,delays,UDP,wait。(3)有些工具支持有些工具不支持的结构:casex,casez,wand,triand,wor,trior,real,disable,forever,arrays,memories,repeat,task,while。
*//* wire 类型变量定义物理连线,不保存东西,reg 类型变量定义寄存器,用于保存东西 *//*引自 https://zhuanlan.zhihu.com/p/72012739wire 用法总结1.wire可以在Verilog中表示任意宽度的单线/总线2.wire可以用于模块的输入和输出端口以及一些其他元素并在实际模块声明中3.wire不能存储值(无状态),并且不能在always @ 块内赋值(=或<=)左侧使用。4.wire是assign语句左侧唯一的合法类型(assign 后面跟着的必须是一个 wire 类型)5.wire只能用于组合逻辑reg 用法总结1. 声明寄存器,可以存储信息(有内存,有状态)允许连接到模块的输入端口,但不能连接到一个模块的实例化的输出2. 在模块声明中,reg可以用作输出,但不能用作输入3. 在always@(......)语句块内,= 或者 <= 赋值语句的左边必须是是reg变量在initial语句块内,= 赋值语句的左边必须是是reg变量4. Reg不能用于assign赋值语句的左侧5. 当与@(posedge clock)块一起使用时,reg可用于创建寄存器6. reg可用于组合逻辑和时序逻辑
*//* 连续赋值语句(assign)用于对线型变量(wire)的赋值,不能够出现在任何一个过程块(begin ... end)中;连续赋值语句(assign)定义组合逻辑,声明物理逻辑的关系;线型变量一旦被连续赋值语句赋值后,赋值语句右端表达式中的信号有任何变化,都将实时地反映到左端的线型变量中 *//* 过程赋值语句(= 和 <=)完成对寄存器变量(reg)的赋值,只能在过程块语句中被赋值;过程赋值语句只有在语句被执行到时,赋值过程才能够进行一次,而且赋值过程的具体执行时间还受到各种因素的影响 *//*数据类型:5'o37 5 位八进制数,二进制为 1111110'o37 右对齐,高位补 010'bx0x1 左边补 x,完整即 x x x x x x x 0 x 1,x 表示未知状态4'b1x_01 4 位二进制数,为 1 x 0 1,下划线方便阅读4'hz 4 位z(扩展的z) , 即 zzzz,z 表高阻状态parameter SEC_TIME = 48_000_000; 十进制数位长不能够为变量表达式,可以为预编译、parameter 的表达式verilog 中 整形、浮点型等变量的 定义字 相当于 define 或者 parameter 的作用,这里只用 后二者即可了字符串reg [8*14 : 1]Message = "INTERNAL ERROR"; I 为第 1 位,N 为 第 2 位,依此类推数组reg [wordsize : 0]my_memory[arraysize : 0];引用数组某个数的某个位my_memory_1 = my_memory[1];my_memory_1_bit0 = my_memory_1[0];verilog 不支持 数组作为 模块的输入或输出,systemVerilog 支持运算;算术运算符(+,-,x,/,%)赋值运算符(=,<=)关系运算符(>,<,>=,<=)逻辑运算符(&&,||,!)条件运算符(?;)位运算符 (~,|,^,&,^~) 对于 & 运算用法之一:assign max_avl_address = &avl_address; 则 avl_address 最大(全1)的时候 max_avl_address 为 1,否则为 0移位运算符(<<,>>)拼接运算符({})
*//*预编译:宏定义:`define WIDTH 8引用:reg [`WIDTH-1:0] s1; 原样替换`ifdef 宏名 (标识符)程序段1`else程序段2`endif
*//*for 语句,尽量不要用,要使用 计数器 + case 语句 来替代https://blog.csdn.net/messi_cyc/article/details/79098444
*//*使用语句实现 边沿检测https://blog.csdn.net/bleauchat/article/details/85322247
*//* 模块注释规范 */
/********************************************************************************************************** File Name : xxx.v* Author : xxx* Version : V1.0.0* Date : 20xx-xx-xx* Brief : xxxxx******************************************************************************************************** History* 1.Author: xxx* Date: 20xx-xx-xx* Mod: xxxxx** 2.Author: xxx* Date: 20xx-xx-xx* Mod: xxxxx*********************************************************************************************************/// *********************************************************************************
// Project Name :
// Author : xxx
// Email :
// Blogs :
// File Name : xxx.v
// Module Name :
// Called By :
// Abstract :
//
// CopyRight(c) 2018-2021, xxx Studio..
// All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// xxxx/xx/xx xx 1.0 Original
//
// *********************************************************************************
module example_module
(/*输入信号*/input clk_in, /*时钟输入*/input rst_n_in, /*复位(低有效)信号输入*//*输出信号*/output reg [7:0]q_out, /*q 左移位输出,要用语句块赋值,所以定义为 寄存器类型 */output reg [7:0]p_out /*p 右移位输出*//* 寄存器组定义 reg [7:0]Mem[0:1] 即 2 个 8 位的 Mem*/output output_1 = 0,output_2 /* 缺省为 wire 线网类型,可以定义初始值 *//* tri 主要用于定义三态的线网 */);/* 定义常量参数 */parameter bit_7 = 7,bit_8 = 8;/* 三目运算例子 wire [2:0] Student = Marks > 18 ? Grade_A : Grade_C;assign LR = (LR_select[1] == 1'b1) ? 1'bz : LR_select[0];*//* 时序逻辑定义,对 q 左移位输出*/always @(posedge clk_in or negedge rst_n_in)begin/* 顺序执行 */if(!rst_n_in)beginq_out <= 8'bzzZz_0001; /* 总线赋值 */endelsebeginq_out <= { q_out[6:0] , q_out[bit_7] }; /* 使用位拼接,左移位 */endend/*对 p 右移位输出*/always @(posedge clk_in or negedge rst_n_in)beginif(rst_n_in == 1'b0)beginp_out <= 8'b1000_0000;endelsebeginp_out <= { p_out[0] , p_out[7:1] }; /* 使用位拼接,右移位 */endend/* 在模块里面调用模块,即 FA_struct 模块例化,并建立连接 */FA_struct FA1(.A (q_out[1]),.B (p_out[1]),.C (rst_n_in),.output_1(), /* 该引脚悬空,如果是 example_module 模块的输入则变为高阻,如果是输出则弃用 */ .output_2(output_2));endmodule/* case 语句例子
case(case_expr)case item_expr : procedural_statement;. . .. . .[default:procedural_statement]
endcase
*//* 门级描述组合逻辑电路 */
module FA_struct
(input A;input B;input C;output output_1;output output_2;
);/* 模块内连线 */wire S1, T1, T2, T3;xor x1 (S1, A, B);xor x2 (output_1, S1, C);and A1 (T3, A, B );and A2 (T2, B, C);and A3 (T1, A, C);or O1 (output_2, T1, T2, T3 );endmodule/*一个参数化模块设计例子定义:
module Sdram_Write
#( parameter DATA_WIDTH = 16, 注,#() 这个部分用于模块参数化配置,对于 verilog 不可综合parameter ADDR_WIDTH = 12,parameter ROW_DEPTH = 2,parameter COL_DEPTH = 256,parameter BURST_LENGTH = 4, //burst lengthparameter ACT_DEPTH = 1,parameter BREAK_PRE = 1
)
(input clk,input rst_n,input wr_trig,input wr_en,input ref_rq,output reg [3:0] wr_cmd,output reg [ADDR_WIDTH - 1:0] wr_addr,output wr_rq,output reg wr_end_flag,output [1:0] wr_bank_addr,output [DATA_WIDTH - 1:0] wr_data,//wfifooutput wfifo_rd_en,input [7:0] wfifo_rd_data
);例化:
Sdram_Write
#( .DATA_WIDTH ('d16),.ADDR_WIDTH ('d12),.ROW_DEPTH ('d1),.COL_DEPTH ('d4),.BURST_LENGTH ('d4), //burst length.ACT_DEPTH ('d1),.BREAK_PRE ('d1)
)
Sdram_Write_inst(.clk (clk),.rst_n (rst_n),.wr_trig (wr_trig),.wr_en (wr_en),.ref_rq (ref_rq),.wr_cmd (wr_cmd),.wr_addr (wr_addr),.wr_rq (wr_rq),.wr_end_flag (wr_end_flag),.wr_bank_addr (wr_bank_addr),.wr_data (wr_data),.wfifo_rd_en (wfifo_rd_en),.wfifo_rd_data (wfifo_rd_data)
);*/
编写规范
-
以时钟信号同步的时序逻辑编写时尽量只用非阻塞赋值”<="(同步执行),用阻塞赋值”="(顺序执行)可能会产生bug,后者一般用于组合逻辑设计。尽量避免使用异步信号(比如异步总线等),即慎用或少用 assign 语句连接逻辑,而尽量把所有逻辑在 always @(*) begin … end 中实现;如果传入一个异步信号,尽量加寄存器(D触发器)用时钟进行锁存。
-
尽量大部分功能使用时序逻辑电路设计,使用行为语句 + 时序逻辑电路描述(“always@” + “<=”) 完成建模(对于 reg 类型变量)。对于组合逻辑电路描述,简单逻辑可以使用连续赋值语句(“assign” + “=”)(对于 wire 类型变量),对于复杂组合逻辑使用 “always@( 所有敏感信号 )” + “=” 的语句。
-
Always 块的一般形式为:
/* 这里加注释对该模块进行功能描述 */ always @(negedge clk_in or negedge i2s_module_rst) /* 在时钟的边沿触发,再加一个复位触发条件 */beginif(!i2s_module_rst) /* 先判断是否复位 */begin /* 在复位块中,因该对 else 情况里面的所有 被幅值的 reg 变量进行 复位,都设置为复位值,必要! */WS <= 1'b1; /* 添加语句描述 */endelsebegin /* 保持格式 */if(one_flame_counter < half_flame_count)beginWS <= 1'b0;endelsebeginWS <= 1'b1;endendend
-
case 语句必须带 default 分支,照顾到 case 的所有情况;if 语句必须带 else 分支;即分支语句要 写到/考虑 所有情况。
-
所有的内部寄存器都应该能够被复位,尽量每个模块都要有时钟同步复位信号(不要用异步复位)。
-
设计逻辑尽量避免不定态 x 或者高阻态 z 进入参与关键逻辑区域,仿真时就注意。
-
移位操作直接用位拼接。
-
同一个信号在很多地方使用,比如参数和时钟等等,应该在每一个用到的地方加一个寄存器(D触发器)用于中继缓冲,避免一个信号扇出信号数量过多。
-
常用的,时钟上升沿锁存数据,时钟下降沿改变数据。
-
从可综合性角度考虑,应慎用各种循环语句(for,while 等,因为编译器仅将其展开成重复语句,过多占用逻辑),大部分情况下,用于设计的循环语句可以用其他方式所替代,比如用 case 语句替代循环语句。并行块(fork … join)不可综合且容易出现竞争问题,在仿真设计中不建议使用。
-
逻辑表达式不要写的太长,可以简化逻辑(卡诺图法或者公式法,或者 multisim 里面的逻辑分析仪简化逻辑表达式)或者分多行去写,即不要让 RTL 图中某一段逻辑链过于长;长逻辑表达式用括号划分清关系减少歧义。
-
竞争与冒险的概念:逻辑电路中,由于门的输入信号经过不同的延时,到达门的时间不一致,这种情况叫竞争;由于竞争而导致输出产生毛刺(瞬时错误),这一现象叫冒险。为避免组合逻辑的输出出现“毛刺”,即冒险或竞争的发生,可以在输出加一个寄存器(D触发器),即让输出与时钟同步,当所有信号都到达寄存器(D 触发器)的输入后,时钟再“打一拍” 进行锁存 才能输出,这样避免最后的输出有“毛刺”;避免锁存器,使用触发器。
-
对于有 选择 和 加法、比较 等逻辑块,编写时应让信号先经过 选择器,再送入 乘法器、加法器 或 比较器 等,即“先选后比,先选后加,先选后乘”。逻辑电路面积大小对比:乘法器 > 加法器 > 比较器 > 选择器。
-
尽量不要用减法和除法(一个考虑多,一个面积大);乘以常数直接用 “*”,编译器会优化;两变量乘法用硬件乘法器IP。
-
使用 function 函数语句对复杂数值运算打包(它不能包含任何时间控制语句);函数(function)可以调用其他函数(function)但不能调用任务(task),(function)函数由 任务(task)或其它 module 中调用。使用 task 语句写可重用的、固定下来的组合逻辑(不能有时序逻辑 always,不能有 wire 类型数据,这就是和 module 的区别;任务(task)可以调用其他任务(task)和函数(function),任务(task)只能在 module 中的语句块中被调用)。
-
可以用 generate for 写 同结构不同参数 的 always@(*) 等代码,用 generate if/case 写一些随着需求可变的代码或 IP 核。 generate 语句属于预编译语句。
-
FGPA 的功耗与被使用的触发器或门电路的数量及其翻转次数成正比,尽量减少高速翻转的触发器数量是降低 FPGA 功耗的根本方法之一。
设计技巧
p.s 以下内容引自 信息理论与技术教研中心 别志松 的 PPT 《复杂数字系统设计的常用技巧》,本文作者又做了一些补充。
解决速度与面积矛盾的基本原则:1、向关键路径要速度。对于关键路径,可以采用牺牲面积换取速度的方式。2、向非关键路径要面积。对于非关键路径,通过各种方式换取面积。
基本途径:1、EDA工具的约束和优化方式的设置。2、优化代码。以下就是代码优化的一些方法。
速度的三重定义
- Throughput:吞吐量
- 单位时间内能处理的数据量。
- 常用单位有bit/s,等。
- Latency:时延
- 指的是从数据输入到达至相应数据输出之间的时间。
- 单位是微秒等。
- Timing:时序
- 指的是时序单元之间的路径所对应的时延。
- 通常说时序关系不满足一般指触发器间关键路径的最大时延超过目标时钟周期。
- 标准度量是时钟周期或频率。
速度的三个方面的折中
- Latency 换 Throughput。
- Timing 换 Throughput。
- Timing 换 Latency 等。
提高吞吐量的方法
-
流水线技术
流水线技术就是把本来只能在一个较长的时钟周期内执行的操作(组合逻辑)分成几步较小的操作,并在多个较高速的时钟内完成。这些步骤的划分是通过多级寄存器来实现的。前级寄存器处理新输入数据的同时,末级寄存器产生老输入数据所对应的输出。
-
多路并行处理
将耗时较长的电路复制若干份,每份处理部分数据。这种处理方法需要对输入数据进行分解,对输出数据进行合并。主要用于减小 Latency。
减小延时的主要思想
- 尽快将数据从输入传递到输出,减小中间过程处理时延,即减少数据处理链的长度。
- 流水线技术不符合低 Latency 的要求。经常采用的方法:
- 并行处理。
- 去掉流水线。
- 减小 Latency 的代价:有可能减少吞吐量;会造成关键路径时延增大。
- 既要保证 Throughput,又要保证较小的 Latency,只能采取前述并行处理方法。
改善时序性能的方法
即提高最高运行速度。
-
添加中间寄存器层。流水线化。防止中间纯组合逻辑链太长导致延时太长,即防止一个语句特别长运算特别多,所以中间添加寄存器层。
将多数据的数学运算分为多个运算的组合,这些组合是并行处理的。
-
关键路径改造为并行结构。并行处理。减少串入串出,增加输入和输出的数量,写为并入并出。
-
展平逻辑结构。也是增加并行。
-
减小扇出。
面积优化方法
- 精简代码。引用上面“编写规范”一节:
- 对于有选择和加法、比较等逻辑块,编写时应让信号先经过选择器,再送入乘法器、加法器或比较器等,即“先选后比,先选后加,先选后乘”。面积:乘法器 > 加法器 > 比较器 > 选择器。
- 不使用除法和减法。等等等等。
- 资源共享。
- 模块化。声明会被高利用率的寄存器。
- 等。
- 合理使用复位信号。
乒乓操作
“乒乓操作” 是一个常常应用于数据流控制的处理技巧。数据缓冲模块可以为 单\双口 RAM、FIFO 等。向缓冲区 1 存数据的时候,缓冲区 2 向外出数据,向缓冲区 2 存数据的时候,缓冲区 1 向外出数据,以此循环。
- 乒乓操作的最大特点是通过 “输入数据选择单元 ”和 “输出数据选择单元” 按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到 “数据流运算处理模块” 进行运算与处理。
- 把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数据流进行流水线式处理。
- 所以乒乓操作常常与流水线结合使用,完成数据的无缝缓冲与处理。
乒乓缓存的实施
- 两个单口 RAM 方式。设计一个 MUX 模块,输入为一个类似 SRAM 接口,其后面控制两个单口 RAM(外部 SRAM 芯片 或 FPGA 内建 RAM),输出一个 类似 SRAM 接口芯片和 通知可读的信号(通知后面,一个 RAM 块已经写满可以快速读出)。
- 一个双口 RAM 方式。设计一个 MUX 模块,输入接口同上,其 后面控制 一个 双口 RAM(外部芯片或 FPGA 内建),MUX 模块的输入数据就不断循环 从地址 0 到最大地址 存进该 RAM,当存到一半的时候 输出一个 通知可读信号,即当 前半部分 存满后 可以读前半部分(此时 MUX模块正在后半部分存),当后半部分存满后可以读后半部分(此时 MUX模块又回到前半部分地址开始存),这样 一个 双口 RAM 的前后两半作为两块 RAM 进行乒乓操作。