Loading

2022.09-2023.05数字前端工作总结

0. 前言

去年9月到现在总共做了两次ic的数字部分的开发,第一个设计是一个rfid的逻辑部分,第二个设计是一个多核处理器,我的工作主要是做架构设计以及主体代码的编写,模块代码一般分配给组里的其他研究生做。在做的过程中对一些问题有了反思,主要从工作流,设计约束和代码质量三个角度来做一下总结,希望反思能够带来更多的进步。

1. 工作流

我开发的基本flow是:粗设计规划(先明确此次设计的主要功能和大致结构,产生功能文档)-> 细设计规划(明确到有哪些模块与模块接口,细化功能文档,明确各个模块功能,编写模块端口表)-> 编写框架(将模块端口表转化成框架verilog代码,规定端口和关键信号)-> 分配模块(以文档和框架代码作为约束控制组员的代码实现)-> 模块汇总(整理完成的各个模块,仿真检验跨模块功能)-> 封顶测试(建立完整顶层,更新实现过程中文档里out of date的部分,整体转交验证)-> 代码综合(编写sdc,做综合,将rtl代码转为门级网表)-> 交付后端(提供文档,文档中说明物理尺寸约束和出线位置规划,以及网表和时序约束)-> 后仿验证(用后端做完的网表和sdf做后仿真对比前仿真的case,确定符合功能规划)

在具体工具上主要是VCS,Verdi,Design Compiler。验证时上fpga会用到Vivado。编写bench时会使用到Matlab,C,C++,Python这些高级语言。另外AI也是我新加入工作流的一个重要工具(我使用new bing,软件是一款跨平台桌面客户端的BingGPT),不管是作为编程助手,还是翻译软件,还是资讯检索,还是作为代码批处理工具都挺好用的,并且作为一款“自然语言编程工具”,AI具有极大的潜能,可以说其能发挥多大的作用是完全取决于使用者的,为此我专门去研究了一下prompt engineering。

我们的数字后端都是外包团队做的,所以只能够以我一般交付后端时的标准来谈。规范的产品开发流程肯定会比我这里复杂很多,毕竟高校研究所,就这么个把号研究生的草台班子。

2. 设计约束

首先明确一下目标,Design Compiler跑完综合后必须要看报告。即:

report_constraint -all_violators -verbose          > $report_path/constraint.rpt
report_qor                > $report_path/qor.rpt
report_power              > $report_path/power.rpt
report_area               > $report_path/area.rpt
report_cell               > $report_path/cell.rpt
report_clock              > $report_path/clk.rpt
report_hierarchy          > $report_path/hierarchy.rpt
report_design             > $report_path/design.rpt
report_reference          > $report_path/reference.rpt
report_timing             > $report_path/timing.rpt

check_design > $report_path/check_design_post_compile.rpt
check_timing > $report_path/check_timing_post_compile.rpt

这几个report里的内容,时序报告看constraint.rpt和timing.rpt,代码质量看check_design和check_timing,面积和功耗看power.rpt,area.rpt。constraint.rpt主要报一些违例,一般是约束写的问题,尽量在前端清理掉,setup的问题可以降低频率重新跑,hold的问题可以让后端插buffer来解,当然也可以梳理一下自己是否有逻辑块写的太大了导致的时序问题,如果是的话,可以在中间再插几级寄存器处理成多拍的。timing.rpt会报一个最大延时路径,与时钟周期比较,如果slack算出来是正的就ok,但是最好还是留一些余量,因为实际带上RC延时后,slack如果小了的话还是会超掉的。

check_design_post_compile.rpt尽量clean,真的有不可综合的语法的话应该design compiler都跑不完,所以check_design查出来的主要是一些信号的短路,冗余的线之类的问题,查一遍看看有没有不符合设计预期的,另外都清理掉会让后端做的时候更舒服一些。check_timing_post_compile.rpt必须确保clean。

设计约束这个事情一言以蔽之:写sdc。我写到现在还是不觉得自己能够把sdc写好了,只是大概知道要写什么内容,接下来说的一些点也只是我的个人经验,欢迎有经验的网友在评论区提供批评建议。

对于简单的单时钟的case,主要是确定几个重要参数,一般pnr工具的约束就看.lib里的和.sdc定义的,哪个更紧用哪个,所以写sdc的时候可以去参考.lib里面的一些参数,找一下要用到的cell的时序信息,看看自己要不要设置的更紧一点,因为如果设置的比.lib还松是没有意义的。input_delay,output_delay,这两个参数比较看经验,我还没有实测过流回来的芯片,师兄给的脚本设置的是时钟周期的20%,感觉有点过松,我看网上设置的情况有直接设置到70%的,不知道设置的这么松会不会在芯片实测时出现问题。

多时钟相对复杂一点,主要是需要配合设计一起看,create_generated_clock在使用时需要加上-add参数,以及处理好pin的问题(这个事情和下面check_timing联系在一起了)。

3. 代码质量

首先可综合的问题就不说了,这个太入门了,学过数集应该第一节课就说过这个问题。但是能混过仿真器和综合器的代码也不一定能用,在check_design和check_timing阶段就会把问题暴露出来,我谈一谈我遇到的几个case:

check_design:

  1. Warning: In design 'xxx', port 'xxx' is not connected to any nets. (LINT-28)

位宽问题,譬如一个模块拉了一个10b位宽的信号出去,但是在外面只连接了低8b的信号,那么高2b就会报这个问题,解决方法就是在引出时只拉需要用的线。

  1. Warning: In design 'xxx', input port 'xxx' is connected directly to output port 'xxx'. (LINT-29)

输入到输出信号直接连接,如果非必要的话,那就直接让信号到外部连线,不要穿过这个模块。

  1. Warning: In design 'xxx', the same net is connected to more than one pin on submodule 'xxx'. (LINT-33)
    Net 'xxx' is connected to pins 'xxx', 'xxx'', 'xxx', 'xxx'

一般普通的多驱动不会引发这个问题(clk和rst_n就是典型的多驱动),我这边出现这个情况还是信号位宽不匹配引发的,外部一个9b的信号连接到了一个16b模块的端口,所以用了符号位拓展的写法{{7{signal[8]}},signal},但实际上最好还是直接让两边位宽匹配上。

  1. Warning: In design 'xxx', output port 'xxx' is connected directly to 'logic 1'. (LINT-52)
    Warning: In design 'xxx', output port 'xxx' is connected directly to 'logic 0'. (LINT-52)

输出端口有直接的赋值就会引发这个warning,注意检查一下自己的赋值操作是否确实就是有意为之的。我这边的一个惨痛教训就是设计了几个可以写的reg,但是在代码里对写地址的处理出现了问题,写地址变量到不了写这几个reg的地址上去,综合器就直接把他们连成了reset时赋的初始值,但是当时没有注意查看check_design里面的内容,直接把这个设计交出去流片了。

check_timing:

  1. Checking generated_clocks

Warning: A non-unate path in clock network for clock 'clk_xxx'
from pin 'u_xxx/xxx/xxx' is detected. (TIM-052)

这个问题是在写分频设计时碰到的,我和这里是一样的情况(巧的要死,发这个帖子是微电子所的师兄,我接的就是他的代码),没有使用级联分频的写法,而使用了基于counter的分频电路。然后在最后加约束的时候没有把generated clock的位置加到分频时钟信号产生的位置上,导致了这个问题。这里的解决方案也分两种:1. 把分频时钟的reg用wire给引出来然后约束到wire上面(帖子里是这个方法)2.分频器的输出连接一个综合library中例化的dff,然后约束到这个dff的Q端,我采用了后面这种方案解决了这个问题。

另外对于跨时钟域设计注意一下时序上的处理,我在这里也吃过亏,快时钟到慢时钟时漏了打两拍处理,不知道回片以后这部分测试会不会出问题,明明fpga测试时是查出过这里的问题的,后来因为综合时在一个module下面调用了两个时钟导致出了一些问题又把这部分改掉了,改的时候漏掉了跨时钟域处理。如果是慢到快倒是直接采样就好,没有那么多问题。

  1. Checking loops

Warning: Disabling timing arc between pins 'A' and 'Z' on cell 'u_xxxx/xxx/xxx'
to break a timing loop. (OPT-314)

这个的成因是写了逻辑环路,这个帖子 底下就有讨论。我碰到的case是在写处理器的regfile时,想要做一个如果在同一拍读写同一个寄存器地址,那么就直接让写端口赋值给读端口的逻辑,结果这个显而易见的引发了逻辑回环问题。解决过程是首先根据report里报告的产生回环的逻辑块,找到相应的逻辑,然后分析一下到底是怎么形成回环的,同时在写代码的时候就得谨慎的处理输入直接给输出或者通过组合逻辑给输出的情况,得要提前判断存不存在一条输出再到这个输入的反馈回路,打破loop的方法除了删除掉回环的部分,也可以在不影响时序的前提下在输入和输出之间插入一级寄存器。

  1. Checking unconstrained_endpoints

Warning: The following end-points are not constrained for maximum delay.

这个大部分情况下都是写了latch,在design compiler的运行log里面就可以看到打印的内容,如果是正常的寄存器:

Inferred memory devices in process
        in routine xxx line xxx in file
                'xxx/xxx.v'.
===============================================================================
|    Register Name    |   Type    | Width | Bus | MB | AR | AS | SR | SS | ST |
===============================================================================
|         xxxx        | Flip-flop |   8   |  Y  | N  | N  | N  | N  | N  | N  |
===============================================================================

而如果是latch:

Inferred memory devices in process
        in routine xxx line xxx in file
                'xxx/xxx.v'.
====================================================================================
|        Register Name         | Type  | Width | Bus | MB | AR | AS | SR | SS | ST |
====================================================================================
|              xxx             | Latch |   8   |  Y  | N  | N  | N  | -  | -  | -  |
====================================================================================

如果是多时钟的情况也可能是generated clock处理的有问题。latch也是上课肯定会提到的事情,但是不去真的做数字前端就不会重视这方面的代码质量问题。我在两个地方都碰到过这个典型问题:

  1. case
  2. if...else...

这两个地方我一开始都采用alway@(*)的组合式写法,针对情况1,按照教科书的说法是case逻辑里面漏掉default或者if...else...里面漏掉else会引发latch,但是实际情况要复杂的多,去写一段很长的译码逻辑时必须要用到很长的case或者if...else...语句,而写得越长检查起来越麻烦,同时工具的表现也越不可控,我两次开发都在这个问题上栽了跟头,针对这种情况,方案1肯定是尽可能的检查latch的成因想办法消除掉,但还有一个简单粗暴的方案2:在不影响时序功能情况下直接改成时序驱动的always@(posedge clk),可以有效消除这一问题,比如在三段式FSM的输出逻辑部分,如果方案2不好使(改成时序驱动以后会引发时序问题)的话,那么还有方案3:所有组合的case/if...else...一定是可以用assign xx = xx ? xx : xx三目运算符的嵌套来等效替换,换成这种写法之后latch肯定就无了。在e203的代码可以看到主要是方案3,这种写法确实电路上也是最可控的,只是会牺牲掉一些可读性。

从工作流的角度来说还有一些可以改进的地方:

  1. 子模块编写后就可以跑DC进行可综合性检查以及check_design,check_timing,不要一直拖到顶层完成才开始做。
  2. 应该引入spyglass来进行代码检查,相比check_design能查出更多的问题,尤其是一些跨时钟域的问题。

另外今天晚上复盘的过程中才意识到自己前面设计里面犯得一些严重问题(不看check_design,导致综合后的电路和设计预期的电路功能不符,跨时钟域处理失误等)。这些都是教训,估计很难预期回片后的测试能够成功了。但是做事就是这样,先有失败才有成功。

此外我计划在下次设计用systemverilog替换verilog,目前来看sv有很多特性非常吸引我,感觉是能够提高开发效率和开发质量的,当然具体如何要在试水之后才知道。

posted @ 2023-05-03 01:11  sasasatori  阅读(921)  评论(2编辑  收藏  举报