UG949_RTL 编码指南
- 前言
- 基本功能
- 使用Vivado设计套件HDL模板
- 高效率HDL编码
-
- 循环
- 状态机指南
- 保存层级边界
- 避免触发器发生边沿混合
- 使用调试逻辑
- 数组型端口声明
- 扩展部分 控制信号和控制集
-
- 复位
-
- 分析1
- 分析2
- 时钟使能
前言
这里的编码指南跟数字IC代码规范或者代码风格还是有一些不同的,它的物理依据是不同型号的7系芯片架构,即FPGA上的资源(LUT、BRAM、IO、GT、走线等)都是有限的,而且部分资源的特性和位置已是固定的,为了更合理、高效的使用这些资源,这里的编码指南更像是针对该7系芯片的编码规范。值得说明的是,Vivado具有对代码检查的功能,DRC的结果会以多种形式显示出来,对于error必须要修改,否则编译不通过,对于严重警告,我的经验是最好也解决掉,否则可能即便在实现阶段通过,也会在生成bite流的过程中出错,普通警告最好也解决掉,至少要浏览告警信息。
基本功能
1、“xilinx建议对所有时序元件使用非阻塞赋值语句,对所有组合元件使用阻塞赋值语句,这样可以让仿真时间的序列更加符合预期,不易导致竟态条件”,不过我也见过在时序代码块中使用阻塞赋值的情景,好处在于可以节约一个时钟。
2、保证 敏感列表完整,“否则RTL仿真行为和实际电路行为相左”
3、design中的延迟(#delay)是无法综合到组件中的
4、注意避免设计外的锁存器生成
5、复位不规范,即在同一
always
块中,某些寄存器有复位逻辑、某些没有,正常来讲,这种情况在一些代码中还是很常见的,并不会引起明显的功能或时序问题,但这点,xilinx特别强调,因为这会引起控制级的增加,进而影响布局布线的最优求解。
使用Vivado设计套件HDL模板
信官方,无bug
高效率HDL编码
循环
HDL中循环同样很常见,虽说HDL实现循环像高级语言一样也有for(;;)关键字,但更建议使用if-else或case语句去实现,因为“工具更容易解读并生成有效的硬件”,通常,我习惯用case去实现大循环的框架,内部小循环使用if-else
状态机指南
这里讲了比较传统的状态机方法,我个人是更偏向于case语句实现状态机,但我不知道综合工具包含的FSM抽取算法能否把其识别出来?
保存层级边界
这里并不是要求模块层级间必须保存边界,而是要根据实际需求,通常开发初期建议保存边界,避免该层级中信号被跨层级优化,以至于在调试抓信号的时候,发现找不到该信号,确实很常见;但这样的弊端在于一些可以简并的逻辑被分别保存,“从而给设计的面积和时序造成不利影响”
避免触发器发生边沿混合
如果真的使用到两种不同的极性触发·,如DDR,请使用IDDR/ODDR原语
使用调试逻辑
为保证设计的纯粹性和最优性,建议在添加调试逻辑时单独出调试版本,因为调试所占用的逻辑也会影响实现阶段的最优求解。
数组型端口声明
VHDL是允许数组型端口的,但这与verilog冲突,不兼容,所以需注意
扩展部分 控制信号和控制集
重要!建议阅读原文,有两大部分复位逻辑和时钟使能
“控制集指用于驱动任何给定SRL、RAM 或寄存器的控制信号组(设置/ 复位、时钟使能和时钟)。对任意控制信号的独特组合,都能构成唯一的控制集。其后的原因是一个重要的概念,即每个Slice 中的寄存器都共享相同的控制信号,因此只有使用相同控制集的寄存器可以打包到同一个
Slice 中。
同时拥有多个唯一控制集的设计会造成大量资源浪费和布局选项数量减少,导致功耗上升,性能下降。从布局的角度而言,拥有较少数量控制集的设计能提供更多选项和更高灵活性,一般也能产生更加理想的结果。”
复位
建议阅读全文,只分析文中的编码实例,
分析1
别怪我代码乱,它本身就这样,直接复制过来的,我们主要关注它的复位逻辑。
首先,复位逻辑在主时钟域下进行打拍寄存,实现复位信号的同步,然后功能模块的复位控制是异步复位、下降沿有效。
// Reset synchronization
always @(posedge CLK) begin
reset_sync <= SYS_RST;
reset_reg <= reset_sync;
end
// Uses active-Low, async reset
// Also using an active-Low CE
always (posedge CLK, negedge reset_reg)
if (!reset_reg) begin
data1_reg <= 16’h0000;
data2_reg <= 16’h0000;
// ...
end else if (!NEW_DATA) begin
data1_reg <= DATA1;
data2_reg <= DATA2;
// ...
end
// Uses an async reset when a reset is not necessary
always @(posedge CLK, negedge reset_reg)
if (!reset_reg) begin
parity <= 4’h0;
data1_pipe <= 32’h00000000;
data2_pipe <= 32’h00000000;
// ... ...
end else begin
// ... ...
end
根据指南的建议,将上述代码重写
1、去除不必要的复位
2、修改异步复位为同步复位
3、修改低电平有效复位为高电平有效
// Reset synchronization, inversion moved here
always @(posedge CLK) begin
reset_sync <= SYS_RST;
reset_reg <= ~reset_sync;
end
// Notice the inversion above
// sync reset has become active High, though:
// from the top level port (SYS_RST) perspecive,
// it is still active Low.
// Also changed to active-High CE
always @(posedge CLK)
if (reset_reg) begin
data1_reg <= 16’h0000;
data2_reg <= 16’h0000;
// ..
end else if (NEW_DATA) begin
data1_reg <= DATA1;
data2_reg <= DATA2;
// ...
end
// Removed unnecessary reset on datapath
always @(posedge CLK) begin
data1_pipe <= data1_reg;
data2_pipe <= data2_reg;
// ... ...
修改后的代码,首先在同步复位信号的时候进行了电平反转,然后在always块中去掉复位敏感,并在后一个功能模块去掉不必要的复位逻辑,对比优势明显
分析2
源码,在对异步复位信号
rst_n
进行同步的过程中已经生成一个由LUT实现的反逻辑,因此可以在
synchronized_rst_n
与
synchronizer_ckt[3]
赋值的过程中,通过极性反转
assign synchronized_rst_n = ~synchronizer_ckt[3];
来消除这个多余LUT,避免引入额外的路径延迟
always @ (posedge clk or negedge rst_n) //async. negedge reset
begin
if(!rst_n)
synchronizer_ckt <= 4’b0; // 4 stage reset syncornization
else
synchronizer_ckt <= {synchornizer_ckt[2:0], 1’b1};
end
assign synchronized_rst_n = synchronizer_ckt[3]; // the final reset signal which is
used to reset the actual flops in the design
时钟使能
“
如果使用得当,时钟使能能够显著地降低系统功耗,同时对面积或性能的影响极小。但是如果不加区分地使用时钟使能,可能会造成下列后果:在许多使用大量控制集的设计中,低扇出时钟使能可能是导致控制集数量众多的主要原因。
1、面积增大
2、密度减小
3、功耗上升
4、性能下降
”