位流验证,对于芯片研发是一个非常重要的测试手段,对于纯软件开发人员,最难理解的就是位流验证。在FPGA芯片研发中,位流验证是在做什么,在哪些阶段需要做位流验证,如何做?都是问题。
我们先整体的说一下:
首先:在硬件设计阶段,位流验证是设计验证部的重要工作,它是为了验证硬件设计的数字电路部分的合理性,对于FPGA芯片,主要就是对标准的架构组件进行验证。如果相应的EDA软件未成形之前,相应的位流需要通过手工单独生成。
然后:在FPGA配套的EDA软件开发阶段,位流验证也非常重要。因为EDA软件的最大目标就是生成正确的位流,位流输出是否符合预期,关系到软件各个环节的正确性。所以,一般会将位流验证做为重要的回归测试手段,一方面,可以持续验证最新输出软件的正确性,另外,如果芯片的底层架构发生变化,模型变更,仿真库变更,也可以通过位流验证,保证架构更改的正确性(架构主要是按照硬件设计对应的软件模型,而硬件设计的这些模型,都会有对应的仿真库和参考模型,用于验证)。
最后:在生产测试阶段,位流验证同样是非常好的测试手段,它可以测试位流写入芯片后上板上芯片的效果,确认芯片实际的运行情况。在软件中只是保证了逻辑上的正确,实际结合电气特性和各种不同环境是否能正常可靠运行,可能通过位流加载后的效果进行验证。加载到实际板上的效果,也可以通过驱动程序取出,与预期参老模型的结果做对比,验证其准确性。
当然,位流验证可能并不仅限于上面的三种情况(限于我了解的内容有限),但通过上面的描述,你可以发现,位流验证本身是就是会跨多个部门的比较特殊的手段,在不同部门做验证时,一定会存在配合,和一些有相似的工作。但是,因为各自的目的不同,发现错误后,定位和纠错的方向也很不一样。反以,反过来说,选择的相应示例也会有些不同。
今天,我们只讲在EDA软件如何利用位流验证,完成对EDA软件的重要回归测试。
大概的讲法是:
:我们会讲解位流验证依赖的框架UVM
:会讲一下UVM的实现平台。
:会讲一下针对EDA软件的验证,会对哪些部分进行验证,会使用什么样的示例 。
:发现问题后,会如何通过输出去定位错误。
:因为用于自动化回归,也讲一下相应的自动化平台应该如何拱建。
首先,我们还是得再来理解一下位流验证需要用到的UVM框架。(之前在单讲测试时,也说过一次,但讲得并不仔细)
一:UVM框架
1.1:理论
Universal Verifacation Methodology:是一套基于SystemVerilog的标准验证方法学。它提供了一套完整的框架,用于构建复杂的验证环境,应用于芯片设计领域的功能验证。旨在提高验证的可重复性、可重用性和自动化程度。应该说,对于硬件设计的验证,UVM是最基础,最常用的平台,离不开它。
2.1.1:DUT
待测设计——Design Under Test,就是你要验证的设计单元。
如果你是要验证/测试一颗 FPGA 芯片,DUT 一般会分为三种,一种是单Tile的设计,一种是多Tile的设计,还有就是整个芯片的设计。
注意这里我加了个bitstream,这并不是UVM必须的,加上bitstream是和我们今天要讲的位流验证扣题,也就是我们在验证时,使用FPGA的位流文件,来形成DUT的功能。
env:Testbenc Environment ,除了DUT,其它就是UVM的验证环境了,它包含了一大堆东西,后面一个一个介绍。
- Sequencer:测试数据序列,因为测试时需要很多的输入数据,我们按序列提供。
- Driver:如何数据输入DUT的过程,可以理解为激励的过程。
- Monitor:监控DUT的输入/输出信号
- Reference Model:参考模型,对应DUT的设计,提供相同行为的给果。
- 这里参考模型的运行,一般我们会使用仿真工具的内置实现来完成。
- Compare:比较DUT的输出和参考模型的输出,判断是否匹配。
2.1.2:Sequencer
Sitmulus:生成测试数据序列。我们需要基于测试的需求,产生数据,交给Driver来驱动到DUT。比如:你要测试DSP的乘法,那就是提供相应的输入数值对。
class my_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(my_sequence)task body();
my_transaction tr;
foreach (tr.data[i]) begin
tr.data[i] = $urandom_range(0, 255); // 随机生成 8 位数据
start_item(tr); // 发送数据
finish_item(tr);
end
endtask
endclass
2.1.3:Driver
接收Sequencer的测试数据并将其转换为DUT的输入信号。
这里可能需要模拟时序,比如:时钟的同步,握手协议等。
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // 接收 sequencer 的数据
@(posedge clk); // 时钟同步
dut_input = req.data; // 将数据驱动到 DUT
seq_item_port.item_done();
end
endtask
endclass
2.1.4:Monitor
实时监控DUT的输入和输出信号,将监控到的数据送出到比较系统Compare,进行输出分析。
class my_monitor extends uvm_monitor #(my_transaction);
`uvm_component_utils(my_monitor)task run_phase(uvm_phase phase);
forever begin
@(posedge clk);
tr.data = dut_output; // 采集 DUT 的输出数据
analysis_port.write(tr); // 将数据发送给 Scoreboard
end
endtask
endclass
2.1.5:Reference Model
参考模型,用于生成和DUT相同逻辑功能的输出(Golden Model)。
基于相同的输入激励信号,获得理想的输出。
function bit [7:0] ref_model(input bit [7:0] data);
return data + 1; // 示例:参考模型输出为输入加 1
endfunction
2.1.6:Compare
这里的Compare,可能是简单的比较结果的输出,也可能是比较复杂的Scoreboard(一个记录数据表)。根据比较结果,输出不匹配的情况,并输出报告。
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)task run_phase(uvm_phase phase);
my_transaction ref_tr, dut_tr;
forever begin
ref_tr = ref_fifo.get(); // 从参考模型接收预期数据
dut_tr = dut_fifo.get(); // 从 DUT 获取实际输出
if (ref_tr.data !== dut_tr.data)
`uvm_error("Mismatch", $sformatf("Expected: %0h, Got: %0h", ref_tr.data, dut_tr.data));
end
endtask
endclass
2.1.7:示例
我们再来一个完整的示例,说明一下各部分做的事情。
DUT设计—— 一个简单的8位加法器。
DUT代码:(对于位流验证,输入不是这个,后面会举例说明)
module adder(
input logic [7:0] a,
input logic [7:0] b,
output logic [7:0] sum
);
assign sum = a + b;
endmodule
UVM环境:
这里需要定义一个Transaction,因为每次激励的执行都是不同的事务,事务标明输入和输出。
Transaction 定义:
class my_transaction extends uvm_sequence_item;
rand bit [7:0] a, b; // 输入信号
bit [7:0] expected_sum; // 参考模型生成的期望输出`uvm_object_utils(my_transaction)
// 构造函数
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
Sequencer:
class my_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(my_sequence)task body();
my_transaction tr;repeat (10) begin
tr = my_transaction::type_id::create("tr");
tr.a = $urandom_range(0, 255); // 随机生成输入 a
tr.b = $urandom_range(0, 255); // 随机生成输入 b
start_item(tr); // 开始传输数据
finish_item(tr); // 结束传输数据
end
endtask
endclass
Driver:
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)// DUT 的接口
virtual adder_if dut_if;// 构造函数
function new(string name = "my_driver", uvm_component parent);
super.new(name, parent);
endfunction// 运行阶段
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // 从 Sequencer 获取数据
@(posedge dut_if.clk); // 等待时钟上升沿
dut_if.a = req.a; // 驱动 DUT 输入 a
dut_if.b = req.b; // 驱动 DUT 输入 b
seq_item_port.item_done(); // 标记数据已完成
end
endtask
endclass
Monitor:
输入监控
class input_monitor extends uvm_monitor;
`uvm_component_utils(input_monitor)// DUT 接口
virtual adder_if dut_if;
uvm_analysis_port #(my_transaction) analysis_port; // 分析端口function new(string name = "input_monitor", uvm_component parent);
super.new(name, parent);
endfunction// 运行阶段
task run_phase(uvm_phase phase);
forever begin
@(posedge dut_if.clk); // 等待时钟上升沿
my_transaction tr = my_transaction::type_id::create("tr");
tr.a = dut_if.a; // 采集 DUT 输入 a
tr.b = dut_if.b; // 采集 DUT 输入 b
analysis_port.write(tr); // 将交易数据发送给分析端口
end
endtask
endclass
输出监控:
class output_monitor extends uvm_monitor;
`uvm_component_utils(output_monitor)// DUT 接口
virtual adder_if dut_if;
uvm_analysis_port #(my_transaction) analysis_port; // 分析端口function new(string name = "output_monitor", uvm_component parent);
super.new(name, parent);
endfunction// 运行阶段
task run_phase(uvm_phase phase);
forever begin
@(posedge dut_if.clk); // 等待时钟上升沿
my_transaction tr = my_transaction::type_id::create("tr");
tr.expected_sum = dut_if.sum; // 获取 DUT 输出 sum
analysis_port.write(tr); // 发送到分析端口
end
endtask
endclass
Compare:
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)uvm_analysis_imp #(my_transaction, my_scoreboard) input_analysis_imp;
uvm_analysis_imp #(my_transaction, my_scoreboard) output_analysis_imp;my_transaction expected_tr, actual_tr;
function new(string name = "my_scoreboard", uvm_component parent);
super.new(name, parent);
input_analysis_imp = uvm_analysis_imp #(my_transaction, my_scoreboard)
::type_id::create("input_analysis_imp", this);
output_analysis_imp = uvm_analysis_imp #(my_transaction, my_scoreboard)
::type_id::create("output_analysis_imp", this);
endfunction// 输入数据分析
function void write(input my_transaction tr);
expected_tr = tr;
expected_tr.expected_sum = expected_tr.a + expected_tr.b; // 参考模型
endfunction// 输出数据分析
function void write(output my_transaction tr);
actual_tr = tr;// 比较 DUT 输出和参考模型的期望值
if (actual_tr.expected_sum !== expected_tr.expected_sum) begin
`uvm_error("Mismatch", $sformatf("Expected: %0d, Got: %0d",
expected_tr.expected_sum, actual_tr.expected_sum));
end
endfunction
endclass
2.1.8:位流验证
那对于位流验证,UVM是如何使用的呢?
1:首先,必须准备好你的FPGA的EDA软件(准备好运行时需要的 Flow 的tcl命令)。给出你的芯片仿真时需要的Primitve仿真库。
2:针对你要测试的全芯片或者部分tile,选择你要测试的设计用例(需要有一定的代表性噢,可以是N多,需要有一定覆盖度),用 flow 生成仿真用的门级网表文件,并输出用例的route结果,输出最终的bitstream。
3:开始对要测试的用例,编写参考模型(也是另外一种实现方案)。
4:编写激励(测试)程序,提供相应的激励数据序列。
5:在TestBench上,针对 DUT 的 RefModel模型进行仿真,验证两者输出是否一致,如果一致,说明位流测试正常。比较的方法,要看测试用例的功能。
6:如果测试不正常,可以利用中间输出,日志输出,波形输出等信息进行定位,查看出错原因。
好了,那UVM框架是如何实现的呢?有什么支撑平台和工具吗?
1.2:支撑
那UVM这种框架是如何通平台来支撑它的呢?我们以Synopsys的VCS为例,来说明它是如何支持的(当然,也可以使用Mentor提供的QuestaSim)。
1.2.1:工具
VCS(Verlog Compiler Simulator)是业界领先的仿真工具,原生支持UVM,它提供了一系列的工具和功能,全而覆盖了UVM测试平台中的各项工作。
UVM 组件 | VCS 支持工具/功能 |
---|---|
Sequence | SystemVerilog 编译器直接支持 UVM sequence 的随机生成和调试。 |
Driver | 通过 VCS 的时钟精确仿真,支持 UVM driver 精确地驱动 DUT 信号。 |
Monitor | UVM 信号分析器,与 DVE 图形工具集成,监控信号流。 |
Compare Scoreboard | 与覆盖率分析工具集成,支持寄存器覆盖率和功能覆盖率的检查。 |
Debug | 提供图形化的 UVM Phase 调试工具,支持动态调试和波形交互分析。 |
Verification | 集成 DVE 和 Verdi 工具,查看波形和调试测试平台的运行时行为。 |
基于VCS的功能,要实现UVM平台,还需要封装一些标准的命令,达成常见的功能。
1.2.2:平台
为了实现UVM的基础功能,我们需要提供一些封装功能,我们称之为平台。因为VCS并不是直接针对UVM的,所以,我们需要针对它做一些封装,然后我们基于封装来使用,这样,看起来就更像是一个针对UVM实现的平台了。
基本功能如下:(封装方法可以使用Python或其它脚本语言来实现)
- 平台初始化:
确认 测试平台的主目录,测试工程目录,测试用例目录,工具目录,输出目录……
- 设置重要参数:
测试的代码列表库
测试用例的分组
运行次数
是否调试模式:提供更多log输出
提供随机种子数:用于控制输入参数的随机种子,控制测试数据的生成。
……
- 编译/构建:
我们的testbench的代码还是相对比较复杂的,另外,它也依赖于背后的仿真平台提供的大量的库文件,所以,我们一般需要对testbench的代码进行构建,输出各种我们需要的环境。
首先要对DUT代码进行编译,一般是SystemVerilog代码。
一般会使用 Makefile,需要编写相应的makefile,主要是需要将用到的UVM的动态库和引用资源进行构建。VCS有一些针对UVM的支撑库。
- 仿真:
直接调用VCS的仿真,有低功耗的仿真模式可选
- 波形分析:
输出波形,用于Verdi波形调试
- 代码覆盖率统计:
统计测试的代码覆盖率
……
1.2.3:运行
在运行期,为了保证示例可以并行执行,一般会使用HPC集群环境。我们可以使用LSF集群管理,通过 bsub 来将相应的任务,提交到HPC中运行。
比如:
‘一般会把任务分为多条,按顺序将任务放入同一个队列,保证先后次序
bsub -n 1 -M 20480 -q dv_test -Is "上面封装的命令,完成编译或仿真或波形分析的功能"
以上是运算支撑最基本的要求。详细的实现,我们后面会展开给个实例来讲。
二:位流验证
对于位流验证,我们今天要讲的主要是 FPGA的EDA工具测试,在EDA工具的Flow已基本成形的情况下,就可以开始搭建位流验证平台了,用于回归/验证EDA工具的功能。
2.1:流程图
我们先来解释一下上面的典型流程:
- 测试用例RTL文件
使用FPGA的器件的用例。测试用例文件,一般是DeviceModel提供的一些适合于验证功能的标准用例。作为待测用例。(这些用例,一般是由简到繁)
- EDA Flow
FPGA的软件工具的运行flow,在自动化运行时,一般是采用无界面的tcl命令来执行。
这里需要保留多个输出:
综合/映射后的网表:这是针对所有Primitve的电路实现,可以理解为罗列出,我们的设计最终实际用到了哪些逻辑的实例。就是可以看到设计最终使用的Instance,有明确的标识名称。
Route后输出的路由文件:这个用来定位所有实例Instance在芯片中实际的物理位置。也就是Impl后,实际上物理芯片上使用的逻辑块和相应的布线。结合上一步的输出,可以明确查到实例具体的位置,以及走线的路径。
Bitstream 位流:这个是生成的bitstream,这个作为Dut的输入,直接加载到测试平台(如:VCS),加上提前准备好的芯片的原生的仿真库,仿真平台可以实际的模拟芯片的运行,并且得到相应的运行中结果和中间输出。
- 用例的TestBench
根据实际情况,生成的tb文件。包含激励,定位,比较的逻辑。
测试用例表:原始的测试用例
Primitive行为模型:明确这个Primitive的行为是什么,在寻找参考模型时,需要找到对应的参考实现。
解析输入/输出管脚:找出输入IO 和 输出IO,这个可以在Route 中找到。
编写用例参考模型:根据Primitive的行为和你的设计用例,写等价的功能。注意,这里的等价模型,一般是仿真平台直接执行出结果(并不是基于FPGA的逻辑电路来执行)。
设计输入的激励信号:设计测试的输入数据和相应的序列。
编写比较函数:看如何比较两者的输出。对于返回值 ,可以简单比较输出。对于时序,可能需要检查波形,一般是选择几个关键的点,不可能全部检查。
- 位流测试平台
结合位流文件,仿真库,testbech ,使用UVM平台进行实际的运行。完成代码的编译,构建,仿真,结果比较的功能。
- 测试结果
实际比较结果,将结果输出到指定位置,并形成报告。
对于失败的报告,还可以通过查看其它输出(日志,波形等),具体进行问题定位。
对于覆盖度的统计,可以查看目前回归用例的覆盖度,进而逐渐提升。
2.2:验证的问题
如果2.1验证出问题,那可能有哪些问题呢?我们可以看看。
- 文件的问题
可能是测试用例的问题,route输出的问题(net丢失之类),postmap输出的问题…… - IOPackage的问题
可能是IO Pad的错误,这可能是IO Package的问题。可能是输入Pad,也可能是输出Pad不对。IO配置不对,配置信号不对,绑定约束的处理问题。 - 路由的问题
提供的switch box 的连接不对。 - 逻辑单元不对
模块的配置有问题,配置问题导致功能出错,模块到Switch box的连线问题 - 其它
DeviceModel建模的问题,也有可能是Primitive实现的问题(硬件方面的)
具体的定位,可以通过源代码,PostMap文件,route文件来查到具体的实例,然后在VCS的仿真器上,查看具体时点的输出,这里可能是需要使用工具查看波形。
route文件,postmap文件(这个一般是打平的flattern文件)的具体格式就不方便给出样例了,这个每家FPGA的格式会有所不同。
三:FPGA芯片验证方法
我们设计出来的FPGA芯片,是否能满足预期。我们要保证我们提供给用户的FPGA上每一个器件的功能是符合预期的,也就是单个tile的功能,以及Tile之间的连接,整体芯片的逻辑是完全正常的。
所以,我们需要针对单Tile,多Tile,fullchip做位流验证。方法就是使用上面的位流验证平台。针对这三种方法,我们分别来说明,说明应该如何设计样例 ,来达成相应的结果。
3.1:单Tile位流验证
对于每一种Tile做单独的测试(所有的Primitive),比如:CLB(LUT,CLA,DFF,SFTR……),CMT,IO,DSP,BRAM,EMRAM,FIFO,OBUF,IBUF,IOBUF,……
并且也包括Tile内部的连接信号路由的验证。
我们以CLB为例:
对于CLB,里面主要涉及不同的LUT,DFF,CLA,SFTR,需要设计一系列的用例,去覆盖相关的器件使用。
我们以最简单的 Lut2为例,来看看如何构建测试用例,参考用例,激励数据,结果比较。
3.1.1:CLB —— LUT2
首先,我们要了解Lut2的原理,它是由2输入1输出组成,可以完成 2个单bit输入1个输出的任何逻辑。比如:a & b,a | b , a xor b。
一般来说,厂商会提供 LUT2的Primitive IP,我们假如提供的IP就叫做 YY_LUT2。
那么,对于Dut,那就很简单了。注意 a & b 的真值 表是 1000b 也就是 h8
Dut的设计代码:
moudule And2(
input a,
input b,
output result
);
YY_LUT2 #(.INIT(4'h8)) lut2(.Io(a), .I1(b), .O(result));
end module
将该代码使用 eda flow 执行,生成 bitstream。
我们来看一下参考用例的写法:
module RefModel(input A,
input B,
output Y);
assign Y = A & B ; // 参考模型的布尔函数
endmodule
以上的代码,仿真工具在运行时,会在每个仿真周期,根据输入数据的变化来重新输出结果。对应的逻辑表达式的运算是在仿真器中执行(实际上就是语法解析,然后交给CPU来执行了)。
当然,你的示例还可以是 A | B,A xor B,若干的样例。
另外:
- INIT=0 IS_C_INVERTED = 0 IS_CLR_INVERTED=1 可以验证 DFFCE 的功能
- 1bit加,1bit 减 可以验证CLA功能
- wclk极性不反转 验证 SFTR32
- 将INIT、IC_C_INVERTED、IS_PRE(CLR/R/S/)_INVERTED的值进行随机,inst约束到FFA0 验证 LPQCE
3.1.2:DSP
我们再以DSP为例,选择合适的用例(针对DSPX18 的 2个输入乘法)
直接调用 YY_DSP48_CPLX18 ,具体就不写了。
具体如何设置用例,需要根据DSP48_CPLX18的输入参数来设置,尽量保证相关的参数可覆盖。
3.2:多Tile位流验证
需要验证 Tile间路由的正确性,在多个tile分配资源,全局信号,时钟可以正常共享,跨tile的优化结果符合预期。主要是对一组tile,某个功能区域进行验证。验证多个tile的功能及其之间的交互。可以用来验证协同工作,比如:DSP 与 BRAM的协同工作情况,验证范围有限,无法覆盖全芯片的全局资源,比如:时钟网络,全局布线等。
需要构造如下用例:
- 长路径信号传输:
- Tile间级联:多个tile之间的逻辑级联(比如:多级加法器)
- Tile内外通信:在不同Tile实现的模块之间进行通信(如:FIFO,AXI总线)
3.3:全芯片位流验证
对于全局资源(如:时钟,全局线)的使用进行验证。关注边界行为(I/O引脚,DDR,PCIe)。
需要构建的用例 :
- 设计一条覆盖尽可能多Tile的信号路径,从一个边界Tile传播到对角线另一侧。
- 在设计中启动全局时钟。
- 包含外部接口(I/O,DDR,PCIe)在设计中。
选用的用例,一般会是一些经典的电路设计,比如:FPGA一般会和闪存配合使用,那我们就选用一些标闪的闪存器件来搭建用例(因为闪存用例是有公开的行为模型和仿真库的)
因为是针对整个芯片的测试,所以,激励数据,应该采用jtag方式输入,涉及到PRAM的操作。相对比较复杂,这里不再展开细说。
3.4:代码的测试覆盖率
测试是否有效,主要看覆盖率,所以,在仿真运行中输出测试覆盖率,是非常重要的指标。
覆盖率报告通常包括以下信息:
-
总体覆盖率:
-
显示整个设计的总体覆盖率(如行覆盖率、分支覆盖率等)。
-
-
模块级覆盖率:
-
显示每个模块的覆盖率详情。
-
-
代码行覆盖率:
-
显示每一行代码是否被执行。
-
-
分支覆盖率:
-
显示每个分支是否被覆盖。
-
-
条件覆盖率:
-
显示每个布尔表达式的条件组合是否被覆盖。
-
-
状态机覆盖率:
-
显示状态机的所有状态和状态转换是否被覆盖。
-
-
翻转覆盖率:
-
显示信号是否发生了 0 到 1 或 1 到 0 的翻转。
-
代码覆盖度能够查看测试的完整度。是非常重要的参考指标。
最后我们来看看,要搭建一个自动化的验证平台,需要做一些什么样的工作:
这部分是一个封装动作,和验证本身的关系并不是很大,但如果对验证的细节不清楚,也不能搭建一个好的,可扩展的平台,因为平台的功能也不是一成不变的,需要有一个演进的过程。如果有好的设计,自然最佳。
四:位流验证的自动化平台
4.1:平台架构
这软件EDA的位流验证的架构。如果验证是上板验证(生产测试中心)。那位流的运行就不是在仿真环境,而是在板上的物理芯片运行,然后想办法把物理芯片的运行结果,按时序取回,然后进行对比。
4.2:资源管理
需要管理的资源:
- 平台的代码
主要是流程控制的代码,Python,Shell脚本,主要是存放到Git中,应该需要管理版本,
一般会是解释性脚本语言,不会有编译/构建的过程。
- 测试工程
每一次回归测试,我们定义为一个工程。而工程会描述所有运行的输入,输出。
工程的定义:针对某个芯片,某种验证方式,某个器件的测试,我们认为是一个验证工程。
待验证芯片——芯片的系列(架构),具体输出的芯片版本号(锁定的版本)
待验证的内容—— FullChip,或者 TILE_CLB_LUT2,或者 BRAM & DSP 的联合工程。
对应的仿真库——不同验证点,需要提供不同的仿真库,比如 fullchip和单tile需要的仿真库是不同的。但相对固定,基本上是提前准备好的。但是如果芯片的架构发生变化,需要做相应的更换。也可以理解芯片系列的版本发生变化。
对应的待测式DUT——待测试的DUT的测试用例。
DUT 对应的RefModel—— 对应的参考模型。
相关的配置信息——可能存在一些配置,可能待测内容不同,配置会有所不同。略
输出目录——包括位流输出目录(包括一些中间文件输出),最终输出目录。
- DUT Testbench
每个待测工程,都需要有一个TestBench 代码,TestBench 用来定义整个测试的过程。包括如何使用生成的位流进行仿真运行,如何调用RefModel(Golden case)获得结果,两者如何进行比较,如何输出。
注意,不同测试工程,可能代码会有所不同。
- 测试用例
测试用例是针对芯片待测单元的调用。可能需要覆盖待测单元的大多数输入组合。这些用例需要进行统一管理,在需要时,在测试工程中配置。
比如:针对 LUT1的测试用例 (只需要初始化一下真值表即可)
YY_LUT1 # ( INIT (2'h3) inst_lut1() // o = a
- 参考模型代码
针对测试用例,与之对应的另外一种实现(不依赖于FPGA),直接在VCS中运行。如果需要考虑时序,该模型代码需要打拍。
比如:assign o = a 就是针对上面的LUT1用例的Golden case。
- 其它配置
配置EDA工具的运行环境,这个是可以按需配置,因为对于EDA,研发每天都会有新的版本。
4.3:调度平台
因为需要自动化,必须要有调度,这样可以定时自动执行。
可以通过脚本控制执行的入口和过程,控制超时时间,控制运行的队列(串行还是并行),可以将大任务发到计算集群。
调度很简单,一般使用 Jekins的 Pipeline即可,Jekins支持Shell,可以进行任务的编排。
4.4:位流生成
位流生成,就是使用j最新EDA和相应的flow tcl ,用来生成位流。
位流生成需要注意几个点:
可能会添加固定的物理约束,简化输出,明确实际的 IOPAD。
4.5:仿真运行
对于test bencch 运行,会有多个步骤。
- 激励数据的处理
这里要看一下,是否需要形成有针对性的激励数据。可能和位置转换有关(实际位置可以通过位流输出的 instance和 route信息,获取实际的位置信息)。
- test bench 代码编译
针对 test bench代码,建议采用makefile进行编译/构建,这样比较容易针对VCS进行动态链接和引用,也定义输出的动作。
- test bech 仿真运行
使用仿真平台,输入激励,进行仿真,输出波形,输出代码覆盖度。
对Golden Case 进行运行,输入激励,执行输出。
两者进行比较,在仿真log中输出
4.6:结果输出
收集结果,确认是否成功,输出最终报告。
可能还会输出一些必要的数据,波形,代码覆盖度之类。
五:其它
5.1:硬件设计验证
对于硬件部的位流验证,实际上基本上是一致的。不同之处主要有:
1:在EDA的位流生成功能不成熟前,需要有其它方式生成位流。
2:硬件验证不通过的情况,会关注硬件本身的设计,而不是位流的生成。关注点会有所不同。
5.2:生产测试中心位流验证
对于生产测试中心,需要实际上板测试。所以。
1:位流需要download到实际芯片上运行,激励需要实际输入到芯片。
2:参考模型与软件一样,但注意,大多数时候要考虑时序,而不是简单的组合逻辑。
3:对于实际芯片的运行输入/输出,一般需要做特殊处理,比如:设计专用电路,完成激励输入,设计驱动程序将输出数据写入本地磁盘,然后通过磁盘文件获取结果,进行比较。
输出的文件需要表达时序信息。
4:生产测试发现问题,关注的是硬件的可靠性。
大概就是这样了,对于仿真的实际运行的细节和实际的硬件相关,有空再说。