RTL编码风格所造成的仿真和综合的不匹配
1.0 简介
ASIC或者FPGA设计就是把一个想法或者概念转换成物理实现的过程。这篇文章讨论了HDL编码风格所造成的RTLGate-level仿真的不一致的几种情况。
它的一个基本的判定规则是,符合以下两种情况的编码风格是坏的编码风格。
· 提供给HDL仿真器的关于设计的信息不能传送给综合工具
· 综合开关提供给综合工具的信息在仿真器中不可得
如果上犯了上两条禁忌,就会造成综合前的RTL仿真和综合后的门级仿真不匹配的问题。这些问题很难发现,因为由于门的数量的增多,完备测试是不可能的,而且如果不注意会最终导致ASIC生产的失败。
解决方法就是了解什么样的编码风格或者综合选项会导致RTL到门级的仿真不一致,并避免这些问题。
2.0 敏感列表
当一个always块中并不包含Verilog的关键字posedge或者negedge的时候综合工具会把它综合成组合或者锁存器逻辑。对于一个组合逻辑always块,逻辑直接从块中的等式中推导出,*而与敏感列表没有关系*。综合工具读取敏感列表,并把它与always块中的等式相比较,报告出可能造成pre-和post-synthesis仿真不一致的疏漏的敏感列表。
若一个信号在敏感列表中出现而没有在always块用到,它不会对pre-和post-synthesis造成任何功能上的不同。多余的敏感信号的唯一的后果就是使仿真变慢了。
2.1 不完全敏感信号列表
综合工具总是把always块中的等式按完整敏感信号来综合。但是pre-synthesis的功能仿真就会非常不同。
· module code1a (o, a, b);
· output o;
· input a, b;
· reg o;
· always @(a or b)
· o = a & b;
· endmodule
在code1a中敏感列表时完整的,pre-synthesis和post-synthesis都会按一个2输入and门来仿真
· module code1b (o, a, b);
· output o;
· input a, b;
· reg o;
· always @(a)
· o = a & b;
· endmodule
在code1b中敏感列表不完整,post-synthesis仍然是个2-input and门,而在pre-synthesis的仿真中只有在a有变化的时候always块才会执行。
· module code1c (o, a, b);
· output o;
· input a, b;
· reg o;
· always
· o = a & b;
· endmodule
code1c不包括任何敏感列表,在pre-synthesis的时候仿真器在在always块中陷入无限循环,而post-synthesis仍然是个2-input and门
2.2 不同赋值顺序的完整敏感列表
-在pre-synthesis时always块中的赋值语句会按顺序执行。这样,在always块中有局部临时变量时就会产生问题,临时变量可能在if语句的判断条件部分,case语句,或者赋值语句的右端中用到。*如果一个临时变量在被赋值之前用到,就会导致赋值的乱序*。直到temp变量的赋值语句被执行,temp将保持之前传送给它的值。
· module code2a (o, a, b, c, d);
· output o;
· input a, b, c, d;
· reg o, temp;
· always @(a or b or c or d) begin
· o = a & b | temp;
· temp = c & d;
· end
· endmodule
-在code2a中,temp在被赋值之前被用到,temp将保持之前的值。之后temp被赋新值。在pre-synthesis仿真中,temp将像一个锁存器。而在综合时,它会按正确的顺序赋值。此会造成不一致。
· module code2b (o, a, b, c, d);
· output o;
· input a, b, c, d;
· reg o, temp;
· always @(a or b or c or d) begin
temp = c & d;
o = a & b | temp;
· end
· endmodule
-code2b中pre-和post-synthesis仿真结果是一致的。
3.0 function
-function总是被综合成组合电路,由于这个原因,一些工程师把所有的组合逻辑都用function来实现。如果function按照组合逻辑来仿真使用function是没有什么问题的。当工程师在function中的组合逻辑中犯了错误而使它仿真时像一个锁存器,就会产生错误。当function代码仿真寄存器的行为的时候综合工具是没有警告信息的。用function建模组合逻辑是危险的。
· module code3a (o, a, nrst, en);
· output o;
· input a, nrst, en;
· reg o;
· always @(a or nrst or en)
· if (!nrst) o = 1'b0;
· else if (en) o = a;
· endmodule
-code3a是设计锁存器的典型方法。
· module code3b (o, a, nrst, en);
· output o;
· input a, nrst, en;
· reg o;
· always @(a or nrst or en)
· o = latch(a, nrst, en);
· function latch;
· input a, nrst, en;
· if (!nrst) latch = 1'b0;
· else if (en) latch = a;
· endfunction
· endmodule
-code3b中把相同的代码用在了一个if语句中,产生了3-input and门。
-如果在function中的代码是用来生成latch的,pre-synthesis仿真将仿真成latch的行为,为post-synthesis仿真将会仿真成组合逻辑。从而造成不匹配。
4.0 case语句
4.1 Full Case
*用编译指令//synopsys full_case 给编译工具更多的关于设计的信息,而不能把这些信息提供给仿真工具*。这条特别指令是提醒编译工具,case语句是fully defined的,输出对于没用用到的case是"don't care"的。当用此条指令时,pre-和post-synthesis的仿真可能会不同。此外,尽管此条指令是告诉编译工具把未用的的情况视为“不处理”,这条指令有时会使设计比忽略full_case时更大和更慢
· module code4a (y, a, en);
· output [3:0] y;
· input [1:0] a;
· input en;
· reg [3:0] y;
· always @(a or en) begin
· y = 4'h0;
· case ({en,a})
· 3'b1_00: y[a] = 1'b1;
· 3'b1_01: y[a] = 1'b1;
· 3'b1_10: y[a] = 1'b1;
· 3'b1_11: y[a] = 1'b1;
· endcase
· end
· endmodule
在code4a中case没有综合指令,结果是三输入与门和一个反相器。pre-和post-synthesis仿真是一致的。
· module code4b (y, a, en);
· output [3:0] y;
· input [1:0] a;
· input en;
· reg [3:0] y;
· always @(a or en) begin
· y = 4'h0;
· case ({en,a}) // synopsys full_case
3'b1_00: y[a] = 1'b1;
· 3'b1_01: y[a] = 1'b1;
· 3'b1_10: y[a] = 1'b1;
· 3'b1_11: y[a] = 1'b1;
· endcase
· end
· endmodule
在code4b中case语句带综合指令full_case,正是因为此,在综合时en输入被优化掉而成为一个悬挂的输入。code4a和code4b综合前的仿真结果以及code4a综合后的结果都是一致的,而它们是与code4b综合后的结果不同的。
4.2 Parael Case
*用编译指令//synopsys parallel_case 给编译工具更多的关于设计的信息,而不能把这些信息提供给仿真工具*。这条编译指令是告诉编译器所有的情况是并行执行的,尽管有重叠的情况(这种情况通常会形成一种优先编码器)。当设计中有重叠的case时,pre-和post-synthesis 的仿真结果将会不同。在某些情况,用这条指令会使设计变得更大和更慢。
添加parallel_case的一个目的是优化面积和速度。*优先编码器行为的RTL模型通过了RTL级测试,但是如果在门级测试时忽略了这个漏洞(它其实是没有优先级的并行逻辑),导致的结果就是,设计是错误的,但是bug直到ASIC原型交货前也没有发现,那么在高度的金钱和时间成本下,ASIC必须重新设计。*
· module code5a (y, z, a, b, c, d);
· output y, z;
· input a, b, c, d;
· reg y, z;
· always @(a or b or c or d) begin
· {y, z} = 2'b0;
· casez ({a, b, c, d})
· 4'b11??: z = 1;
· 4'b??11: y = 1;
· endcase
· end
· endmodule
code5a会被被综合成成具有优先编码器的功能的逻辑。但是code5b却被综合成两个and门。
· module code5b (y, z, a, b, c, d);
· output y, z;
· input a, b, c, d;
· reg y, z;
· always @(a or b or c or d) begin
· {y, z} = 2'b0;
· casez ({a, b, c, d}) // synopsys parallel_case
· 4'b11??: z = 1;
· 4'b??11: y = 1;
· endcase
· end
· endmodule
综合工具指令//synopsys parallel_case的使用将导致具有优先编码器功能的逻辑实现成并行逻辑,从而导致综合前后的不匹配。
4.3 Casex
casex语句的使用也会导致设计问题。casex把'X'视为'don't care'(如果它们出现在case表达式和case条目里)。casex所导致的问题会在casex表达式的输入被初始化成不定值时出现。在处理casex语句时,综合前仿真会把不定值视为'don't care'。综合后的仿真把'X'在门级的模型中传递。
有个公司在他们的设计中使用了casex语句。casex语句的输入在复位后成为不定值,因为综合前RTL仿真把未知的输入视为'don't care',casex语句错误地初始化设计在工作状态,而门级仿真并不够复杂或者说详细以致于没有发现这个错误,从而使递交的ASIC带着严重的bug。
· module code6 (memce0, memce1, cs, en, addr);
· output memce0, memce1, cs;
· input en;
· input [31:30] addr;
· reg memce0, memce1, cs;
· always @(addr or en) begin
· {memce0, memce1, cs} = 3'b0;
· casex ({addr, en})
· 3'b101: memce0 = 1'b1;
· 3'b111: memce1 = 1'b1;
· 3'b0?1: cs = 1'b1;
· endcase
· end
· endmodule
code6是一个带着enable信号的简单的地址解码器,有时外部接口的设计错误会导致enable信号初始化后在被设置成一个有效状态之前成为不定值。当enable处于未知状态时,case选择器基于addr信号错误地匹配case选项。这个初始化的问题只有在post-synthesis仿真时出出现。一个类似的情况就是,当en有效,地址线的MSB位成为不定值时。这将会导致memce0或者memce1有效,而不管片选cs信号是否有效。
*使用原则*:出现不定值信号时很容易的,最好用casez语句而不用casex语句。
4.4 casez
casez语句会导致和casex相同的设计问题,只不过它在验证时更容易被发现。用casez当输入被初始化成高阻态时就会出现问题。但是casez是设计很多结构的一种简洁和有效的方式,如优先编码器,中断处理器,和地址解码器。因此当设计工程师再用HDL设计某些结构时应该考虑用casez语句。
· module code7 (memce0, memce1, cs, en, addr);
· output memce0, memce1, cs;
· input en;
· input [31:30] addr;
· reg memce0, memce1, cs;
· always @(addr or en) begin
· {memce0, memce1, cs} = 3'b0;
· casez ({addr, en})
· 3'b101: memce0 = 1'b1;
· 3'b111: memce1 = 1'b1;
· 3'b0?1: cs = 1'b1;
· endcase
· end
· endmodule
code7是与code6相同的带enable信号的简单地址解码器,只不过它用casez替换了casex。当某个输入成为高阻态而不是有效值时,它会与code6产生相同的问题。同样,错误的case匹配会发生(取决于case语句输入的状态)。然而,casez语句的匹配(浮置输入(floating input)或者三态驱动信号)比cazex语句匹配(信号成为不定值)可能性要小得多。*但是casez语句在设计地址解码器和优先编码器的时候是很有用的*
*使用原则*:在RTL级谨慎使用casez语句。
5.0 初始化
5.1 'X'初始化
当在RTL代码中复制时,有时会被赋给'X'值。'X'被Verilog仿真器解释为不定值(casex例外),但是会被综合工具当做'don't care'。赋值成'X'会导致综合前后仿真的不一致。但是赋值成'X'值也是一种有用的技巧。在FSM设计中,存在未用到的状态时,把状态变量赋值成'X'可以帮助调试状态转换。这是在进入case状态之前把状态寄存器赋值成'X'值(它是为所有的不正确的状态而准备)。切记,综合工具将把任何的'X'值解释为'don't care'。
· module code8a (y, a, b, c, s);
· output y;
· input a, b, c;
· input [1:0] s;
· reg y;
· always @(a or b or c or s) begin
· y = 1'bx;
· case (s)
· 2'b00: y = a;
· 2'b01: y = b;
· 2'b10: y = c;
· endcase
· end
· endmodule
· module code8b (y, a, b, c, s);
· output y;
· input a, b, c;
· input [1:0] s;
· reg y;
· always @(a or b or c or s)
· case (s)
· 2'b00: y = a;
· 2'b01: y = b;
· 2'b10, 2'b11: y = c;
· endcase
· endmodule
code8a和code8b是3选1选择器的简单的Verilog模型。如果一旦case的2'b11情况发生,code8a的编码风格会造成仿真的不一致。而在code8b中则不会发生这种情况。而这种不匹配在2'b11情况永远不会发生时是有用的,因为一旦2'b11情况发生,y的仿真输出就会是不定值。
5.2 用translate_off/translate_on初始化设计
在translate_off和translate_on,对于综合工具是不可见的,而仿真工具可以。这样就会造成综合前后仿真的不一致,有可能在综合前仿真中初始化是正确的,但是在初始化之后,结果就不正确了。
· module code9 (y1, go, clk, nrst);
· output y1;
· input go, clk, nrst;
· reg y1;
· parameter IDLE = 1'd0,
BUSY = 1'd1;
· reg [0:0] state, next;
· // Hiding the initialization of variables from the
· // synthesis tool is a very dangerous practice!!
· // synopsys translate_off
· initial y1 = 1'b1;
· // synopsys translate_on
· always @(posedge clk or negedge nrst)
· if (!nrst) state <= IDLE;
· else state <= next;
· always @(state or go) begin
· next = 1'bx;
· y1 = 1'b0;
· case (state)
· IDLE: if (go) next = BUSY;
· BUSY: begin
if (!go) next = IDLE;
y1 = 1'b1;
end
· endcase
· end
· endmodule
translate_off/translate_on的初始化部分,会导致综合前后仿真结果的不一致。
6.0 translate_off/translate_on的一般用法
translate_off/translate_on综合指令应该谨慎使用。它们在显示设计的信息时是非常有用的,但是当它被用来模拟某种功能时是很危险的。一个例外是异步复位和置位的D触发器。他要求用非综合的模型提供正确的综合前仿真模型以和综合后模型匹配。这种条件按以下方式创建:置reset有效,置set有效,置reset无效,使set继续有效。在这种情况,D触发器模型需要一点辅助来在综合前阶段正确建模set条件。这是由于在always块里只有在set/reset的边沿时才会触发。当输入是异步的,一旦reset信号被去除,set就会有效,但是这种情况不会发生,因为always块不能被触发。为了解决这个问题,可以用translate_off/translate_on指令强制输出正确的值。*最好的方法就是尽可能避免异步set/reset触发器的使用。
· // Generally good DFF with asynchronous set and reset
· module code10a (q, d, clk, rstn, setn);
· output q;
· input d, clk, rstn, setn;
· reg q;
· always @(posedge clk or negedge rstn or negedge setn)
· if (!rstn) q <= 0; // asynchronous reset
· else if (!setn) q <= 1; // asynchronous set
· else q <= d;
· endmodule
· // synopsys translate_off
· // Bad DFF with asynchronous set and reset. This design
· // will not compile from Synopsys, and the design will
· // not simulate correctly.
· module code10b (q, d, clk, rstn, setn);
· output q;
· input d, clk, rstn, setn;
· reg q;
· always @(posedge clk or rstn or setn)
· if (!rstn) q <= 0; // asynchronous reset
· else if (!setn) q <= 1; // asynchronous set
· else q <= d;
· endmodule
· // synopsys translate_on
· // Good DFF with asynchronous set and reset and self-
· // correcting
· // set-reset assignment
· module code10c (q, d, clk, rstn, setn);
· output q;
· input d, clk, rstn, setn;
· reg q;
· always @(posedge clk or negedge rstn or negedge setn)
· if (!rstn) q <= 0; // asynchronous reset
· else if (!setn) q <= 1; // asynchronous set
· else q <= d;
· // synopsys translate_off
· always @(rstn or setn)
· if (rstn && !setn) force q = 1;
· else release q;
· // synopsys translate_on
· endmodule
code10a在仿真时%99的情况是正确的,但是当rstn的上升沿来到时,如果setn一直是低电平,always块是不被触发的,这导致了仿真的不正确。而在code10b中不管是综合前后的仿真,还是综合本身,都会是错误的。而code10c会100%仿真正确,综合前后的仿真也会匹配,它使用translate_off/translate_on综合指令,强制使输出输出正确的值。(这确实是很有意思)。
7.0 延时
在always块中,如果不在0时间点上有延时,就会错过RTL或者行为级的触发事件。
· module code11 (out1, out2, in);
· output out1, out2;
· input in;
· reg out1, out2;
· always @(in) begin
· #25 out1 = ~in;
· #40 out2 = ~in;
· end
· endmodule
在code11中,在赋值语句的左端添加了延时,将导致综合前后仿真结果的不一致。首先,一旦敏感列表中的in变量变化时,就会进入always块,之后的65个时间单元内in的变化将不在重新出发always块。第二,25个时间单元的延迟后,in的值将被读取,取反,传送给out1,再过40个时间单元后,in的值将再一次被读取,取反,传送给out2。在延迟的过程中,所有的发生在in上的事件都将被忽略。如果任何输入的变化频率比每65个时间单元快,在code11的输出端不会有更新。综合之后的门级模型将仿真成两个反相器,而综合前的RTL代码将错失很多输入的变化。*在alway块中的赋值语句的左端加入延时,并不能精确进行RTL和行为级的建模*。
8.0 总结
知道那一种编码风格会导致综合前后的仿真结果不一致,理解为什么不一致会发生,并且避免容易出错的编码风格将大大减少RTL设计中的漏洞和修补这些不匹配所花的调试时间。由于ASIC的尺寸正在持续增加,综合后运行100%覆盖率的回归测试越来越不可能。设计者必须能够在设计过程中的用所有可能的方法尽早减少风险。
在项目进行过程中,根据自己的经验,总结各大公司的相关文档,创建了自己的编码守则,内容不多,也不教条,适合自己用,大家也可以看看。先来说说这几项守则的目标,任何东西都是在一定的目标和评价体系下,才有其真实的意义。 |
文件风格
1. 一个文件内只能有一个模块 |
端口风格
1. 端口信号用大写 |
时钟风格
1. 时钟名称采用频率作为后缀 |
复位风格
1. 不要在模块内创建门控复位并使用之。 |
例化风格
1. 例化子模块时,端口映射一定要采用显式名称映射方式,比如 |
逻辑风格
1. 输入信号都要FF锁存后使用 |
存储器风格
1. 时钟用上升沿 |
FPGA风格
1. FPGA使用的内容要单独规划,方便ASIC的移植。这个方法很多,大家选自己擅长的 |
文章评论(0条评论)
登录后参与讨论