发表于 2025-4-2 11:54:00

【芯片设计】为什么RTL中使用for循环要多加谨慎

编写RTL一晃十来年了,遇到了很多形形色色工程编码规范、约束和建议,故而在此阶段性汇总。本系列中所提及和讨论的verilog/sv编码建议以及一些工程的要求只是工作过程所接触和了解内容,非普适性的编码规范,也并不代表个人赞同这些规范,一家之言仅和大家探讨这些建议原因和得失。
本系列的前七篇请参见:为什么要求不在RTL中使用always为什么避免在RTL中滥用宏定义为什么避免在RTL中使用task为什么在设计中优先使用无复位寄存器为什么RTL中避免使用#delay引入延迟
为什么自研代码的filelist避免-y和-v索引为什么RTL中避免使用casez/casexVerilog语法中有两种常见的for循环结构:普通for和generate-for,SystemVerilog里对for语法也有所拓展。在前司时,generate-for被禁止使用,坦率的讲这个是到目前为止我最不理解且不认同的编码约束,私下里总是觉得颇为投鼠忌器因噎废食。私下里也去问过为什么要加这条规则,得到的答复基本有以下这几个方面的考虑:1.隐式循环生成逻辑对后端的影响。发现这条理由就是个框啊,啥约束都往里面扔。简单说就是,generate-for语法生成的逻辑在代码中是隐式展开的,可能使得定位和修改目标逻辑变得更加困难。对时序路径的确定和优化,以及ECO时寻找特定的单元门造成一定困扰。我觉得这点只要label name显示的写清楚,就应当不构成困扰(当然我没做过ECO所以没有发言权),即使有问题也应该是EDA工具亟待解决的。
2.工具支持的差异。这个也很框,好几条编码规则都用这个理由搪塞过。就是担心某些EDA工具对generate语法的支持可能不完全,尤其是在旧版本或特定工具链中,可能导致综合或仿真错误。但是目前为止还没遇到过这个问题,所以存疑。在原文评论区有朋友补充了一个VHDL的例子,补充在这里:“编译器这个还真有可能,之前用中科亿*微的*linx,就不能正确的识别vhdl中的generate-for,最后不得不把代码复制好几遍。”
3.增加验证和覆盖率复杂性。包括仿真性能下降、仿真定位难度增大、覆盖率收集更复杂(尤其循环+条件的例化情况)、前后仿对齐和定位复杂性上升等,这一点个人觉得倒是觉得能有些道理。
4.ECO难度加大。这个也提了很多次,导致我一直有ECO经验缺失焦虑。某些ECO工具(但具体是哪些呢)可能对generate语法的支持有限,尤其是在处理复杂的条件生成或嵌套生成时。同时在ECO阶段,综合和布局布线工具需要重新处理修改后的逻辑。如果generate语法生成的逻辑在综合和布局布线阶段被处理得不一致,可能会导致ECO失败或引入新的问题。
聊到的主要理由就是这4条,可即使大家遵循了这个规则,仍然有很多时候generate-for是不可替代的,比如说参数化的例化若干模块或逻辑。所以由此又衍生出了其他的问题和工具,我自己课下不是也用python写了一个auto_unfold脚本嘛,展开for循环不见得多好用,但是生成海量的循环文本倒是挺好用。
因为我自己不太认同这个编码建议也不怎么遵循,所以就不说更多了,转头来说说普通的for循环。关于for循环(非genreate-for)的一些小坑在很久之前还真的认真做过笔记,在这篇文章里复现一下当时说慎用的几段代码行为,相当于重写一下文章。本文内的验证环境由auto_testbench生成,编译器为VCS(之前那篇用的modelsim)。
总的来说,for循环的雷区主要有两个:非阻塞赋值和else分支,一旦for循环和这两种情况耦合在一起,那么就需要尤其的打起精神了。直接看几段反面典型/错误示范,代码的来源是以前找大佬问问题时做的笔记,在实际项目里是不会有人写出这样逆天的代码。所以代码原本的目的是啥也不做太深探究,只关注下仿真行为就好。
第一段代码,这么写代码本身是有些愚蠢的但是为了展现语法特性,将就着看看:
[*][*][*][*][*][*][*][*]logic sign;logic [$clog2(DATA_WD) -1:0]cnt;always @(posedge clk)begininteger i;for(i=0; iDATA_WD; i=i+1)begin    cnt   endend含义就是计算一个sign里有多少bit是为1的,这里错误的使用了非阻塞赋值。于是代码行为就变成了这样:

换句话说,这段代码等价于:
[*][*][*][*][*][*]always @(posedge clk)begincnt 0];cnt 1];...cnt PORT_NUM-1];end因为是非阻塞赋值前后代码并不会有先后关系,最后执行出什么结果很大程度上看编译器心情(这里我不确定了,之前看协议timeslot机制这里执行顺序写的是“E = any event from region”,所以个人一直理解的没有确定顺序),VCS应该执行的是cnt
第二段代码,这段代码的本意是避免出现latch,之前大家不是总说always@*里的if要有else嘛,好那就写上else,当然还是要强调下这个代码没必要这样写很傻,只是单纯展示仿真行为:
[*][*][*][*][*][*][*][*][*]logic sign;logic flag;always @* begininteger m,n;    for(n=0; nDATA_WD; n=n+1)begin      if(sign) flag = 1;      else      flag = 0;endend这个写法甚至不需要仿真波形,必然是只检查了sign的最后一个bit。用这段代码来引出第三段代码。这段代码的本意是获取高优先级端口的数据,优先级in_vld最高,in_vld最低。于是在for循环里由低到高看in_vld的值,如果为1则data0取过来;如果为0则data0保持自身不变,于是在时序单元里直接写了:
[*][*][*][*][*][*][*][*][*][*]logicdata0, data1;logicin_data;logicin_vld;always @(posedge clk)begininteger i;for(i=0; i1)begin    if(in_vld) data0   else          data0   endend那么这个代码等价于啥呢?他等价于这样:
[*][*][*][*]always @(posedge clk)begin    if(in_vld) data DATA_WD*(PORT_NUM-1) +:DATA_WD];    else                   data data;end没错,他只会检查in_vld最高bit的情况而放弃了其他比特,看波形:

只有in_vld为高时data0才会跳变,in_vld完全被忽略了。所以这种需求最好是换到阻塞赋值的for循环里去写,如果非要保持非阻塞那么就把else去掉:
[*][*][*][*][*][*]always @(posedge clk)begininteger i;for(i=0; iPORT_NUM; i=i+1)begin    if(in_vld)          data1 DATA_WD*i +:DATA_WD];endend那么行为就符合预期了:


系列文章入口
【芯片设计】SoC 101(一):绪论【芯片设计】FIFO漫谈(零)从无处不在的FIFO开始说起【芯片设计】计算机体系结构(一)虚拟内存【芯片设计】深入理解AMBA总线(零)绪论
【芯片设计】握手协议的介绍与时序说明【芯片设计】复位那些小事 —— 复位消抖【芯片设计】快速入门数字芯片设计(一)Introduction【芯片验证】UVM源码计划(零)下定决心读源码前的自测环节
【芯片设计】异步电路碎碎念(一) 到底什么是异步电路
【芯片设计】从RTL到GDS(一):Introduction
【芯片设计】系统中的可维可测状态记录寄存器设计
其他文章链接
【芯片验证】sva_assertion: 15道助力飞升的断言练习【芯片验证】可能是RTL定向验证的巅峰之作【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析【芯片设计】verilog中有符号数和无符号数的本质探究
【芯片设计】论RTL中always语法的消失术【芯片设计】代码即注释,注释即代码【芯片设计】700行代码的risc处理器你确实不能要求太多了入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢如何计算系统的outstanding 和 burst length?芯片搬砖日常·逼死强迫症的关键词不对齐事件熟人社会里,一群没有社会价值的局外人
页: [1]
查看完整版本: 【芯片设计】为什么RTL中使用for循环要多加谨慎