最近开始读Cummings大神的一系列文章,然后就单纯做做读书笔记,这次的文章全名是RTL Coding Styles That Yield Simulation and Synthesis Mismatches。网上搜Cummings和文章名应该就能找到,这里就不放链接了。
仿真和综合不匹配通常会以综合前的仿真和综合后的仿真不一致来体现,所以综合后看看仿真结果也是需要的。那什么是不好的RTL代码习惯呢?Cummings总结下来就是,基本上如果RTL代码里面的设计信息可以被HDL仿真器提取,却不能被综合工具理解的,就是不好的;反过来说,如果可以被综合工具理解,仿真器却不支持,那也是不好的。
Cummings提到的情况有这些:
1. 不完整的sensitivity list
使用always 模块来实现组合逻辑电路和锁存器时,如果给出的sensitivity list不完整的话就会导致仿真和综合不匹配了。综合工具可以不依赖于列表,通过分析逻辑方程从而完成综合,但是综合前仿真就不行了。
下面三段代码,code1a的仿真和综合是匹配的。Coed1a由于列表里缺少信号b,其综合前仿真的输出o无法对信号b的变化作出反应。Code1c则因为没有列表而导致综合前仿真陷入死循环。此时可以通过使用always @(*) 来避免。
2. 错误的语句顺序
由于always模块在仿真中会顺序执行里面的赋值语句,所以不同的语句顺序也可能导致仿真和综合不匹配,尤其是当被赋值的信号会在同一个always 模块用于if, case里的判断或是用于赋值其他信号。
下面代码被综合后实现的都是两个2输入的与门和一个2输入的或门。但是对于code2a的综合前仿真来说,由于中间量temp在被新的信号c和d赋值前,就被用于赋值输出o,也就是说输出o 用的是temp上一次更新的值从而导致仿真结果不匹配。而code2b的语句顺序就没有这个问题。
3. Function模块的使用
Functions里的模块总是会被综合成组合逻辑电路,所以当仿真器理解function里描述的是锁存器时,就会造成仿真和综合的不匹配。Code3a里的always模块会被综合成锁存器,而code3b由于使用了function,里面的逻辑会被综合成3输入的与门。所以在使用function来进行逻辑设计时需格外谨慎。
4. Full case directive
在使用case语句时加入//synopsys full_case可能会导致仿真和综合不匹配。这个directive会告诉综合工具这个case语句所需的情况都定义好了,没有定义的情况就会被当作don’t care。
下面的code4a和code4b的综合前仿真结果是相同的,但是code4a没有使用full_case, 整个case语句综合出来的4个3输入的与门和2个取反器。而code4b使用full_case后,由于没定义的case(即en信号为0是情况)直接被当成don’t care,信号en就直接被优化掉了,整个case语句跟en没有关系了,所以综合得到的电路是4个2输入的或非门和2个取反器。
5. Parallel case driective
当case语句加入//synopsys parallel_case时,相似的情况也会出现。这个directive会告诉综合工具并行地描述整个case语句。而一般情况下,如果case中有重叠的情况,优先编码器很可能会被综合出来。
下面的code5a和code5b在综合前仿真会得到相同的结果,但是code5a被综合成一个优先编码器,而由于parallel case的使用,code5b被综合成并行的两个2输入与门。
6. casex 的使用
使用casex可能会造成仿真和综合不匹配。如果一个信号在casex模块里是作为判断条件的,但没有被初始化或者其他原因处于unknown的时候,模拟器会把这个信号当成”don’t care”。但是由于实际电路就不存在unknown,这个unknown信号经过实际电路会解析成信号0或1,这样就有可能错误地匹配到casex里面的语句了,而这种错误在综合前仿真就可能注意不到了。
Code6就描述了一个简单的地址decoder。如果电路中出现错误导致en信号在初始化一段时间内处于unknown的状态,在出现相应的地址时就有可能导致memce0或者memce1被错误地拉高到高电平。
Cummings直接推荐不要在RTL里使用casex语句,有需要的话就使用casez。虽然casez和casex有相似的问题,就是在信号在high-z的时候会被模拟器当成”don’t care”。但是相对于unknown,high-z的情况偏少并且较容易被注意到。但是使用casez时还是要多加注意。
(为了和代码的顺序一致,跳过了7)
8. 把信号赋值成X
除了上述使用casex的情况外,模拟器都是把代码里的X当成unknown的,但是综合工具为了更简单优化的电路会把X当成”don’t care”,这就会导致综合前后的仿真出现不一致了。当然这种不一致有时候是调试的技巧。
下面code8a里的信号y,在用case语句赋值前就先赋值到X。如果这个信号S是不应该跑到2’11的,那如果在综合前仿真中发现信号y有时候出现unknown了就容易捕抓到这个错误(毕竟波形图里unknown是红色的比较显眼)。当然如果无所谓信号S是不是会出现2’11的情况,那这个仿真不匹配就有点恼人了(毕竟红色刺眼:D)。
11. always 模块里的时间延迟
还有就是时间延迟的使用。当构建一些用于仿真的模型时(例如模拟一个ADC来输出信号),可能会用到一些时间的语句。如code11, 当信号in发生翻转时,延时25个单位时间后信号in取反然后赋值给信号out1,再延时40个单位时间后,给信号out2赋值。问题在于,如果进入always模块后65个单位时间内,信号in出现翻转,就不会再触发一次进入always模块的情况。这意味着如果信号in的变换频率高于65个单位时间,信号out1和out2的更新速度是跟不上的。仿真和综合不匹配的情况就可能会出现了。
文章还提到了translate_on和translate_off这些综合directives的使用也会造成仿真和综合的不匹配。暂时没有什么体验就不多说了,但是据文章的描述,使用这对directives可以强制输出正确的信号水平来弥补一些模拟器模拟不充分的情况。有兴趣的朋友可以多搜搜。
除了文章提到的,为了避免综合前后仿真不一致的情况,还需要注意metastability的设计,例如异步的信号有没有被正确地进行同步和跨时钟领域处理。综合后时序也需要检查,看看有没有违反时序的情况,时钟频率和各种时序约束是否正确。还有testbench中没有使用blocking语句等等。这些都有可能是造成仿真和综合不匹配的原因。