if…else 与 case 语句分析
两者结构完全一致的情况 两段代码,EX1使用if……else语句,EX2使用case语句,它们综合的结果有多大差异呢?最终布局布线后的结构又有多大差异呢?二者代码如下:
input clk; input rst_n; // 修正了rst_n的命名 input [3 :0] data; output [2:0] add; reg [2 :0] add; // 修正了方括号的字符 always @(posedge clk) begin if (!rst_n) begin add <= 0; end else begin if (data < 4) add <= 1; else if (data < 8) add <= 2; else if (data < 12) add <= 3; else add <= 4; end end
我们可以分析一个等价的case语句版本的代码(EX2):
input clk; input rst_n; input [3 :0] data; output [2:0] add; reg [2 :0] add; always @(posedge clk) begin if (!rst_n) begin add <= 0; end else begin case {4{data[3]}}: // 假设data永远不会大于11,这里是为了确保case的完整性 add <= 4; {3{data[3]}, ~data[2]}: // data在8到11之间 add <= 4; {2{data[3]}, ~data[2], ~data[1]}: // data在4到7之间 add <= 3; {data[3], ~data[2], ~data[1], ~data[0]}: // data在0到3之间 add <= 1; default: // 如果data不在上述范围(实际上这是不可能的,因为data是4位) add <= 0; // 或者你可以省略这一行,因为上面已经覆盖了所有情况 endcase end end
两者结构完全一致的情况:
在功能逻辑上,如果两个代码段都是正确编写的,并且覆盖了所有可能的情况,那么它们将产生相同的结果。但是,case语句通常用于更明确地表示多个离散的条件,而if...else语句则用于表示更复杂的逻辑或条件序列。
最终布局布线后的结构差异:
布局和布线(placement and routing)是编译器和合成器在将高级描述(如Verilog)转换为实际硬件配置时执行的过程。这些过程可能会受到多种因素的影响,包括目标硬件架构、编译器优化、代码质量等。
在大多数情况下,如果if...else和case语句在功能上等价,并且没有其他因素(如其他逻辑或资源限制)影响布局和布线,那么它们最终产生的硬件结构应该是相似的。然而,由于编译器和合成器的实现细节,可能会存在微小的差异。
一个是基于if...else结构的(在您最初的提问中)和另一个是基于case结构的。不过,具体的数字在case结构的描述中并未给出,但我们可以基于一般的理解来推测它们之间的潜在差异。
理解这些资源使用指标的含义:
Total logic elements:总的逻辑元素数量,包括组合逻辑和寄存器。
Combinational with no register:没有寄存器的组合逻辑元素数量。
Register only:只有寄存器的逻辑元素数量(但这里没有给出实际数值,通常这是为了配置纯存储功能)。
Combinational with a register:带有寄存器的组合逻辑元素数量。
Logic element usage by number of LUT inputs:根据查找表(LUT)输入数量分类的逻辑元素使用。
Logic elements by mode:根据操作模式(如正常模式、算术模式等)分类的逻辑元素。
Total registers:总的寄存器数量。
I/O pins:输入输出引脚数量。
Maximum fan-out node:具有最大扇出(即连接到其他逻辑元素的数量)的节点。
Maximum fan-out:任何节点的最大扇出值。
Total fan-out:所有扇出的总和。
Average fan-out:每个节点的平均扇出值。
关于if...else和case结构在资源使用上的差异:
LUT输入数量:对于if...else和case语句,如果它们实现相同的逻辑功能,并且编译器/合成器足够智能,那么它们可能会使用相同数量的LUT输入。但是,如果case语句的分支非常多,它可能会需要更多的LUT输入或更多的LUT实例。
操作模式:对于简单的if...else和case结构,它们可能主要处于正常模式。然而,如果case语句包含了更复杂的算术或特殊操作,它可能会使用更多的算术模式或特殊模式。
寄存器使用:如果if...else和case结构都涉及到寄存器的使用(即Combinational with a register),那么寄存器的数量将取决于逻辑功能的实现方式。如果case语句的分支导致了更多的寄存器使用(例如,每个分支都需要存储不同的值),那么它可能会使用更多的寄存器。
I/O引脚:对于相同的逻辑功能,if...else和case结构通常会有相同的I/O引脚数量,因为它们都需要相同的输入和输出。
扇出:扇出主要取决于逻辑功能的连接方式和目标硬件的架构。如果if...else和case结构在硬件中产生了类似的连接,那么它们的扇出可能会很相似。
if…else语句和case语句综合后的RTL视图。单从RTL,视图来看,二者综合后的结果是有明显区别的。if…else趋向于有优先级的结构,而case则是并行的结构。
if…else 语句综合的 RTL视图:
case 语句综合的 RTL视图:
基于if...else结构的资源使用描述(我假设这是您提供的第一个描述):
UsageResource
Total logic elements
-Combinational with no register: 0
-Register only: 0
-Combinational with a register: ? (这个值没有给出,但假设存在)
Logic element usage by number of LUT inputs
--4 input functions: 0
--3 input functions: 2
--2 input functions: 1
--1 input functions: 0
--0 input functions: 0
Logic elements by node
-- normal mode: 3
-- arithmetic mode: 0
-- qfbk mode: 0(这可能是个打字错误,应为"qfbk mode: 0"或其他有效值)
-- register cascade mode: 0
-- synchronous clear/load node: 0 (同样,这个值没有给出)
-- asynchronous clear/load mode: 3
Total registers: 0 (这个值没有给出)
I/O pins: 9
Maximum fan-out node: rst_n
Maximum fan out: 3
Total fan-out: 14
Average fan-out: 1.17
注释:
Combinational with no register 和 Register only 都是0,意味着没有纯组合逻辑或纯寄存器逻辑。
Combinational with a register 的值没有给出,但假设存在。
LUT输入的使用情况表明,有两个3输入的函数,没有1输入或0输入的函数(这通常是合理的,因为1输入或0输入的函数很少用LUT实现)。
操作模式表明主要是正常模式和异步清除/加载模式。
寄存器总数和同步清除/加载节点的值没有给出。
再看它们的TechnologyMapViewer,分别如图所示。二者完全一致,所以,可以明确的说,在这个例子中,if…else和case语句最终的实现都是并行的,而且完全·致。
if…else 的Technology Map Viewer:
case 的Technology Map Viewer:
记得特权同学过去也曾认为if…else和case综合实现的结果是不一样的,也曾就这个实例写过博文,分析得头头是道。但是现在的结果似乎推翻了这样一种思想,过去使用的是Quartus Ⅱ 7.1i做这个测试,现在使用了9.1版本,8.1的也测试了,也许if…else和case 语句的优化随着软件的升级,已经不再简单地交给用户的代码来决定,而是默认优化了。就像状态机中讨论独热码好还是格雷码好一样,其实这个优化已经成为软件选项了。而综合的RTI,视图到Technology Map Viewer 其实也还是有差距的,它们之间的优化就是映射所要干的活。
总之,if…else和case语句实现的结构到底是怎样还是要看开发工具,具体问题具体分析,不能片面地强调if…else和case语谁好谁坏。
最后引用网友riple兄的一段话,不仅可以应用在if…else和case的讨论中,对于任何的代码风格问题都是一样的:
具体逻辑具体分析,如果你要表达的是同一个逻辑问题,那么if…else和 case 只不过是形式上的不同。综合工具的优化能力足够强的话,就能看穿这个形式上的不同,实现逻辑上的相同。
下面再看看它们综合后的RTL视图,如图所示。从RTL视图看,二者的实现确确实实也正如早先所预期的,一个带优先级,一个并行处理。
if…else 综合后 RTL视图:
case 综合后 RTL 视图:
再看看布局布线后的结构吧,如图所示。从最终布局布线后的结构看,和RTL视图很接近,这两个实例代码所使用的if…else和case最终实现的结构是有差异的。从之前的实例分析看这并不稀奇,意外的是使用if…else实现的结构资源消耗居然比。case要来得少(只是相对而言)。这样的结果似乎能够很好地反驳不少人提出的所谓“多用case语句,少用if…else语句,因为实现带优先级的结构比并行结构更耗费资源”的论断。特权同学提出这一点,并不是认为实现带优先级的结构更节省资源,而是想强调一点,任何代码的实现都不是绝对的,所谓好的代码风格也不是一成不变的,是需要设计者在实践中更多地进行具体问题具体分析。
另外,补充一点,该例子中使用多个if…if…语句实现的结果会和case语句的结果一致。
if…else布局布线后Technology Map Viewer:.
case布局布线后Technology Map Viewer:
input clk;input rst n; input close,wr,rd; output[2:0] db; reg[2;0] dbr; always @(posedge clk or negedge rst_n) beginif(!rst n)begindbr<= 3'd0; endelse begin dbr <= 3'd0; if({close,rd,wr)== 3b100)dbr<= 3'bl11; if({close,rd,wr)==3'b010)dbr <= 3'b101; if({close,rd,wr)==3b001)dbr<= 3'b011; end assigndb=dbr;
总结:Verilog代码示例(EX1使用if...else,EX2使用case)中,两者在功能上是等价的,并且假设输入data的范围是0到11(由于它是一个4位宽的输入),那么两个代码片段都会为add输出正确的值。
关于布局布线后的结构差异,确实取决于多种因素,包括编译器/合成器的实现、优化级别、目标硬件架构等。但通常,如果逻辑是等价的,那么生成的硬件结构在资源使用和性能上也应该是相似的。
然而,对于case语句,它通常被设计用于处理多个离散值的情况,并且对于合成器来说可能更容易进行某些优化,尤其是当条件表达式是简单的常量比较时。另一方面,if...else语句在处理更复杂的逻辑条件时可能更灵活。
在您提供的case语句示例中,有一些地方可能需要澄清或修改:
{4{data[3]}} 这个条件实际上永远不会为真,因为data[3]只有0或1两种可能,而{4{data[3]}}会生成一个4位宽的向量,其所有位都是data[3]的值。您可能想要直接比较data的值。
您的case语句中的条件表达式使用了位扩展和取反来匹配data的不同范围,这在某些情况下可能不是最高效的方法。通常,case语句的条件表达式会直接使用data的值。
在case语句的default分支中,由于您已经覆盖了所有可能的data值(从0到11),所以这个default分支实际上是不必要的。
至于资源使用指标:
Total logic elements:表示实现整个逻辑功能所需的总逻辑元素数量。
Combinational with no register:表示仅包含组合逻辑(没有存储元素)的部分。
Register only:表示仅包含寄存器(没有组合逻辑)的部分,但在这个例子中不太可能出现,因为add是一个寄存器,但它也参与组合逻辑。
Combinational with a register:表示包含组合逻辑和寄存器的部分,这是最常见的类型。
Logic element usage by number of LUT inputs:这提供了根据查找表(LUT)输入数量分类的逻辑元素使用情况的统计信息。LUT是用于实现组合逻辑的基本构件,其大小(即输入数量)会影响资源使用和性能。