☰
异步FIFO验证与UVM环境搭建:格雷码、scoreboard与笔试实践
2026/10/19 3:13:31 网站建设 项目流程

简介:思朗科技2022届提前批数字IC验证岗位真实笔试题,完整呈现异步FIFO的UVM验证环境搭建、激励发送、覆盖率收集与错误点分析全过程,适合2023届及后续目标进入IC验证领域的求职者作为笔试复盘参考。压缩包共193个文件、约5.13MB,以SV与Verilog源码、覆盖率报告网页、Questa Sim工程配置及说明文档为主,目录结构清晰,便于按模块检索学习。已有6226人浏览学习。UVM环境源码位于异步FIFO工程目录下,分为输入与输出两个包:输入包包含事务类(32位动态数组)、驱动激励发送、输入端口监测、序列发生器中转以及代理容器装配;输出包负责输出端口监测。异步FIFO涉及跨时钟域读写与空满判断,该环境围绕输入输出端口分别搭建,可覆盖主要功能点,配合仿真工具能复现覆盖率报告与错误点反馈,是理解UVM验证方法学的完整样例。

1. 数字IC验证笔试:为什么异步FIFO和UVM总是成对出现

这几年数字IC验证岗位的笔试和面试里,异步FIFO几乎成了默认的试金石。它既是设计题,又是验证题,还是UVM场景题。一个题目下来,既考你对跨时钟域的理解,又考你能不能把格雷码指针讲清楚,还会追问你怎么在UVM环境里搭组件、怎么测满空边界。很多人觉得异步FIFO太小,不值得花精力,结果一到笔试就露馅:RTL写得出来,UVM环境无从下手;UVM环境搭得出来,又问不明白scoreboard怎么对数据。这篇文章想讲的,不是再看一遍异步FIFO的原理,而是站在验证工程师的视角,把从设计理解、UVM环境搭建、测试用例设计到笔试作答这一条完整路径说清楚,给你一份能让面试官觉得“这人是真做过验证”的实践笔记。

2. 异步FIFO的设计边界:验证之前先看透空满判断与同步器

很多验证新手拿到异步FIFO,第一反应是“看代码,写driver,比对数据”。实际上,异步FIFO验证的难点不在数据通路,而在控制逻辑:空满标志、格雷码、两级同步器、读写域之间的配合。你如果只是按“写进去再读出来”的思路测,测一百轮也测不出真正的问题。所以真正动手搭UVM环境之前,得先把异步FIFO的边界条件拆清楚,否则后面写scoreboard、写覆盖率模型时没有依据。

2.1 格雷码指针:为什么写成“异步”仍能保证判空判满正确

异步FIFO的经典结构是双端口RAM加读写指针。写指针由写时钟域控制,读指针由读时钟域控制。但问题是:判断“空”的时候,读时钟域需要知道写指针的位置;判断“满”的时候,写时钟域需要知道读指针的位置。指针从一边传到另一边,跨时钟域采样必然有延迟,怎么还能保证功能正确?核心就是格雷码。

格雷码相邻两个计数之间每次只有一位变化,所以当写指针从格雷码的某个值变到下一个值时,读时钟域即使采到旧值,也最多落后一个采样周期,不可能采到一个“跳变中的中间值”。这就是为什么同步器里采格雷码比采二进制码安全得多。验证时你要特别留意每个模块里有没有把格雷码转回二进制码的地方,以及空满判断是拿“同步过来的格雷码指针”和“本地指针”比较,还是拿转换后的二进制码比较。两者结果不一样,但很多RTL设计里写法非常隐晦。

空满判断还有个细节:空信号一般在读时钟域产生,判断条件是读指针同步了写指针后,两者相等;满信号一般在写时钟域产生,判断条件是写指针同步了读指针后,两者格雷码的MSB相反,其余位相同。为什么要MSB相反?因为FIFO深度是2的幂时,指针回绕后MSB会翻转,如果只比较普通二进制相等,就无法区分“空”和“满”。这个点几乎每次笔试都会考,你在验证环境里也一定要把这部分当作观察对象,不光是看数据对不对,还要盯着空满信号在哪个周期拉高、在哪个周期拉低。

2.2 空满信号的产生位置决定了UVM scoreboard怎么写

验证环境里,scoreboard最常犯的错误是把“数据比对”和“控制信号比对”混在一起做。异步FIFO的建议是分开:数据比对只管写入数据的队列和读出数据的流是否一致,控制信号比对单独做。为什么?因为读出的数据经过一个周期的延迟,而空满信号的变化节点有它自己的时序,把它们揉在同一个check里,很容易因为一拍偏差误报。

空满信号的产生位置也需要一个明确约定。我一般会先看RTL确认这几个问题:

  • 空标志是组合逻辑输出还是寄存器输出?
  • 满标志在写时钟域产生,是不是在“写使能且未满”的下一拍才会变化?
  • 读指针同步到写时钟域用了两级寄存器,同步后的指针在满判断参与比较时要不要额外加一拍?

这些信息对你的UVM monitor采样很有用。有的RTL里空信号在“读请求到来”的同一拍拉高,有的要下一拍才拉高;你的monitor写入事务、读事务的采样时刻一定要和这个节奏对齐,否则会产生大量假失败。我见过不少验证环境跑仿真,scoreboard报数据不匹配,查到最后是空标志时序没对齐,不是设计的问题。

2.3 用最小RTL样例校准你对FIFO行为的预期

一种可靠的验证起步方式是:先把异步FIFO的RTL拉出来,单独写一个极简的仿真testbench,不经过UVM,用手工激励确认其行为。这样做不是为了重复劳动,而是让你在搭UVM之前先建立一个“行为基准”:深度为N时写满需要多少拍;连续写完立即读,第一个有效数据在多少拍后出现;复位后空标志是立即拉高还是两个时钟周期后才拉高。有了这些基准,你在UVM里跑大规模随机时,报错了才知道是激励问题还是DUT问题。

常见的最小RTL验证脚本会在顶层把写时钟和读时钟用两个频率和相位完全独立的clock生成器驱动。这里有个关键:时钟不能写在同一个always块里分频产生,那样会让读时钟和写时钟的相位产生固定关系,掩盖掉真正异步场景下的问题。我一般用两个独立的clocking block或者两个initial块分别生成,而且频率比值最好选一个带小数关系的组合,比如写时钟100MHz、读时钟66.667MHz。

// 极简异步FIFO行为校准testbench片段 reg wclk, rclk; initial begin wclk = 0; forever #5 wclk = ~wclk; // 100MHz写时钟 end initial begin rclk = 0; forever #7.5 rclk = ~rclk; // ~66.67MHz读时钟,与写时钟无整数倍关系 end initial begin // 复位 rst_n = 0; #20 rst_n = 1; // 连续写入深度个数据 repeat (FIFO_DEPTH) begin @(posedge wclk); winc = 1; wdata = $random; end winc = 0; // 观察满信号应在最后一次写请求后1~2拍内拉高 end

逻辑说明:这段代码只做一件事,确认“写满时满信号的拉高时刻”和“复位释放后空信号的行为”。时钟频率故意不取整数倍数,是为了让两个域之间的相位差随机化,而不是固定在每个周期同一位置翻转。参数说明:FIFO_DEPTH是异步FIFO深度,$random用于产生随机写数据;5ns和7.5ns的周期比对应写快、读慢的场景。如果你发现满信号在写请求停止两拍以上才拉高,说明RTL里满信号可能有额外延迟,后续UVM环境里的采样时机就要相应调整。

3. 搭一套针对异步FIFO的最小UVM环境:组件划分与代码骨架

对异步FIFO来说,UVM环境不需要搞得特别庞大,但组件不能少。一个标准做法是:写agent和读agent分开,各自有driver、monitor和sequencer;scoreboard通过两个analysis port分别接收写侧和读侧的monitor数据;reference model单独一个小类做数据预测。如果你把读写事务放在同一个agent里,一旦两个时钟域的事件交错,事务粒度会非常混乱,这是新手最容易踩的坑。

3.1 接口与transaction:把跨时钟域的握手写进item里

接口设计上,建议把写侧接口和读侧接口拆成两个interface,不要放在同一个interface里用modport硬切。异步FIFO的写侧信号是wclk、winc、wdata、wfull,读侧信号是rclk、rinc、rdata、rempty。两个时钟域的信号放在同一个interface里,虽然语法上能通过,但仿真时如果你在driver里用@(posedge wclk)采样rclk相关信号,就会产生隐式跨时钟域访问,行为上有可能在事件调度时出现意外。

transaction的字段设计也要体现两个域的区别:写事务可以叫w_trans,读事务叫r_trans。严谨的做法是给transaction定义source_domain字段,值为WRITE或READ,这样scoreboard收到事务后可以按域分流。

class fifo_item extends uvm_sequence_item; rand bit [DATA_WIDTH-1:0] data; rand bit inc; bit [ADDR_WIDTH-1:0] ptr_value; bit domain_id; // 0写域 1读域 bit empty_flag; bit full_flag; `uvm_object_utils_begin(fifo_item) `uvm_field_int(data, UVM_ALL_ON) `uvm_field_int(inc, UVM_ALL_ON) `uvm_field_int(domain_id, UVM_ALL_ON) `uvm_field_int(ptr_value, UVM_ALL_ON) `uvm_object_utils_end endclass

逻辑说明:这个transaction里把数据、使能、指针值、域标识都放进去,是为了让scoreboard能重建一个完整的读写事件。domain_id字段是核心——scoreboard必须根据它判断一个事务应该进入写入队列还是读取队列,不能靠data是否有效去猜。参数说明:DATA_WIDTH是FIFO位宽,ADDR_WIDTH是深度对应的地址宽度,empty_flag和full_flag这两个字段我建议也加上,方便scoreboard把“数据流比对”和“满空标志比对”分开做,因为这两种检查发生的时机不同,后面调试时分类会更快。

3.2 driver/monitor/scoreboard:谁负责采数、谁负责判空满

driver的作用只有一个:按接口协议把事务里的data和inc驱动到DUT上。写driver只在wclk上升沿驱动winc和wdata;读driver只在rclk上升沿驱动rinc。monitor的工作是采样DUT的输出,包括rdata、wfull、rempty。注意:monitor不能直接采样driver驱动的信号,应该采DUT端口上的信号,否则你会漏掉DUT内部对信号的延迟和修正。

scoreboard是异步FIFO验证里最容易写复杂的地方。建议分三块:预测队列、数据比较器、控制信号比较器。预测队列里存写入侧发过来的所有有效数据;数据比较器在从DUT采样到rdata有效时,从队列头部取数比较;控制信号比较器单独记录wfull和rempty的变化周期,和参考模型里的预期值逐拍比对。这三块之间不要共享状态,否则数据比较出错时你无法区分是队列问题还是控制信号问题。

class fifo_scoreboard extends uvm_scoreboard; fifo_item expected_queue[$]; // 期望数据队列 uvm_analysis_imp #(fifo_item, fifo_scoreboard) write_imp; uvm_analysis_imp #(fifo_item, fifo_scoreboard) read_imp; function void write_analysis(fifo_item item); if (item.inc) begin expected_queue.push_back(item); end endfunction function void read_analysis(fifo_item item); fifo_item exp; if (item.empty_flag == 0 && item.rdata_valid) begin exp = expected_queue.pop_front(); if (exp.data !== item.data) `uvm_error("SCB_DATA", $sformatf("data mismatch exp=%0h act=%0h", exp.data, item.data)) end endfunction endclass

逻辑说明:write_analysis在收到写侧monitor发来的事务后,只要写使能有效就入队;read_analysis收到读侧monitor发来的事务后,判断空标志为低且数据有效时出队比较。这里有两个关键参数化决策:一是rdata_valid这个字段需要monitor在采样时根据当前拍空标志和读使能计算出来,不能直接拿rdata有无变化判断;二是expected_queue用队列结构天然模拟了FIFO的先进先出语义。如果你把队列里的数据内容和指针对应起来检查,还能发现更深层的指针错位问题,但那个属于进阶用法,笔试阶段不一定需要。

3.3 sequencer和env连接:笔试卷上的“组件连接图”落到代码

很多人笔试画UVM结构图画得头头是道,一到写代码就乱。原因是只记了结构图,没记连接的本质。异步FIFO的UVM环境连接核心是两条analysis path:写monitor到scoreboard的write_imp,读monitor到scoreboard的read_imp。两条路径必须独立,不能在中间加一个全局fifo把两个域的事务混在一起再分发。

env里的连接代码,我习惯在connect_phase写清楚每个端口的连接。一个常见失误是忘了把monitor的analysis_port和scoreboard的analysis_imp连接,导致仿真跑完scoreboard一个事务都没收到,还误以为FIFO功能全对。这里送你一个自查口诀:写agent的monitor连scoreboard的write入口,读agent的monitor连scoreboard的read入口,两个sequencer各自和对应的driver绑定,env层不做任何额外的事务转发。

function void fifo_env::connect_phase(uvm_phase phase); super.connect_phase(phase); // 写侧monitor -> scoreboard w_agent.monitor.item_port.connect(scoreboard.write_imp); // 读侧monitor -> scoreboard r_agent.monitor.item_port.connect(scoreboard.read_imp); // 写侧sequencer -> driver w_agent.sequencer.seq_item_port.connect(w_agent.driver.seq_item_export); // 读侧sequencer -> driver r_agent.sequencer.seq_item_port.connect(r_agent.driver.seq_item_export); endfunction

逻辑说明:四行连接代码对应四条通路,少一条仿真都会出现“信号悬空”或“比较器永远不触发”的现象。item_port和analysis_imp的连接必须类型匹配,否则编译期或运行期会报connect类型错误。经验是建议monitor里的item_port声明为uvm_analysis_port#(fifo_item),scoreboard端声明为uvm_analysis_imp#(fifo_item, fifo_scoreboard),这样UVM基类会自动处理广播机制,不需要手写for_each循环。

4. 用UVM验证异步FIFO:四类测试用例与覆盖率闭环

环境搭完只是第一步。异步FIFO的验证价值全在测试用例设计上。笔试时面试官最常问的也是“你打算测哪些场景”。如果你的回答只是“随机读写跑通”,基本等于没做过验证。按我的经验,异步FIFO最少要有四类用例:满空边界、水位深度、复位、读写停顿。四类用例背后对应着四个最容易被忽视的设计缺陷点。

4.1 满空边界与水位深度:直接对应笔试的“最小FIFO深度”计算

满空边界用例要做的不是简单地把FIFO写满,而是精确控制写入次数等于深度、深度减一、深度加一这三档。因为RTL里的满信号通常会在“写请求且当前未满”的下一拍拉高,深度加一这一档用来确认“FIFO并不会真的多存一个数据”。深度减一则用来确认“满信号不会过早拉高导致写侧误停”。

笔试里常见的最小FIFO深度计算题,本质上就是问:写侧突发长度N,读侧每M拍读一个,中间没有反压时深度至少多大。这种题的坑在于读写时钟频率比不是整数,绝大部分人在笔试时直接用整数除法,结果深度差一拍。验证时你要在用例里把这类场景原样构造出来,用burst_length参数化,跑完后看有没有未预期的满标志。这一条用例本身就能当作笔试答案,既显示了你会算,又显示了你会验。

水位深度用例的核心是检查FIFO在非满非空状态下的数据完整性。很多人只测“写满再读空”,但真实场景是写读交替、写快读慢、读快写慢,这些都是不同的水位走向。我建议用constraint控制读写事务的间隔分布,让水位在2%~98%之间来回摆动,而不是一下打满。

4.2 复位与读停写停:验证计划里最容易被忽略的用例

异步复位对FIFO的影响容易被高估——大家默认复位后空标志为高、读指针归零、输出无效为主。但实际上有三个细节必须验证:

  • 复位释放时读写时钟相位不在上升沿怎么办?
  • 复位释放后立刻来写请求,第一笔数据能正确写入吗?
  • 复位释放后立刻来读请求,读输出会有一个周期的未知态(X)吗?

这些在UVM里都需要在sequence里显式控制,而不是随机到。另一个常被忽略的场景是读停写停:读侧连续100拍不发读请求,写侧连续50拍发一个数据,再读侧连续50拍狂读。这个场景用来验证深水位下的读写互相影响,特别是满信号拉高后,读侧开始消费,满信号释放的时机对不对。

class fifo_read_stall_seq extends uvm_sequence #(fifo_item); rand int stall_cycles; constraint c_stall { stall_cycles inside {[10:100]}; } task body(); fifo_item item; // 读停:连续不出读请求 repeat (stall_cycles) begin `uvm_do_on_with(item, p_sequencer.r_seqr, {inc == 0; domain_id == READ;}) end // 读猛拉:连续出读请求 repeat (stall_cycles) begin `uvm_do_on_with(item, p_sequencer.r_seqr, {inc == 1; domain_id == READ;}) end endtask endclass

逻辑说明:这个sequence做的事情是让读侧先完全停止,再连续读,配合写侧的固定速率写入,形成“读停—水位上涨—读拉—水位下降”的节奏。这里有两个参数要注意:stall_cycles约束在10到100之间是为了让水位有足够时间冲到较高位置;domain_id字段在sequence里也必须赋值,因为scoreboard分流靠它,如果忘了赋值,事务会被扔进错误的队列,报错时会让你误判设计有问题。

4.3 功能覆盖率模型:把“验证完备”变成一个可量化的数字

笔试聊到覆盖率,大多数候选人只会说“行覆盖率”“条件覆盖率”,极少有人说出异步FIFO场景的功能覆盖率点怎么设。其实异步FIFO的功能覆盖率点非常典型,我最常设的是五个:

  • 写满时满信号拉高的那一拍,读信号是否同时到来(满与读重叠)
  • 读空时空信号拉高的那一拍,写信号是否同时到来(空与写重叠)
  • 水位区间分布(0~25%,25~50%,50~75%,75~100%)
  • 指针格雷码回绕处(MSB翻转附近)
  • 复位释放后的首个读写请求时序
covergroup fifo_cg @(posedge wclk); cp_full_at_read: coverpoint (wfull && rinc); cp_water_level: coverpoint (fifo_level) { bins low = {[0:25]}; bins mid_low = {[26:50]}; bins mid_high = {[51:75]}; bins high = {[76:100]}; } cp_gray_wrap: coverpoint (wptr_gray) { bins wrap = {0, MEM_DEPTH}; // 关注指针回绕点 } endgroup

逻辑说明:这三个coverpoint分别对应满空重叠、水位分布、格雷码回绕。功能覆盖率的意义不是凑点,而是让验证报告能说清楚“我测到了什么、没测到什么”。如果你在回归报告里看到cp_full_at_read覆盖率为0,说明整个测试过程中没有一次“在满信号有效的同一拍发起读请求”的场景,这通常意味着激励约束太宽松,需要增加读写同时发生的随机概率。参数说明:fifo_level是FIFO内部实例化的深度计数器信号,可以通过interface的DPI或层次引用读到;MEM_DEPTH要按2的幂次设置,否则格雷码回绕点计算没意义。

5. 异步FIFO验证与笔试的高频坑:五条踩坑记录

我让这个章节单独立出来,是因为异步FIFO的坑不是我遇到的个别问题,而是整个验证社区里反复出现的高频踩坑点。笔试时踩一个可能只是少拿几分,仿真时踩一个可能导致你连续debug两三天。下面按“现象——原因——解决”的方式记录,直接照着你自己的项目和笔试复习去对。

5.1 两级同步器读到的指针过期:空满标志推迟一拍逻辑

现象:跑随机用例时,UVM环境里scoreboard偶现数据不匹配,报错位置集中在深度满边界附近,复现概率不定。把随机种子固定后,有时能复现有时复现不了。 原因:读侧同步写指针用了两级寄存器,同步后的指针比写时钟域真实指针晚两拍;满信号在写时钟域产生时,比较的是“同步后的读指针”,这个指针本身已经比真实读指针旧了,所以满信号会晚拉高。scoreboard里如果按真实写次数来预测满拉高时间,就会产生一拍偏差。 解决:在scoreboard的预测逻辑里显式模拟同步器延迟。建议构造一个“同步延迟模型”:参考模型里把写指针、读指针都通过一个两级寄存器模型打两拍后再参与满空比较。这样预测值和DUT行为一致,而不是拿理想值去对比有延迟的实现值。

5.2 用非对称深度估算容量:笔试算深度的常见翻车点

现象:笔试或面试手撕代码时,直接按“FIFO深度 = 突发长度”来答,被面试官追问“读写频率不一样怎么办”时答不上来。 原因:很多人把FIFO深度理解成了“能存多少个数”,忽视了它在异步场景下真正作用是“吸收突发写和突发读之间的速率差”。如果写侧连续写入B个数据,读侧每k个周期读一个,写侧必须多存下的数据量就是B减去这B个数据写入期间读侧消费掉的数量。这个数量依赖时钟比、读写时序,不是固定值。 解决:笔试时先画时间轴,标出每个写数据落下的时刻和每个读数据被取走的时刻,然后数“同一时刻写入量减读出量的最大值”。验证时就把这个最大值在用例里算出来,作为judgement标准,而不是人肉数。常见做法是写一个自动计算函数,遍历整个仿真时间轴,实时统计FIFO水位峰值,和深度做对比。

5.3 UVM中驱动异步复位:set_drain_time与wait_clock的冲突

现象:用例里加了一个复位场景后,回归测试大量出现“timing race”或“driver found 1’bx”类错误,而且复位后的首个数据总是不对齐。 原因:在UVM里,sequence通过driver驱动接口时序时,默认依赖虚拟时钟或interface的blocking方式。异步复位信号没有和时钟对齐,driver如果只等wclk上升沿来采样复位状态,复位释放时刻和上升沿之间的亚稳态被建模成了X态,monitor在X态采样rdata,scoreboard自然报错。 解决:建议在virtual interface里给复位信号单独做一个复位监视器(reset_monitor),在复位释放后产生一个reset_done事件,driver和monitor都等待这个事件而不是等固定的时钟。另外sequence里结束时要调用set_drain_time,给总线一个干净的“排空时间”,防止最后一个事务被截断。

5.4 格雷码单调性检查:规则比随机更值钱

现象:长时间回归测试不报错,但形式验证或代码评审时发现,格雷码指针在某些非2幂深度设置下会跳变出非单调值。 原因:格雷码只在“连续加1”时保证每次只翻转一位。如果RTL实现里为了支持非2幂深度或可变深度,做了“加1后跳到某个预置值”的逻辑,这个跳变不遵循格雷码邻接规则,跨时钟域采样就有风险。随机激励很难测出这种边界,因为概率太低。 解决:在UVM环境里单独加一个断言论(assertion):每两个相邻时钟周期比较指针格雷码,规定只能有1位不同或完全不变。这个断言跑在所有用例后面,能保证“无论在什么激励下,格雷码单调性都成立”。笔试面试提到这个点,面试官会明显感觉到你做过异步FIFO的完整验证闭环。

property p_gray_monotonic; @(posedge clk) disable iff (!rst_n) $onehot(wptr_gray ^ $past(wptr_gray)); endproperty assert property (p_gray_monotonic);

逻辑说明:这个断言检查是“当前格雷码和上一拍格雷码异或后,必须恰好只有1位为1”。如果RTL里出现非邻接跳变,这个断言会立刻报错,而不是等到数据比对阶段才发现。参数说明:$onehot是SystemVerilog内建函数,返回真表示操作数中有且仅有一位为高。如果你写的RTL里存在“指针按非连续方式跳变”的逻辑,这个断言会误报,那就要在断言语义里额外加一个跳变条件,不能无脑套用。

5.5 scoreboard里复用了预期队列的同一指针

现象:仿真跑到一半,scoreboard报“数据顺序错乱”,但反复检查FIFO设计都认为是正常的;换一个种子又不同,出现概率和读事务速率强相关。 原因:可能在scoreboard里把写侧事务的直接引用传进了expected_queue,而transaction对象在UVM的objection机制下被后续进程复用或修改了字段值。UVM里的sequence item在driver发完之后默认会释放回对象池,引用同一地址的队列条目内容被改写,数据比较时自然对不上。 解决:使用transaction时,在write_analysis里必须clone一份再入队,不要直接push_back原对象。养成“入队前clone,出队时比较”的习惯,能避免一大类UVM对象复用带来的偶发错乱问题。笔试时被问“scoreboard要不要做data mirror”时,这个点刚好用来解释为什么“必须复制”。

6. 让笔试回答看起来“做过验证”:三个小技巧

6.1 笔试题里画验证平台图:按组件职责答,不按类名答

异步FIFO笔试时让你画验证平台图,很多人的答案是“UVM环境里有agent有scoreboard有env”。这句话等于没答。建议答题口径是:写侧有一个agent负责产生写事务、采样写接口;读侧有一个agent负责产生读事务、采样读接口;两个agent的monitor把事务分别送给scoreboard的写入口和读入口;scoreboard内部维护一个预期数据队列,同时有独立模块检查满空标志;env负责把agent和scoreboard连起来,test里控制用例场景。按职责讲,面试官能确认你不是背结构,而是真理解每个组件为什么存在。

6.2 遇到设计缺陷不要慌:先给断言,再给改法

笔试时经常会有“异步FIFO深度的verilog代码哪里有问题”之类的题。不要一上来就说“改RTL”,正确的回答顺序是先指出“哪里需要加断言盯住”,再指出“功能从哪里改”。例如说“这个代码空标志用组合逻辑直接判断,验证时可能采样竞争,建议先加断言检查空信号在rclk下降沿是否稳定”,接着说“如果要改,就把空判定打一拍,用寄存器输出”。这个顺序说明你有验证思维,而不是只会看代码。

6.3 验证计划与覆盖率:一句话说出你测到哪了

被问“你怎么确保验证完备”时,记住一个句式:“我计划用X类用例覆盖满空边界、复位、读停写停和格雷码回绕,用功能覆盖率关掉每一个交叉点,回归前跑断言检查指针单调性。”这句话比说“我测了很多数据”有用得多。实际做验证时,我在项目里也习惯把“覆盖点列表”和“测试用例列表”做成交叉表,每个用例对应哪些覆盖点,缺哪一个在表里一目了然。这个习惯也直接延续到了笔试作答上。

最后一件事想提醒你:异步FIFO的UVM验证环境,我早期也走过弯路——写得很大,组件拆得很细,结果debug时到处找问题。后来学会先做行为校准,再搭最小环境,再叠加覆盖率和断言,整个验证效率反而高了。做验证不需要炫技,需要的是把边界圈清楚,让每一个错误都有据可查。希望这篇文章能帮你在笔试和实践中都少走几步弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询