别乱用 FULL_CASE 和 PARALLEL_CASE

作者:孤独的单刀,文章来源:CSDN博客

注:本文由作者授权转发,如需转载请联系作者本人

写在前面
case语句可以说是我们在FPGA开发中使用频率非常高的一条语句。同时,Verilog还提供了语句 casex 和 casez供我们使用。在使用case语句的时候,各类综合工具都提供了以下类似的两句综合语句供我们使用(以Xilinx为例):FULL_CASE 和 PARALLEL_CASE

这两条综合属性可以帮助我们在一定程度上减少资源,但是其使用也比较容易引入BUG--前后仿真不匹配。接下里就一起看看这把双刃剑的正反两面吧。

1、FULL_CASE的用法
在case语句的使用中,我们通常会加上default语句来说明未使用状态的输出赋值,如果不加上default语句则通常会产生锁存器LATCH。

1.1、使用default语句
在如下的8位独热码编码器中,我们用default语句指令了所有除8个有用状态的其他状态。8位2进制数可以一共表示256个状态,这其中只有这8个状态是独热码,剩余的都是我们不需要的。使用default语句全给其输出接到了统一的0。但是,虽然我们不需要使用其他状态,但是综合出来的电路也会有其他状态的编码部分。

module one_hot(
input [7:0] sel,
output reg [2:0] out
);

always @(*) begin
case(sel)
8'b0000_0001 : out = 3'b000;
8'b0000_0010 : out = 3'b001;
8'b0000_0100 : out = 3'b010;
8'b0000_1000 : out = 3'b011;
8'b0001_0000 : out = 3'b100;
8'b0010_0000 : out = 3'b101;
8'b0100_0000 : out = 3'b110;
8'b1000_0000 : out = 3'b111;
default : out = 3'b001;
endcase
end

endmodule

下图是综合出来的电路结构,就是一个纯组合逻辑的编码电路。

使用的资源情况:6个LUT。

1.2、不使用default语句
接下来把default语句注释掉,这样在非独热码状态的情况下,输出就只能保持之前的状态不变了,也就是说3bit输出应该会输出3个锁存器LATCH。

综合出来的电路结构如下,和料想的一致,组合逻辑LUT+锁存器LATCH。

使用的资源情况:5个LUT + 3个FF(FF转变的LATCH)。

1.3、使用综合属性 FULL_CASE
那么综合属性 FULL_CASE是干啥的?

FULL_CASE的作用就是告诉综合工具,在这个case语句中,我已经把所有需要的情况都列出来的,剩下的那些是我不需要,你可别再生成多余的电路了。

还是上面的例子,8bit的独热码编码器,我已经把8个独热码都列出来了,剩下的那256-8=248个臭鱼烂虾都不是独热码编码形式的,我肯定不会用,所以我警告你(综合工具)最好识相点儿,不要生成对应剩余编码的编码电路了。
module one_hot(
input [7:0] sel,
output reg [2:0] out
);

always @(*) begin
(* full_case *)case(sel) //所有需要的情况都列出来了
8'b0000_0001 : out = 3'b000;
8'b0000_0010 : out = 3'b001;
8'b0000_0100 : out = 3'b010;
8'b0000_1000 : out = 3'b011;
8'b0001_0000 : out = 3'b100;
8'b0010_0000 : out = 3'b101;
8'b0100_0000 : out = 3'b110;
8'b1000_0000 : out = 3'b111;
//default : out = 3'b001;
endcase
end

endmodule

综合出来的电路,比上面两种情况少用了很多资源。因为多余的248个状态vivado不需要编码了,只需要对8个独热码状态进行编码即可。

使用的资源情况:3个LUT。

1.4、综合前后电路仿真不一致
优点是显而易见的,就是节省资源。缺点同样明显,那就是 FULL_CASE 语句是一条综合属性,只能在vivado对RTL进行综合,translate成电路的时候才使用,在做功能仿真的时候是不适用的,也就是说会造成前后仿真结果不一致的问题,容易引入BUG。

我们可以写个简单的testbench分别对综合前、综合后的功能仿真做下测试。
`timescale 1 ns/ 1 ns
module one_hot_tb();

reg [7:0] sel;
wire [2:0] out;

one_hot one_hot_inst(
.sel (sel),
.out (out)
);

initial begin
sel=8'b0;
# 400 $finish; //200ns后结束仿真
end

always #10 sel = {$random}%256; //没10ns生成一个0~255的随机数

endmodule

综合前功能仿真结果如下:

由于之前sel值均不是独热码值,所以输出out无法被编码,又没有使用default语句,所以此时的输出均为x。直到第一个独热码00000001出现,输出开始编码到000。后面又不符合独热码规则,无法编码,所以输出值一直保持之前的值不变,成为了事实上的锁存器。

直到输入重新变成了10000000和00100000后,输出才开始变成对应的编码值 。

综合后功能仿真结果如下:

这个结果是没有生成锁存器的,即使对于非独热码,也有默认的编码值。

1.5、陷阱
一般情况下,FULL_CASE 语句是不建议使用的,除非你对自己的RTL代码和电路结构有很深的理解。下面是一些使用该语句的陷阱。

(情况一:可能还会存在的锁存器)

有种说法是:FULL_CASE 语句技能减少资源使用,也能消除锁存器。实则不然,在某些情况下FULL_CASE 语句并不能消除锁存器,比如:

module addrDecode1a (mce0_n, mce1_n, rce_n, addr);

output mce0_n, mce1_n, rce_n;
input [31:30] addr;

reg mce0_n, mce1_n, rce_n;

always @(addr)
(* full_case *)casez (addr)
2'b10: {mce1_n, mce0_n} = 2'b10;
2'b11: {mce1_n, mce0_n} = 2'b01;
2'b0?: rce_n = 1'b0;
endcase
endmodule

综合出来的电路结构:可以看到还是综合出了2个锁存器。这是因为在case执行语句中,同时出现了对多个变量和单个变量进行赋值,这使得在某些条件下某些变量是无法执行输出的。

解决这一情况的办法,就是在case语句之前对所有变量均进行赋值,后者直接使用default语句,让所有的情况都有一个入口。

(情况二:非预期的电路结构)

有些时候,如果不注意电路的结构,非要使用FULL_CASE 语句的话,甚至会综合出与预期功能完全不一样的电路。比如:

// no 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'b0; //初始赋值,防止其他情况无入口产生锁存器
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

上面的例子中,首先通过 y = 4'b0;为所有情况都提供了一个赋值的入口,防止了锁存器的产生。然后en作为使能信号,只有在其高电平有效的情况下,才能将地址选择信号a对应的输出y[a]的值拉高。

综合出来的电路如下:可以看到每一位y的输出都有en参与运算。

因为地址选择信号只有2位,所以其最大只有4种情况,在上述代码中,这四种情况刚好都列出来。于是,你觉得这个时候可以使用FULL_CASE 语句了,说不定能节省点电路面积。所以,让我们看看接下来会发生什么。

这是使用了FULL_CASE 语句后综合出来的电路:

注意看,这个电路最离谱的是,使能信号en已经和整个电路完全没关系了。这是为什么呢?

这是因为,虽然在使能信号置位时,地址选择信号a只有4个状态;但是在使能信号无效时, 地址选择信号a同样有4个状态。如果使用了FULL_CASE 语句,综合工具可能就会认为此时地址选择信号a已经列出了所有的情况,那么case语句中的{en,a}就从一个3bit信号变成了2bit信号,而自动把使能信号en给忽略掉了!

2、PARALLEL_CASE的用法

在学习综合语句PARALLEL_CASE的使用之前可能需要复习一下casex和casez语句的使用。Verilog中case,casez,casex语句的用法

有时在用case语句时,产生的电路会有优先级。如果希望没有优先级,即所有的输入都是并行的情况,要怎么办呢?答案就是使用综合属性PARALLEL_CASE。

PARALLEL_CASE的作用就是告诉综合工具,在这个case语句中,所有已经被我列出来的情况都是并行的,不需要优先级,你可别按优先级给我生成电路了!

比如下面使用casez语句的例子,如果不使用PARALLEL_CASE属性,则综合出来的电路肯定是有优先级的:

module intctl2a (int2, int1, int0, irq);
output int2, int1, int0;
input [2:0] irq;
reg int2, int1, int0;

always @(irq) begin
{int2, int1, int0} = 3'b0;
casez (irq)
3'b1??: int2 = 1'b1;
3'b01?: int1 = 1'b1;
3'b001: int0 = 1'b1;
endcase
end
endmodule

综合出来的电路如下。可以看到 int2 只由 irq[2] 决定,int1 由 irq[2] 和 irq[1] 决定,int0 则由irq[2], irq[1] 和 irq[0] 决定。这是因为 case 语句是有优先级的,写在前面的优先级最高。

接下来我们加入综合属性语句:(* parallel_case *)。
module intctl2a (int2, int1, int0, irq);
output int2, int1, int0;
input [2:0] irq;
reg int2, int1, int0;

always @(irq) begin
{int2, int1, int0} = 3'b0;
(* parallel_case *)casez (irq)
3'b1??: int2 = 1'b1;
3'b01?: int1 = 1'b1;
3'b001: int0 = 1'b1;
endcase
end
endmodule

综合的电路如下。修改后的电路已经不存在优先级了,都是对应bit的irq信号直接控制对应的int信号。

parallel_case与full_case综合属性一样,存在的一个最大问题就是综合前后的仿真结果不一致的问题,容易引入BUG。

3、总结
casez语句的使用要要谨慎,而casex语句则尽量不要使用
如果计划在Verilog代码中添加“full_case parallel_case”指令,则需要更对设计的RTL有全面深入的了解
不要滥用仅full_case和parallel_case,一般情况下只用来来优化独热码的状态机设计
最重要的一点,比起使用“full_case”和“parallel_case”指令,更好的方法是编写完整和并行的case语句!

最新文章

最新文章