| ||
几乎每个规模不小的设计里都包含至少一个状态机,并且通过其合法状态、状态转换以及状态转换的各种原因来行使该状态机是验证设计功能的关键。在某些情况下,我们可以简单地使用状态机作为对设计执行正常操作的副产品。在其他情况下,状态机可能非常复杂,以至于我们必须采取明确的针对性步骤来有效地行使状态机。在本文中,我们将了解inFact的系统性激励生成和生成约束感知功能覆盖范围的能力如何通过生成命令序列来简化执行状态机的过程。
本文使用的示例是LPDDR SDRAM存储器的状态机。下图显示了一个通用的略微简化的LPDDR状态转换图,其中包含引起这些转换的相关状态和命令。
这个状态机看似简单。但正如我们看到的,执行所有有效的三层命令序列绝非易事!许多复杂性来自于这样一个事实,我们不仅需要执行状态机,我们还需要确保设计处于可以执行状态机转换的状态。
图1- LPDDR的状态机
我们可以通过几种方式来编写激励程序来生成LPDDR命令,以执行存储设备的状态机。我们可以编写一个表示单个LPDDR命令的事务,而不尝试定位命令序列。在另一个极端,我们可以编写一组定向测试,以尝试在状态机中进行每个可能的转换。我们将采用略有不同的路径,并捕获将设备状态与三个命令中的每个命令相关联的约束。这将使我们能够使用inFact的系统激励来高效地执行所有有效的命令序列。
像许多其他状态一样,LPDDR状态机也受设备状态限制。例如,为了在一个存储体上执行写操作(WR_x),该存储体必须处于激活状态。我们的首要任务是识别设备的状态,这些设备的状态决定了可以在任意给定时间点上应用有效命令。
使用LPDDR,需要注意两个状态元素:
设备是否处于自刷新状态
给定的存储体是否处于激活状态
我们从获取结构中的这些状态元素开始,如图2所示。请注意,在这个LPDDR存储器中,有八个存储体。
typedef struct { rand bit[3:0] bank_active[8]; rand bit refresh; } cmd_state_s; |
图2 - LPDDR状态信息
我们将使用此状态来调节接下来可以生成的命令集。
typedef enum { PREA, SRE, SRX, REFA, ACT_0, ACT_1, ACT_2, ACT_3, ACT_4, ACT_5, ACT_6, ACT_7, WR_0, WR_1, WR_2, WR_3, WR_4, WR_5, WR_6, WR_7, WRA_0, WRA_1, WRA_2, WRA_3, WRA_4, WRA_5, WRA_6, WRA_7, RD_0, RD_1, RD_2, RD_3, RD_4, RD_5, RD_6, RD_7, RDA_0, RDA_1, RDA_2, RDA_3, RDA_4, RDA_5, RDA_6, RDA_7, PRE_0, PRE_1, PRE_2, PRE_3, PRE_4, PRE_5, PRE_6, PRE_7, REF_0, REF_1, REF_2, REF_3, REF_4, REF_5, REF_6, REF_7 } lpddr_cmd_e; |
图3 - LPDDR指令集
接下来,我们获取用一个类来描述单一命令所需的一切,如图4所示。
class lpddr4_cmd; rand lpddr4_cmd_e cmd; rand int target_bank; rand cmd_state_s state; // . . .
endclass |
图4 - 单一命令类
单一命令类获取在执行命令之前的设备状态(状态字段)、要生成的命令以及要对其应用命令的目标存储区(如果适用)。
现在,我们需要形成约束,以反映在状态机中表达的规则作为对当前状态的约束。
constraint refresh_c { // When in self-refresh mode, the only thing we can // do is to exit self-refresh mode. When not in // self-refresh mode, we cannot exit self-refresh mode if (state.refresh) { // SRE stays in self-refresh mode. SRX exits cmd inside {SRE, SRX}; } else { cmd != SRX; }
(state.bank_active.sum() != 0) -> cmd != SRE;
}
|
图5 - 自刷新的约束
上面的图5显示了在空闲状态和自刷新状态之间对弧进行编码所需的约束。请注意,处于自刷新状态时,我们唯一可以做的就是保持该状态或退出自刷新状态。当我们不处于刷新状态时,我们将无法发出自刷新退出的命令。最后,除非所有存储体都处于空闲状态,否则我们将无法发出自刷新命令。
有一组类似的约束来控制何时可以发出特定存储体的命令,如图6所示。
constraint bank_cmd_c { if (cmd inside {PREA, SRE, SRX, REFA}) { target_bank == -1; } else { target_bank inside {[0:7]};
// Restrict possible commands based on bank state foreach (state.bank_active[i]) { if (i == target_bank) { if (state.bank_active[i]) { // The only things we cannot do when the bank // is active is to activate the bank or refresh cmd == (ACT_0+i || WR_0+i || WRA_0+i || RD_0+i || RDA_0+i || PRE_0+i); } else { // In the idle state, the only thing we can do // is to activate the bank or refresh a bank cmd == (ACT_0+i || REF_0+i); } } } } } |
图6 - 特定存储体命令的约束
这里,约束主要确保仅在激活适当的存储体时才能发出特定命令。同样,在这里,我们可以将这些限制追溯到图1所示的状态机描述的规则。
之前那些只是第一步。现在我们需要获取围绕命令序列的规则。如本文开头所述,我们的目标是生成三个命令的序列。因此,我们定义了基于上一节中介绍的命令类的一个三层深度数组。
class lpddr_cmd_seq; parameter int unsigned N_CMDS = 3;
rand lpddr_cmd cmds[N_CMDS]; cmd_state_s prev_state;
function new(); foreach (cmds[i]) begin cmds[i] = new(); end endfunction // . . .
endclass |
图7 - 命令序列类
请注意,我们还获取了先前的状态,该状态将是序列的最后一条命令执行后的设备状态(或初始状态)。
// Constraints to relate th sub-commands constraint prop_bank_state_c { foreach (cmds[i]) { if (i == 0) { // Pull the previous state cmds[i].state.refresh == prev_state.refresh; foreach (cmds[i].state.bank_active[j]) { cmds[i].state.bank_active[j] == prev_state.bank_active[j]; } } else { // Look back and determine how the // last commands impacts current state if (cmds[i-1].cmd == SRE) { cmds[i].state.refresh == 1; } else { cmds[i].state.refresh == 0; }
if (cmds[i-1].cmd inside {PREA, SRE, SRX, REFA}) { // a PREA command deactivates all banks // Other global commands require banks to be inactive foreach (cmds[i].state.bank_active[j]) { cmds[i].state.bank_active[j] == 0; } } else { foreach (cmds[i].state.bank_active[j]) { if (cmds[i-1].cmd == ACT_0+j) { // A previous bank-activate command causes // the current bank state to be 1 cmds[i].state.bank_active[j] == 1; } else if ( cmds[i-1].cmd == WRA_0+j || cmds[i-1].cmd == RDA_0+j || cmds[i-1].cmd == PRE_0+j) { // A RD/WR with auto-precharge deactivates the bank // An explicit precharge deactivates the bank cmds[i].state.bank_active[j] == 0; } else { // Other bank-specific cmds[i].state.bank_active[j] == cmds[i-1].state.bank_active[j]; } } } } } } |
图8 - 状态转移约束
现在,我们已经定义了用于生成三个深度的LPDDR命令序列的激励模型。我们将回到如何使用inFact有效地生成这些命令序列的方式。
除了产生有效的激励,我们还希望能够收集覆盖率。由于有效命令序列的状态之间的依赖性以及命令之间的约束,因此很难为这样的命令序列生成覆盖率。幸运的是,inFact为我们提供了一种方法,既可以轻松指定这些覆盖率目标,又可以根据此覆盖率规范生成Systemverilog覆盖率模型。
第一步是捕获覆盖率目标。图9显示了一个以电子表格形式显示的CSV(逗号分隔值)文件,该文件描述了我们的三个深度命令序列的覆盖率目标。
图9 - 覆盖率定义文件
我们的覆盖率规范仅需要获取我们关心的目标:三深度的命令序列数组中的三个命令之间的交叉覆盖。
qcc lpddr_cmd_seq_pkg::lpddr4_cmd_seq \ -coverage-strategy app_lpddr_cmd_seq_cov.csv \ -name app_lpddr_cmd_seq_cov \ -o app_lpddr_cmd_seq_cov.svh
|
图10 - 生成功能覆盖率
上面的图10展示了用于生成SystemVerilog覆盖组的inFact命令。 inFact利用CSV文件中获取的覆盖率目标以及SystemVerilog类中获取的约束来生成SystemVerilog覆盖组,该覆盖组可准确获取可达组合实例并排除不可达组合实例。
使用inFact的自动化功能创建功能覆盖的一大好处是,inFact将根据约束条件自动计算不可达组合实例的解决方案,并生成排除集合以排除这些不可达实例。如图11所示,这种情况下的排除集合既广泛又复杂。当然不是人工能轻易完成的东西!
图11 - 命令序列覆盖率的排除集
除了生成代码以排除不可达的组合之外,inFact还报告可达的组合的数量,如图12所示。正如预期的那样,每个单独命令的所有60个变体都是完全可达的。由于约束,直观的216,000个命令序列组合中只有187,845个是可到达的。我们需要执行大量的命令序列!
// Coverpoint cmd_0 cmd_0 : coverpoint item.cmd[0].cmd { option.weight = 60; bins lpddr4_cmd_seq_inst_cmds_0_cmd[] = {[lpddr4_cmd_seq_pkg::PREA:lpddr4_cmd_seq_pkg::REF_7]}; } // Coverpoint cmd_1 cmd_1 : coverpoint item.cmd[1].cmd { option.weight = 60; bins lpddr4_cmd_seq_inst_cmds_1_cmd[] = {[lpddr4_cmd_seq_pkg::PREA:lpddr4_cmd_seq_pkg::REF_7]}; } // Coverpoint cmd_2 cmd_2 : coverpoint item.cmd[2].cmd { option.weight = 60; bins lpddr4_cmd_seq_inst_cmds_2_cmd[] = {[lpddr4_cmd_seq_pkg::PREA:lpddr4_cmd_seq_pkg::REF_7]}; } // Cross_cmd_cross cmd_cross : cross cmd_0, cmd_1, cmd_2 { option.weight = 187845; |
图12 - 命令序列可到达的组合
为了将inFact自动化的命令序列生成器集成到测试平台中,我们需要做两件事:使用inFact创建激励生成器类,并将该类集成到UVM序列中(假设我们在测试平台中使用UVM)。
qso lpddr_cmd_seq_pkg::lpddr_cmd_seq \ -coverage-strategy app_lpddr_cmd_seq_cov.csv \ -o lpddr_cmd_seq_gen.svh
|
图13 - 激励生成类创建命令
上面的图13展示了inFact命令,该命令读取包含命令和约束数组以及覆盖策略CSV文件的命令序列类,并生成一个类以允许inFact有效地生成所有可到达的命令序列组合。
class lpddr_cmdseq_seq extends lpddr_seq;
function new();
endfunction
task body(); lpddr_cmd_seq cmd_seq = lpddr_cmd_seq::type_id::create(); lpddr_cmd_seq_gen cmd_gen = new({get_full_name(), ".cmd_gen"});
repeat (10000) begin // Call inFact to generate three commands cmd_gen.ifc_fill(cmd_seq);
// Execute the three commands foreach (cmd_seq.cmds[i]) begin run_cmd(cmd_seq.cmds[i]); end end endtask
endclass
|
图14 - 集成命令序列生成器
现在,我们当然需要将命令序列生成器和UVM测试平台相连接。这里唯一的挑战是我们的命令生成器一次生成三个命令,而我们的测试台一次只接受一个命令。如图14所示,我们采用的集成方法是一次生成三个命令,然后通过“ run_cmd”任务将它们逐一应用到测试平台上。
请注意,我们的序列运行了10,000个命令序列(30,000个命令)的循环。这意味着我们将需要运行很多次仿真才能达到命令序列覆盖的目标。幸运的是,inFact提供了一种回归模式,在该模式下,每次仿真都朝着总体覆盖目标产生与众不同的进程。
命令序列是进行顺序设计的绝佳方法。尽管有几种方法可以生成命令序列,但是通过获取设计状态和有效的下一个命令之间的关系作为约束条件,可以允许使用自动化来辅助。使用此基于约束的描述,inFact能够生成SystemVerilog的覆盖组,该覆盖组准确地获取可到达的命令序列,并使我们能够高效而系统地生成所有感兴趣的命令序列。
pls. add系列回顾:
使用SystemVerilog使状态机的运行更加容易
扫描上图二维码可直达课程页面,马上试听
往期精彩:
路科发布| 稳中带涨!25w成芯片校招薪资平均底!2020应届秋招数据全面分析!
理解UVM-1.2到IEEE1800.2的变化,掌握这3点就够