编写RTL一晃十来年了,遇到了很多形形色色工程编码规范、约束和建议,故而在此阶段性汇总。本系列中所提及和讨论的verilog/sv编码建议以及一些工程的要求只是工作过程所接触和了解内容,非普适性的编码规范,也并不代表个人赞同这些规范,一家之言仅和大家探讨这些建议原因和得失。
本系列的前六篇请参见:为什么要求不在RTL中使用always为什么避免在RTL中滥用宏定义为什么避免在RTL中使用task为什么在设计中优先使用无复位寄存器为什么RTL中避免使用#delay引入延迟什么自研代码的filelist避免-y和-v索引[/ol]case三兄弟中避免使用casez/casex是很多地方的编码规范,当然只针对以Verilog编写的RTL。也有一些公司是允许使用casez但是不允许使用casex,鉴于两者带来的问题比较接近,在本篇中放在一起聊一聊。case/casez/casex的区别,简单一句话就是:case将0/1/x/z时为4中不同状态进行比对,casez将z当做don't care,casex将x/z均视为don't care进行比对。关于case statement更多的语法细节,请参考IEEE标准的12.5章节。

那么为什么在RTL中避免使用casez/casex呢?从综合实现的角度来看,casez/casex属于“不完全可综合”的语法范畴(借鉴之前笔记中的记录),关于“可以综合”、“不完全可综合”、“不可以综合”的语法划分网上能找到很多归纳(但是不确定是否有官方文档资料的归纳),比如:
可以综合或所有综合工具都支持的语法有: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不完全可综合或部分综合工具支持的语句有:casex、casez、wand、triand、wor、trior、real、disable、forever、arrays、memories、repeat、task、while对于可以综合的语句在前司甚至都有很大一部分在编码规范里禁止使用的,更何况不完全可综合的casex和casez了。当然除了综合工具不一定支持外,也有人说在硬件实现中因为会把x/z状态“翻译”不确定状态,可能会掩盖亚稳态的问题,对于这一点因为没有确认过所以存疑吧。还有一个隐患,不同综合工具对casex/casez的处理可能有所不同,生成不同的电路结构,从而影响功能正确性和时序性能。对于这一点我觉得是case这个语法在verilog中的构造本身有问题,在后面sv标准中对这个事情进行了相当的弥补。
case_keyword(case_expression): case_item: statement_or_null; default: statement_or_nullendcase前面都说的比较空,我认为casex/casez最大的隐患在于仿真行为、预期行为和综合后的实际行为不一致。在case里case_item里大家一般都是严禁使用?的(事实上x/z/?我都没有见过有人在case里用),而casez/casex则不然,几乎都是伴随?一起使用的。也有地方的要求是:case的case_item里不能有x/z/?,casez的case_item里不能有x/z可以有?,casex禁用。因为这个?的引入,造成了case_expression和case_item在don't care维度上的双向奔赴。两个都don't care就坏事了,直接看下面这个让我印象深刻多年没有忘怀的例子:
//-------------------------------------{{{other sig assignlogic [1:0]o_sig;logic [2:0]i_sig;always @* begin casez (i_sig) 3'b1?? : o_sig = 2'b01; 3'b?1? : o_sig = 2'b10; 3'b??1 : o_sig = 2'b11; default: o_sig = 2'b00; endcaseendinitial begin i_sig = 3'b000; `DELAY(100, clk); i_sig = 3'b00z;end在100个clk cycle之后,i_sig的值编程了3'b00z,那么o_sig的值会如何变化呢?没错会跳变为2'b11:
这说明什么呢?说明casez(3'b00z)命中了3'b??1 : o_sig = 2'b11,呈现了verilog的casez/casex在don't care上的双向奔赴:

case_expression care的位置case_item don't care,case_item care的位置case_expression don't care,好,一来一回3'b00z和3'b??1就匹配上了。咱们都不论仿真结果和综合后的电路行为能否对的上,这仿真结果甚至和设计预期都对不上,3'b??1这个分支在设计时一定要的是“不管前面两bit什么值,最后这个bit必须是1”,而仿真时一个3'b00z就把casez破功了。而如果以case来写,至少不会出现这个问题:

因为有这个问题的存在,我觉得其他问题比如验证复杂度增加、覆盖率工具可能无法捕捉到所有的路径、可读性下降、优先级风险这些都没有那么要命了。
应该是意识到了case语法的诸多问题,后面sv标准了对其做了很多修补(case - inside结构主要是针对上面的双向奔赴问题),这些由模型生成一下大家随意看看就好,因为我平时不用sv for design所以只是知道,没有关注太多。
根据原文评论区的反馈,补充一点关于casez和casex的可综合性,因为实际项目里没写过所以只在本地简单做了下实验,dc里是可以上面的写法都是可以综合的:
input clk;input rst_n;input [2:0]i_sig;output reg[1:0]o_sig;reg [1:0]o_sig_d;always @* begin casex(i_sig) 3'b1?? : o_sig_d = 2'b01; 3'b?1? : o_sig_d = 2'b10; 3'b??1 : o_sig_d = 2'b11; default: o_sig_d = 2'b00; endcaseendalways @(posedge clk or negedge rst_n) begin if(!rst_n) o_sig else o_sig o_sig_d;end综合的report如下:Timing Path Group 'clk' ----------------------------------- Levels of Logic: 2.00 Critical Path Length: 0.46 Critical Path Slack: 0.54 Critical Path Clk Period: 1.00 Total Negative Slack: 0.00 No. of Violating Paths: 0.00 Worst Hold Violation: 0.00 Total Hold Violation: 0.00 No. of Hold Violations: 0.00 ----------------------------------- Cell Count ----------------------------------- Hierarchical Cell Count: 0 Hierarchical Port Count: 0 Leaf Cell Count: 9 Buf/Inv Cell Count: 5 Buf Cell Count: 0 Inv Cell Count: 5 CT Buf/Inv Cell Count: 0 Combinational Cell Count: 7 Sequential Cell Count: 2 Macro Count: 0 ----------------------------------- Area ----------------------------------- Combinational Area: 44.132402 Noncombinational Area: 71.290802 Buf/Inv Area: 27.158401 Total Buffer Area: 0.00 Total Inverter Area: 27.16 Macro/Black Box Area: 0.000000 Net Area: 0.000000 ----------------------------------- Cell Area: 115.423204 Design Area: 115.423204关于SV对case语法的补充和打补丁,请见ds生成的参考。SystemVerilog(SV)对 case 语句进行了多项语法拓展,使其更加强大和灵活,适用于复杂的逻辑设计。这些拓展不仅增强了功能,还提高了代码的可读性和维护性。以下是SystemVerilog中对 case 语句的主要语法拓展:1. casez 和 casex 的替代方案虽然 casez 和 casex 仍然存在,但推荐使用更明确的方式来处理不确定或高阻抗状态。例如,可以使用掩码或位操作来实现相同的效果,同时保持更高的可预测性和验证覆盖率。
2. unique 和 priority 关键字unique目的:确保每个分支都是唯一的,即没有多个条件同时为真。作用:如果多个条件同时为真,编译器会报错,迫使设计者处理这种情况。示例:always @(*)begin
uniquecase(state)
IDLE: next_state=START;
START:next_state=RUN;
RUN: next_state=STOP;
default:next_state=IDLE;
endcase
end
priority目的:指定条件的优先级顺序,当多个条件同时为真时,选择优先级最高的那个。作用:即使多个条件同时为真,也只会执行优先级最高的分支。示例:always @(*)begin
prioritycase(state)
IDLE: next_state=START;
START:next_state=RUN;
RUN: next_state=STOP;
default:next_state=IDLE;
endcase
end
3. case 内的表达式多值匹配目的:允许在 case 语句中使用复杂的表达式,而不仅仅是简单的常量。作用:使得 case 语句更加灵活,能够处理更复杂的情况。示例:always @(*)begin
case({a,b})// 匹配多个信号的组合
2'b00:result=0;
2'b01:result=1;
2'b10:result=2;
2'b11:result=3;
default:result=4;
endcase
end
范围匹配目的:允许在一个范围内进行匹配,简化了连续值的处理。作用:减少重复代码,提高可读性。示例:always @(*)begin
case(value)
0:result="Zero";
[1:9]:result="Single digit";
[10:99]:result="Double digit";
default:result="Other";
endcase
end
4. inside 关键字集合匹配目的:用于检查一个值是否属于某个集合。作用:简化了对多个离散值的匹配。示例:always @(*) begin
case (value)
inside {1, 3, 5, 7, 9}: result = "Odd number";
inside {0, 2, 4, 6, 8}: result = "Even number";
default: result = "Other";
endcase
end5. 默认分支 default强制要求目的:确保所有未处理的情况都有一个默认的行为。作用:防止遗漏重要情况,提高代码的健壮性。示例:always @(*)begin
case(state)
IDLE: next_state=START;
START:next_state=RUN;
RUN: next_state=STOP;
default:next_state=ERROR;// 确保所有未定义的状态都有处理
endcase
end
6. if-else 结构的替代嵌套 case目的:用 case 语句替代复杂的 if-else 结构,使代码更加简洁和易读。作用:减少了嵌套层次,提高了代码的可读性和维护性。示例:always @(*) begin
case (condition1)
1: case (condition2)
1: result = "Both true";
default: result = "Only condition1 true";
endcase
default: result = "Condition1 false";
endcase
end7. 模式匹配正则表达式风格的匹配目的:支持更复杂的模式匹配,类似于正则表达式的功能。作用:提高了匹配的灵活性,适用于需要复杂模式识别的场景。示例:typedef bit[3:0]nibble_t;
always@(*)begin
case(data[3:0])
nibble_t'(4'b????):result="Any value";// 使用通配符
nibble_t'(4'b1??0):result="Pattern match";// 匹配特定模式
default:result="Other";
endcase
end
总结通过这些拓展,SystemVerilog中的 case 语句变得更加灵活和强大,能够处理更复杂的逻辑需求。合理使用这些特性可以帮助设计者编写更加清晰、可靠和高效的RTL代码。此外,这些增强的功能也有助于简化验证过程,确保设计满足预期的功能和性能要求。

系列文章入口
【芯片设计】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? | 芯片搬砖日常·逼死强迫症的关键词不对齐事件 | 熟人社会里,一群没有社会价值的局外人 |
|