1.形式验证工具
对于某些电路的移植,一般不需要对新电路进行仿真验证,而可以直接通过EDA工具来分析该电路的功能是否与原电路一致,此种验证方法可以大量减少验证时间,提高电路的效率。
等效性检查(Equivalence Check)是目前形式验证的主流,用于比较两个电路逻辑功能的一致性。它是通过采用匹配点并比较这些点之间的逻辑来完成等效性检查的。其生成一种数据结构,并将其与相同输入特性曲线条件下的输出数值特性曲线进行比较。如果它们不同,则表示被比较的两个电路是不等效的。工具使用的具体流程如下图所示。首先需要给工具提供完整正确的原设计、相关的工艺库及准备验证的设计,其次需要对检查过程给定约束条件和设置参数,并确定比较范围和匹配点,如果结果不相等则需要进行诊断。它通常用来比较RTL代码和布局布线后提取的网表逻辑功能是否一致、加入扫描链之前和之后的网表在正常工作模式下的逻辑功能是否一致以及ECO修正之前和之后的网表逻辑功能是否一致。
在ECO阶段,RTL不会经历完整的综合流程。当定位了一个错误或者在设计流程的最后阶段需要增加一个新的调整时,首先要确认对RTL功能修改,然后需要进行测试以确信所做的修改符合预期。此后直接在网表上进行修改。我们怎样才能确认修改后的网表与RTL是一致的呢?我们可以对修改后的网表进行门级仿真并检查修改是否达到了预期目标。但门级仿真是不能够提供担保的,不能100%的确定修改后的RTL和修改后的网表是等效的。
下面是一些等效性检查工具。
生产厂商 | 工具名 |
Cadence | Conformal |
Synopsys | Formality |
Mentor | FormalPro |
Magma | Quartz Forma |
2.网表ECO
ECO是一个重要的设计阶段。在ECO阶段,可以不对RTL代码进行重新综合,而是直接对网表进行修改。在接近芯片设计的最后阶段(tape-out)时,对RTL代码修改并重新综合会带来高昂的代价。如果只需要进行微小的修改,那么在tape-out之前可以通过ECO加以解决。下面是ECO的流程。
- 在RTL代码上修正错误并对其进行验证,保证芯片功能符合预期。
- 通过ECO,在网表上直接进行错误修复。可以添加或修改逻辑门来修复错误,ECO阶段能够进行的修复工作是受限的。典型情况下,可以增加几个触发器和一些逻辑门。如果涉及几百个触发器和几百个逻辑门,最好全部重新综合,这比ECO要简单。在ECO中,控制单元(状态机)的修改比数据通路的修改更容易。
- 接下来在修改过的RTL代码和修改过的网表之间进行等效性检查以确保二者在功能上是等效的。
由于ECO直接在网表上进行,网表修改后的功能不便于直接分析。下面将讨论两种典型的ECO方法。
方法1:修改逻辑锥
这一方法通过对已有的逻辑门进行增加和重新排列达到修改逻辑功能并与更新后的RTL功能匹配的目的。这涉及查看驱动触发器的逻辑锥,查看现有节点和对逻辑锥进行修改等操作。这需要采用试错法反复试验,但可以通过增加最少的逻辑门达到修改的目的。这一方法需要在修改区域附近空闲逻辑门数量较少时更为有效。ECO Compiler或Conformal LEC等工具可以帮助设计进行ECO。
方法2:新逻辑覆盖
采用此方法时,需要使用逻辑门额外地设计一组逻辑电路,其输出通过一个选择器接入现有的触发器。当满足给定的条件时,通过选择器可以选择我们需要的值,在其他条件下,通过选择器选择旧的值。这种方法更为直观,但需要使用更多的逻辑门。我们在下面将会采用方法2的例子加以分析。
示例描述
状态机从一个FIFO中读出数据,通过dataout和dataout_valid将其交给接收代理电路。接收代理电路通过target_rdy指出一个数据传输阶段的完成。换句话说,当dataout_valid和target_rdy都有效时,一个数据传输操作结束。控制器在一个数据传输阶段会始终驱动dataout和dataout_valid,直到target_rdy有效。状态机持续处于数据传输阶段,直到它从数据FIFO中读取到了end_of_pkt标识。在最后一个数据传输完成后,dataout_last信号有效。这是一个在两个代理之间进行数据传输的典型协议,两个代理可以通过dataout_valid和target_rdy分别进行流量控制。
错误
状态机在设计时有一个错误,它没有考虑到FIFO在包传输结束前(end_of_pkt信号前)可能暂时为空。如果数据FIFO被读空时包没有结束,状态机会对一个空的FIFO进行读操作,从而导致错误的发生。
ECO修正
在BURST_DATA状态时,跳转到另一个状态WAIT_NONEMPTY并等待FIFO进入非空状态。被注释的代码指出了需要增加的新状态和逻辑。这里需要创建一个新的状态,一些输出需要做出改变。示例代码如下:
reg [2:0] xmitstate, xmitstate_nxt;
reg fifo_rden;
reg [15:0] dataout, dataout_nxt;
reg dataout_valid, dataout_valid_nxt;
reg dataout_last, dataout_last_nxt;
parameter IDLE = 3'b000,
DATA_AVAIL = 3'b001,
FIRST_DATA = 3'b010,
BURST_DATA = 3'b011,
LAST_DATA = 3'b100;
//新加的状态
//parameter WAIT_NONEMPTY = 3'b101;
always@(posedge clk, negedge rstb) begin
if(!rstb) begin
xmitstate <= IDLE;
dataout <= 0;
dataout_valid <= 0;
dataout_last <= 0;
end
else begin
xmitstate <= xmitstate_nxt;
dataout <= dataout_nxt;
dataout_valid <= dataout_valid_nxt;
dataout_last <= dataout_last_nxt;
end
end
always@(*) begin
xmitstate_nxt = xmitstate;
fifo_nxt = 1'b0;
dataout_nxt = dataout;
dataout_valid_nxt = 1'b0;
dataout_last_nxt = 1'b0;
case(xmistate)
IDLE: begin
if(start_xmit)
xmitstate_nxt = DATA_AVAIL;
end
DATA_AVAIL: begin
if(!fifo_empty) begin
xmitstate = FIRST_DATA;
fifo_rden = 1'b1;
end
end
FIRST_DATA: begin
data_out_nxt = fifo_rddata;
dataout_valid_nxt = 1'b1;
if(end_of_pkt) begin
xmitstate_nxt = LAST_DATA;
dataout_last_nxt = 1'b1;
end
else begin
xmitstate_nxt = BURST_DATA;
fifo_rden = 1'b1;
end
end
BURST_DATA: begin
if(target_rdy) begin
data_out_nxt = fifo_rddata;
dataout_valid_nxt = 1'b1;
if(end_of_pkt) begin
xmitstate_nxt = LAST_DATA;
dataout_last_nxt = 1'b1;
end
//else if(fifo_empty)
// xmitstate_nxt = WAIT_NONEMPTY;
else
fifo_rden = 1'b1;
end
else begin
dataout_nxt = data_out;
dataout_valid_nxt = 1'b1;
end
end
LAST_DATA: begin
if(target_rdy)
xmitstate_nxt = IDLE;
else begin
dataout_nxt = dataout;
dataout_valid_nxt = 1'b1;
dataout_last_nxt = 1'b1;
end
end
/*新加的状态
WAIT_NONEMPTY: begin
if(target_rdy) begin
if(!fifo_empty) begin
xmitstate_nxt = FIRST_DATA;
fifo_rden = 1'b1;
end
else
xmitstate_nxt = DATA_AVAIL;
end
else
dataout_valid_nxt = 1'b1;
dataout_nxt = dataout;
end
*/
endcase
end
进行ECO
对于修改错误时新加入的RTL代码,需要改变xmistate_nxt[2:0], dataout_valid_nxt和fifo_rden。接下来我们要解决xmistate_nxt[2:0]逻辑,它可能是三者中最困难的。
第一个覆盖条件
if((xmitstate == BURST_DATA) && target_rdy && !end_of_pkt && fifo_empty)
xmitstate_nxt = WAIT_NONEMPTY
即
if((xmitstate == 3'b011) && target_rdy && !end_of_pkt && fifo_empty)
xmitstate_nxt = 3'b101
第二个覆盖条件
if((xmitstate == WAIT_NONEMPTY) && target_rdy && !fifo_empty)
xmitstate_nxt = FIRST_DATA;
即
if((xmitstate == 3'b101) && target_rdy && !fifo_empty)
xmitstate_nxt = 3'b010;
第三个覆盖条件
if((xmitstate == WAIT_NONEMPTY) && target_rdy && fifo_empty)
xmitstate_nxt = DATA_AVAIL;
即
if((xmitstate == 3'b101) && target_rdy && fifo_empty)
xmitstate_nxt = 3'b001;
下图展示了对xmitstate[2]的ECO,对另外两个比特的ECO方法与此类似。当然,在实际ECO时,根据单元库可以选择的逻辑门,可以对逻辑电路进一步优化。在ECO时,最初进入xmitstate[2]触发器的输入,被切断并连接到一个由或门和与门构成的选择器,通过控制选择器可以决定选择新加入的逻辑分支还是最初的逻辑分支。以上三个覆盖条件不能同时全部满足,且可以发现第二和第三覆盖条件中,xmitstate[2]都为0,且用(xmitstate == 3'b101) && target_rdy就可同时覆盖这两者。这就导致了ECO中只出现了两个覆盖条件A和B。
ECO的解释
在图中的点A和点B处,有两个覆盖条件。当第一个覆盖条件(点A)为真时,我们希望xmitstate[2]的输入为1。当A为1时,或门OR-A输出为1,此时第二个覆盖条件为假,即B点为1,与门AND-B输出也为1。当第二个或第三个条件为真时,我们希望xmitstate[2]的输入为0,。在这种情况下,点B为0,且AND-B的输出也为0。这是在第二种和第三种覆盖条件下我们所期望的结果。
总之,我们能够在第一种覆盖条件下得到1,在第二种和第三种条件下得到0。在这些条件下所得到的的逻辑值不依赖最初的条件,因为这些条件处于逻辑锥的前部。当所有覆盖条件均不为真时,点B为1,且点A为0,这使得点C(最初的值)传递到触发器的输入端。
以上内容来源于《Verilog高级数字系统设计技术和实例分析》和《SOC设计方法与实现》