当前位置: 首页 > news >正文

时序约束优先级_如何写出时序收敛的代码

1a74823aefee80a6def60a01f7ead5d8.png

引言

硬件描述语言(verilog,systemVerilog,VHDL等)不同于软件语言(C,C++等)的一点就是,代码对应于硬件实现,不同的代码风格影响硬件的实现效果。好的代码风格能让硬件“跑得更快”,而一个坏的代码风格则给后续时序收敛造成很大负担。你可能要花费很长时间去优化时序,保证时序收敛。拆解你的代码,添加寄存器,修改走线,最后让你原来的代码“遍体鳞伤”。这一篇基于赛灵思的器件来介绍一下如何在开始码代码的时候就考虑时序收敛的问题,写出一手良好的代码。

1. Counter结构

计数器是在FPGA设计中经常要用到的结构,比如在AXI总线中对接收数据量的计算,用计数器来产生地址和last等信号。在计数器中需要用到进位链,进位链是影响时序的主要因素。如果进位链越长,那么组合逻辑的级数就越高,组合逻辑延迟越大,能够支持的最大时钟频率就会越低。在一个CLB中通常会含有一个进位链结构,比如在ultrascale中是CARRY8,在zynq7系列中是CARRY4,CARRY4可以实现4bit进位。如果是一个48bit计数器就需要12个这样的进位结构。一个CARRY4输出有两种CO和O,CO是进位bit,用于级联到下一级的CARRY4的CI,O是结果输出。因此我们可以看到在计数器中最下的进位结构是CARRY4,如果直接让多个进位结构级联,那么组合逻辑就会变大,时序延迟就会增大。如果可以将计数器拆分成小的计数器,那么时序就可以得到改善。

77f32e122b6580494c03e1798a4dcb7a.png

比如一个48bit计数器拆分成3个16bit计数器,那么CARRY4的级联级别就从原来的12个降低到4个。每4个之间增加了FF来进行时序改善。

always @(posedge clk)begin

if(rst)

cnt_o <= 0;

else

cnt_o <= cnt_o + 1;

end

拆分后代码为:

genvar i;

generate

for(i=0;i<3;i=i+1)begin: CNT_LOOP

wire trigger_nxt, trigger_pre;

if(i == 0)begin

always @(posedge clk)begin

if(rst)

cnt_o[i*16 +: 16] <= 0;

else

cnt_o[i*16 +: 16] <= cnt_o[i*16 +: 16] + 1;

end

assign trigger_nxt = (cnt_o[i*16 +: 16] == 16'hFFFF) ? 1 : 0;

end//if

else begin

assign trigger_pre = CNT_LOOP[i-1].trigger_nxt;

always @(posedge clk)begin

if(rst)

cnt_o[i*16 +: 16] <= 0;

else if(trigger_pre)

cnt_o[i*16 +: 16] <= cnt_o[i*16 +: 16] + 1;

end

assign trigger_nxt = CNT_LOOP[i-1].trigger_nxt && (cnt_o[i*16 +: 16] == 16'hFFFF);

end//else

end//for

endgenerate

综合后我们就可以看到它的schematic每4个CARRY4都被FF隔开了,可以降低逻辑延时。但是代价是增加了LUT的数量,这些LUT是用来判断前一个16bit计数器的数值的,从而驱动后边16bit寄存器计数。

0d32040c46cc4c853193499e96a35d1f.png

2. 逻辑拆分

在上一节中拆解计数器本质上就是在拆分组合逻辑。当一个组合逻辑过大的时候,延时较大。将其拆解成两个或者两个以上逻辑,中间增加寄存器可以来提高能跑得时钟频率。比如下图有一个较大的组合逻辑,前边有一个FF,后边连续接2个FF。组合逻辑的延时就成为了整体时钟频率的一个关键路径。如果我们可以将其拆分成两个,中间用一级寄存器连接,这样总共的时钟周期还是3个,但是时钟频率明显会好于前一种。

5dd584a64347bc2517363a9a914fbbc6.png

3. 改善扇出

扇出是指某个信号驱动的信号的数量。驱动的信号越多,那么要求其产生的电流越大。学过数字电路就会知道,当一个信号输出连接的越多的时候,其输出负载就会越小,那么输出电压就会减小。所以如果信号扇出过大就会影响到高低电平,最终就会导致时序不收敛。另外一个原因是如果信号扇出过大,那么由于FPGA上走线路径的差异,就可能造成这个信号到达不同地址的延迟不同,造成时序不同步。一种解决办法是复制,将扇出较大的信号复制几份,这样就可以减小扇出。比如一个输入d_i需要和3个数进行求和。那么这个信号扇出就是3.如果将其复制3份,给每个数输送一份,那么扇出就变为1。

always @(posedge clk)begin

data1_o <= data_i + data1_o;

data2_o <= data_i + data2_o;

data3_o <= data_i + data3_o;

end

460ab23de8692a2f3904ac78ac96b591.png

如果我们复制输入数据,如下图,从中可以看出输入信号复制了三份,分别接给三个加法器。

(* keep = "true" *)reg data_rp1;

(* keep = "true" *)reg data_rp2;

(* keep = "true" *)reg data_rp3;

always @(posedge clk)begin

data_rp1 <= data_i;

data_rp2 <= data_i;

data_rp3 <= data_i;

data1_o <= data_rp1 + data1_o;

data2_o <= data_rp2 + data2_o;

data3_o <= data_rp3 + data3_o;

end

8047b3c6fed3b0acf3a9b9e43c70f722.png

4. URAM和BRAM使用

Xilinx器件中BRAM的大小是36Kbit,如果不使用校验位,可以配置成1-32bit位宽的存储。比如32x1K。在RTL代码中使用存储的时候,需要适配BRAM大小,这样可以不浪费BRAM存储空间。比如你需要使用一个FIFO,那么这个FIFO位宽32bit,那么它的深度512和1024配置,都消耗了一个BRAM。

BRAM输出中最好用register,不要直接接组合逻辑,这样会增加延时。BRAM中含有register,如果代码中输出有用到register,那么这个register在综合时会被移到BRAM内部。如果BRAM外要连接组合逻辑,最好在BRAM的register的外部在添加一个register,这样有更好的时序。

1c89947c7a40f6bcb483e3434a6257f8.png

当我们需要的存储空间和位宽都超过了一个BRAM的时候,就涉及到多个BRAM的级联问题。如何选择单个BRAM的位宽拼接和级联BRAM的个数呢?比如我们要一个32bit位宽,深度为2**15大小的存储。有两种极限方式来配置BRAM。一种是将每个BRAM配置为1x32K,那么32个拼接组成32x32K的存储。另外一种是将每个BRAM设置为32x1K,那么32个级联形成32K深度。前一种不需要多余逻辑来对不同BRAM进行选择操作,但是32个BRAM同时读写,这样会增加power。而后一种32个BRAM级联在一起造成延时路径较长,同时需要增加组合逻辑来选择不同BRAM。但是每次只读写一个BRAM,power较低。可以选择这两个极限的中间值来即降低power也不会有太长的逻辑延时。可以通过约束条件来进行设置。如下图。级联设置为4,这样每次只有8个BRAM同时使能。

(* ram_style = "block", cascade_height = 4 *)

reg [31:0] mem[2**15-1:0];

reg [14:0] addr_reg;

always @(posedge clk)begin

addr_reg <= addr;

dout <= mem[addr_reg];

if(we)

mem[addr_reg] <= din;

end

a55c87bd67a4e9f37b4955af2361b453.png

URAM的使用方式类似,只不过URAM存储空间比BRAM大,其可以配置为72x64K大小。

5. 其它

1) 进行条件判定的时候,如果条件过多,尽量减少if-else语句的使用,尽可能用case替代。因为if-else是有优先级的,而case条件判断的平等的。前者会用掉更多逻辑;

2) 在一个always块中尽量对一个信号赋值,不要对具有不同判断条件的信号同时赋值,这样可以减少不必要的逻辑;

3) 尽量使用时钟同步复位,不要使用异步复位。即要用:

always @(posedge clk)begin

If(rst)

End

而不是

always @(posedge clk or posedge rst)

4) 在使用乘法较多的时候,使用DSP原语是最好的。一个DSP除了有乘法功能外,还有前加法器和后加法器,这两个是经常用到的,可以用来计算很多功能。DSP的具体使用可以参考DSP的手册。

总结

以上总结了几点在进行RTL代码设计时,最需要考虑的几种情况。这些对时序影响很大,需要注意。另外从整体来讲,如何选择一个好的算法,然后设计出一个简洁的架构更加重要。因为这些是从整体让你的设计有更多灵活的空间。

往期回顾

1 用LUT来搭建乘法器

2 在FPGA中实现高效compressor加法树

3 一文告诉你怎么解决cache miss问题

b059a94b3a84650480ebc9cdbe310208.png

相关文章:

  • 移动端布局三种视口_移动端布局适配
  • 柱形图无数据可选中_让领导看呆!Excel多层柱形图来了
  • ios 监听一个控制器的属性_iOS控制器间跳转
  • 语言中日期间的天数怎么计算_计算员工工龄,这个问题千万要注意
  • springboot过滤字段_springboot实现拦截器之验证登录示例
  • python计算机入门书籍_计算机学习--摘自python 入门书 侯爵
  • 如何卸载更换MySQL版本_mysql卸载(win10 适用于想更换版本的)
  • dos导入mysql文件_dos下导入mysql备份文件
  • java -jar 未响应_简单易学的测试攻略:JMeter测试Java请求示例
  • python 求余数_Python数据结构与算法——散列(Hash)
  • 此操作系统不支持 .net framework 4.6.2。_聊聊.net应用程序的Docker镜像
  • mysql binlog 实时_实时备份mysql binlog日志 脚本
  • oracle和mysql用户名_oracle 11g 默认用户名和密码
  • php mysql varchar_mysql中VARCHAR长度详细介绍
  • python图像读写技巧_Python图像读写方法对比
  • ➹使用webpack配置多页面应用(MPA)
  • android 一些 utils
  • echarts花样作死的坑
  • es6(二):字符串的扩展
  • gf框架之分页模块(五) - 自定义分页
  • Nodejs和JavaWeb协助开发
  • Odoo domain写法及运用
  • Python学习之路16-使用API
  • react 代码优化(一) ——事件处理
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • TypeScript实现数据结构(一)栈,队列,链表
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 前端攻城师
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 区块链将重新定义世界
  • 思考 CSS 架构
  • 我感觉这是史上最牛的防sql注入方法类
  • 应用生命周期终极 DevOps 工具包
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • 06-01 点餐小程序前台界面搭建
  • 容器镜像
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .net MySql
  • .NET 反射 Reflect
  • .NET关于 跳过SSL中遇到的问题
  • .NET简谈设计模式之(单件模式)
  • .NET开发人员必知的八个网站
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • .net专家(高海东的专栏)
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • @Bean, @Component, @Configuration简析
  • [.net 面向对象程序设计进阶] (19) 异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序...
  • []error LNK2001: unresolved external symbol _m
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下