想象你走进一家繁忙的餐厅:后厨里贴着十几张菜谱,传菜调度员盯着不断涌来的点菜单,大厨在灶台前一刻不停地颠勺出锅。菜谱不会自己炒菜,点菜单不会自己传菜,真正让一顿宴席有条不紊运转的,是一套环环相扣的“传菜机制”。UVM的Sequence机制,就是硬件验证世界里的这套传菜系统:Sequence是菜谱,SequenceItem是点菜单,Sequencer是传菜调度员,Driver是掌勺大厨。在这篇文章里,我们会走进这家“验证餐厅”的后厨,看懂订单如何排队、插队、被授权、最终变成芯片端口上的信号波形。当你掌握这套机制,复杂的AI芯片验证场景就能像编排宴席一样,层次分明、灵活高效。

岗位关联

对于AI从业者而言,Sequence机制是功能验证工程师的“指挥艺术核心”:掌握它,你就能把随机约束激励组织得像交响乐一样有序,显著提升验证覆盖率与调试效率,是通往验证架构师岗位的必修课。

目录
  1. 可视化演示
  2. 三大角色分工
  3. 执行生命周期
  4. 请求授权握手
  5. 优先级仲裁
  6. 虚拟序列指挥
  7. 复用与分层
  8. 锁与同步控制
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示UVM Sequence执行流程、虚拟序列层次化协调及优先级仲裁机制三种核心模式,直观呈现通用验证方法学激励生成的完整技术架构。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Sequence执行流程 Sequence启动后经pre_body/body/post_body执行激励生成 事务经Sequencer仲裁后由Driver驱动至DUT Sequence → Sequencer → Driver → DUT UVM Sequence机制是验证激励生成的核心引擎

三大角色分工

走进一家高效运转的餐厅,你一定会注意到三样东西:贴在墙上的菜谱、传菜调度员手里的点菜单、以及灶台前的大厨。菜谱规定了每一步怎么做,点菜单记录了一道菜的具体要求,调度员决定先做哪一桌,大厨则负责真正把菜端出来。UVM里的激励生成,正是这样一个“后厨三人组”:Sequence对应菜谱,SequenceItem对应点菜单,Sequencer对应传菜调度员,Driver则是对接被测芯片的大厨。

在UVM中,Sequence是一个继承自uvm_sequence的类,它不直接产生波形,而是像菜谱一样描述“接下来该做什么”;具体的一次次动作——比如一次寄存器写、一笔DMA传输——被封装成SequenceItem消息。Sequence在body()中不断创建并发送这些消息,Sequencer接收消息后进行仲裁排队,最终把获准的一条Item交给Driver。Driver才是真正把信号打到DUT管脚上的角色。

这三者的依赖非常清晰:Sequence只负责“出题”,Sequencer只负责“派单”,Driver只负责“执行”。这种分工把“激励怎么组织”和“激励怎么驱动”彻底解耦,于是同一份菜谱可以换不同的菜系(Driver)来炒,同一个大厨也能被不同菜谱(Sequence)指挥。

可见,整个Sequence机制的核心骨架就是“菜谱—调度—出锅”这条流水线。后文要讲的生命周期、握手、优先级、虚拟序列、复用和同步,全都是在这条流水线不同的环节上增添的规则。只要记住这三个角色各司其职,再复杂的验证场景也能拆解成“谁在出题、谁在排队、谁在干活”这三个问题。

激励流=Sequence→Sequencer→Driver\text{激励流} = \text{Sequence} \to \text{Sequencer} \to \text{Driver}
这条公式是UVM后厨的“上菜流水线”:菜谱产生订单,调度员派单,大厨出锅。
  1. Sequence产生消息Sequence在它的执行体中逐个创建Item,相当于菜谱翻到哪一页,就生成哪一道菜的订单。
    Item=Sequence.create_item()\text{Item} = \text{Sequence}.\text{create}\_\text{item}()
  2. Sequencer接收排队Item被发送给Sequencer,像是点菜单钉在调度员的看板上等待分配。
    Sequencer.queue←Item\text{Sequencer}.\text{queue} \gets \text{Item}
  3. Driver获得并执行Sequencer仲裁通过后,Driver取走Item并驱动到DUT端口上。
    DUT.wave←Driver(Item)\text{DUT}.\text{wave} \gets \text{Driver}(\text{Item})

执行生命周期

一道菜的诞生不是一蹴而就的:大厨先要备料、热锅,接着正式下锅翻炒,最后装盘、清理灶台。UVM的Sequence生命周期也分为三个阶段:pre_body是备料阶段,body是真正“炒菜”的阶段,post_body是收尾清理阶段。这套固定仪式保证了任何序列都能在统一骨架中插入自己的逻辑。

当你在测试用例中调用start()方法启动一个Sequence时,UVM会按顺序自动执行这三个回调。pre_body通常用来做前置配置:创建资源、设置约束、打印调试信息;body是核心,里面往往是一个while循环,不断产生Item并发送出去;post_body则负责清理现场,比如回收资源、统计这次序列产生了多少笔交易。

这就像宴席上切菜、炒菜、洗锅的节奏是固定的,但每个菜谱可以自由填充自己的内容。如果pre_body里直接调用了uvm_error或者执行了return,那么后面的body和post_body都不会执行——备料失败了,这锅菜自然炒不起来;反过来,body提前退出,post_body依然会执行,相当于菜炒到一半装盘,但灶台还是要收拾的。

理解生命周期,你就能精准控制“激励什么时候开始、什么时候结束”。在AI芯片验证中,一个复位序列往往要在pre_body里拉低复位信号、在整个序列结束后释放,这种“备料—炒制—收尾”的三段式思维,让每个Sequence都像一道可独立品鉴的菜,既完整又自洽。

L=pre_body+body+post_bodyL = \text{pre\_body} + \text{body} + \text{post\_body}
一个Sequence的总执行量等于“备料+炒制+收尾”三段之和,任何一段提前退出都会影响整道菜的出锅。
  1. 备料阶段pre_body最先运行,负责准备运行环境,相当于切好葱姜蒜。
    pre_body⇒配置与资源准备\text{pre\_body} \Rightarrow \text{配置与资源准备}
  2. 主体阶段body循环产出SequenceItem,是激励产生的核心引擎。
    body⇒{Item1,Item2,…}\text{body} \Rightarrow \big\{\text{Item}_1, \text{Item}_2, \dots \big\}
  3. 收尾阶段post_body在所有Item完成后执行清理动作,统计和回收。
    post_body⇒result+cleanup\text{post\_body} \Rightarrow \text{result} + \text{cleanup}
  4. 中止传播任一前序阶段失败,后续阶段都不再执行。
    abort⇒¬(body∨post_body)\text{abort} \Rightarrow \neg(\text{body} \lor \text{post\_body})

请求授权握手

传菜调度员和大厨之间有着严格的默契:调度员把点菜单递给大厨,大厨做完之后必须喊一声“好了”,调度员才知道这道菜完工,可以安排下一道。UVM中Sequence、Sequencer与Driver之间同样存在三次握手机制:请求(request)、授权(grant)、完成(done)。

Sequence发送一个Item时,先调用start_item发出请求,此时Item还停留在Sequencer的队列里等待仲裁;接着调用finish_item阻塞等待授权结果。一旦Sequencer把Item交给Driver,Driver通过get_next_item取走它并驱动信号,待驱动完毕再调用item_done回报“完成了”。只有在这之后,Sequence的finish_item才会真正返回,Sequence才能继续生成下一个Item。

这套握手协议保证了每个激励都不会丢失或重叠,就像传菜员不会在上一道菜还没出锅时就硬塞给大厨下一道菜。对于需要返回值的场景,item_done还能携带一个response,相当于大厨回给传菜员一句“这道菜客人加了辣椒”。没有这套严谨的握手,Sequence的循环和Driver之间的节奏就会脱节,验证环境里就会出现“菜越积越多”的拥塞。

这个“请求—授权—完成”的手握循环,是整个Sequence机制最底层的节拍器。你后面看到的优先级仲裁、虚拟序列协调,都建立在这套稳固的握手之上——就像餐厅无论多忙,传菜的基本动作依然是“递单、接单、报一声好了”。

Tcycle=treq+tgrant+tdoneT_\text{cycle} = t_\text{req} + t_\text{grant} + t_\text{done}
单笔交易的时间开销等于“请求排队+仲裁授权+完成回执”三段之和。
  1. 发起请求Sequence调用start_item,把Item放进Sequencer队列。
    treq:Item→Queuet_\text{req} : \text{Item} \to \text{Queue}
  2. 仲裁授权Sequencer按仲裁算法选出Item,交给Driver。
    tgrant:Queue→Drivert_\text{grant} : \text{Queue} \to \text{Driver}
  3. 完成回执Driver执行完后调用item_done,通知Sequencer释放通道。
    tdone:Driver→Sequencer[free]t_\text{done} : \text{Driver} \to \text{Sequencer}[\text{free}]

优先级仲裁

高峰时段餐厅里难免有VIP客人要求加急。传菜调度员会怎么做?他会把加急单插到队列前面,但绝不可能把大厨手里正在炒的菜抢回来重炒。UVM的优先级仲裁机制正是如此:每个Sequence和SequenceItem都可以设置priority字段,数值越高代表越“加急”;Sequencer在授权时优先选择队列中优先级最高的Item,但一旦Item已经被授权给Driver,就不会因为来了更高优先级的请求而中途打断。

初学者最容易误以为“设置了优先级就一定会插队”。实际上UVM默认的仲裁算法是SEQ_ARB_FIFO,也就是纯粹的先到先得,完全不看优先级!只有当你在Sequencer上配置了SEQ_ARB_STRICT_FIFO或SEQ_ARB_STRICT_RANDOM等严格仲裁模式时,优先级才会真正生效。这就像餐厅默认按点单顺序上菜,只有店长开启“VIP优先”模式,加急单才会跳到队伍前面。

同优先级怎么办?在严格FIFO模式下,相同优先级的请求按照到达顺序依次授权;在随机模式下则随机挑一个。UVM还提供了SEQ_ARB_WEIGHTED加权仲裁算法,可以按权重分配机率,适合做约束随机的覆盖率导向激励——哪个区域覆盖率低,就给相关序列提高权重,让验证资源自动涌向“冷门菜品”。

优先级仲裁让验证工程师拥有了一根“指挥棒”:想让某个关键场景多跑几遍,就把它的优先级调高;想模拟极端拥堵,就让所有序列都往同一个Sequencer涌。配合覆盖率反馈动态调整优先级,就能像经验丰富的店长一样,把后厨的产能精确分配到最需要的地方。

sel=arg⁡max⁡i∈Qprioi\text{sel} = \arg\max_{i \in Q} \text{prio}_i
仲裁结果是在所有排队请求中挑出优先级最高的那一个;优先级相同则按内部规则(FIFO或随机)决胜。
  1. 候选队列所有等待授权的Item构成集合$Q$。
    Q={Item1,Item2,… }Q = \{\text{Item}_1, \text{Item}_2, \dots \}
  2. 比较优先级逐项比较prio字段,找最大值。
    priomax⁡=max⁡i∈Qprioi\text{prio}_{\max} = \max_{i \in Q} \text{prio}_i
  3. 选出胜者优先级最高的Item获得授权。
    sel={i∣prioi=priomax⁡}\text{sel} = \{i \mid \text{prio}_i = \text{prio}_{\max}\}
  4. 平局决胜同类优先级下按时间戳或随机数选择。
    sel←tie_break(sel)\text{sel} \leftarrow \text{tie\_break}(\text{sel})

虚拟序列指挥

一场大型婚宴需要凉菜、热菜、甜点同时出菜,总厨不可能自己去守每一个灶台。他的做法是写一份“宴会统筹单”,分派给各条线的负责人,再由负责人调度各自的大厨。UVM的虚拟序列(Virtual Sequence)就是这份统筹单,它运行在一个“虚拟Sequencer”上,自己不直接连驱动,而是持有多个真实Sequencer的引用,然后在body()里启动多个子序列,让它们各自在自己的Sequencer上跑。

虚拟序列体现了“总指挥”与“一线员工”的分离:真实Sequence负责具体的激励生成,虚拟序列只负责编排。比如验证一颗AI加速芯片时,需要同时激励指令总线、数据总线和中断信号:你写一个虚拟序列,在body()中分别启动axi_seq、dma_seq和irq_seq,每个子序列挂在各自的总线Sequencer上。这样,三个“灶台”同时开火,整台芯片的交互场景就被协调起来了。

虚拟序列通常放在测试用例层(uvm_test)中被启动,它也是UVM验证环境的“总入口菜谱”。通过它,验证架构师可以把“测试什么场景”(虚拟序列)和“怎么产生激励”(真实序列)彻底分开:产品经理改需求,只需要换一份统筹单,后厨的菜谱一行都不用动。

虚拟序列让“统筹”成为第一公民。真实的工作中,系统级验证最复杂的部分往往不是某一条总线上的协议,而是多条总线之间的时间交错与依赖关系。虚拟序列把这种协调逻辑从各个Sequence中抽离出来,统一放在一个清晰的编排层里,既好写又好维护。

vseq→start{seq1,seq2,…,seqn}\text{vseq} \xrightarrow{\text{start}} \big\{ \text{seq}_1, \text{seq}_2, \dots, \text{seq}_n \big\}
虚拟序列启动后,会并行或按序拉起多个子序列,形成多路激励并发的局面。
  1. 定义虚拟序列继承uvm_sequence,在类里持有各真实Sequencer的句柄。
    vseq={seqrAXI,seqrDMA,…}\text{vseq} = \big\{ \text{seqr}_\text{AXI}, \text{seqr}_\text{DMA}, \dots \big\}
  2. 启动子序列在body()里调用seq.start(seqr),即把子菜谱交给对应调度员。
    seqk.start(seqrk)\text{seq}_k.\text{start}(\text{seqr}_k)
  3. 等待汇合虚拟序列在末尾等待所有子序列完成,像宴会等所有菜品上齐。
    join(seq1,seq2,…,seqn)\text{join}\big(\text{seq}_1, \text{seq}_2, \dots, \text{seq}_n\big)

复用与分层

连锁餐厅之所以能快速扩张,靠的是标准化的基础菜谱:每一家分店都使用同一套“鱼香肉丝”配方,再组合出各种套餐。UVM的Sequence机制天生支持这种复用:基础序列可以像乐高积木一样嵌套进更大规模的序列中,形成“套餐序列”。

你可以在一个Sequence的body()里直接启动另一个Sequence,这就是序列嵌套。比如先定义reset_seq(复位序列)和config_seq(配置序列),然后写一个init_seq依次启动它们;init_seq又可以作为更上层boot_seq的一部分。这种组合方式比把几百行代码写在一个Sequence里要清晰得多,而且每一层都可以单独调试、单独复用。

为了进一步减少样板代码,UVM提供了uvm_do、uvm_create等宏,把“创建Item—发送—等待完成”压缩成一行。同时,Sequence还可以通过uvm_config_db读取配置,在不同测试里重载约束甚至替换整个子序列——好比连锁店根据地区口味,把“鱼香肉丝”的辣度配置在总部数据库中远程下发。

复用的本质是“把变与不变分开”:不变的协议细节沉淀为底层基础序列,易变的场景组合留到上层编排。面对AI芯片这种动辄几十个接口、上百个配置项的验证环境,没有分层复用的Sequence机制,光靠手写激励脚本,维护成本会像滚雪球一样失控。

Sp=f(S1,S2,…,Sm)S_{p} = f\big(S_{1}, S_{2}, \dots, S_{m}\big)
组合序列是子序列经过某种编排函数$f$(顺序、并行或条件)得到的高级序列。
  1. 定义基础序列把原子操作封装成独立Sequence,如单次读写。
    S1=read_reg,S2=write_regS_1 = \text{read\_reg}, \quad S_2 = \text{write\_reg}
  2. 顺序组合在上层序列中依次启动多个子序列。
    Sp=S1→S2→S1S_{p} = S_1 \to S_2 \to S_1
  3. 条件分支根据运行时信号选择不同子序列,形成决策树。
    Sp={S1,if flagS2,otherwiseS_{p} = \begin{cases} S_1, & \text{if flag} \\ S_2, & \text{otherwise} \end{cases}
  4. 配置重载通过配置库改变子序列内部约束,实现“换汤不换药”。
    constraint←uvm_config_dbnew value\text{constraint} \xleftarrow{\text{uvm\_config\_db}} \text{new value}

锁与同步控制

后厨最怕的是两个传菜员同时把菜塞给同一个大厨,或者两个大厨抢同一口锅。高峰期餐厅会设置“专属时段”:某道招牌菜在特定时间里独占一个灶台。UVM中对应的是lock()与grab()机制,让某个Sequence可以独占Sequencer资源,避免其他序列插队干扰。

lock()相当于“排队领取专属时段”:如果当前有其他序列在使用,它就在后面等着,拿到锁之后一直独占,直到调用unlock()释放。grab()则是“强行抢占时段”,通常用于紧急任务,比如时钟突然异常时需要立刻注入一段复位序列。两者的共同点是都必须在用完后释放,否则其他Sequence会无限期等待——这就是后厨里最危险的“死锁”:你等我出锅,我等你让锅,最后谁也没饭吃。

除了锁,UVM还有uvm_event事件同步机制:序列A可以trigger一个事件,序列B在关键节点wait_trigger等待它。这就像传菜员摇铃“准备好了!”,另一个传菜员听到铃声才开始上菜。合理的锁与事件配合,能让多个序列在共享资源上跳出优雅的“双人舞”。

锁与同步控制是Sequence机制里的“安全阀”。在真实的多IP验证环境中,不带锁的并发序列就像无人指挥的十字路口,迟早会撞车。理解了独占与事件同步,你写的验证环境才敢让激励真正“并发”起来,而不是退化成简单的串行脚本。

资源占用=lock(A)⇒执行A⇒unlock(A)\text{资源占用} = \text{lock}(A) \Rightarrow \text{执行}_A \Rightarrow \text{unlock}(A)
一个Sequence从申请到释放独占权的完整过程,中间不允许其他序列介入共享资源。
  1. 申请锁调lock()请求独占Sequencer,成功后其他序列的请求被挂起。
    lock()⇒owner=seqA\text{lock}() \Rightarrow \text{owner} = \text{seq}_A
  2. 独占执行在锁保护下安全地完成批量Item发送,不用担心被打断。
    seqA:Item1,Item2,…\text{seq}_A : \text{Item}_1, \text{Item}_2, \dots
  3. 释放锁调unlock()交还资源,让排队中的其他序列继续。
    unlock()⇒owner=∅\text{unlock}() \Rightarrow \text{owner} = \emptyset
  4. 死锁防止多个锁必须按同样顺序申请,或用grab打破僵局。
    deadlock  ⟺  cyclelock∧¬timeout\text{deadlock} \iff \text{cycle}_{lock} \land \neg\text{timeout}

AI 应用场景

场景一:AI芯片寄存器配置的随机回归

阶段:验证激励生成与回归测试

解决的问题:传统定向测试用例需要人工枚举几百种寄存器配置组合,效率低且容易漏掉边界情况。AI芯片的寄存器动辄上千个,手写激励脚本几乎不可维护。

具体案例:验证工程师为神经网络加速器的电源管理模块编写power_seq,其中包含多个随机化SequenceItem,每个Item随机生成寄存器地址、数据值和写入顺序。Sequencer按配置的严格优先级仲裁,让关键寄存器配置以更高优先级先执行。回归测试中,这套Sequence自动跑出数万种组合,功能覆盖率从60%提升到95%。

场景二:AI加速器多接口协同的系统级验证

阶段:系统级验证与场景建模

解决的问题:AI芯片工作时,AXI总线在搬运权重、DMA在搬运特征图、中断控制器在响应完成信号,三个接口必须协同验证。如果分开验证,无法发现总线互锁和时序冲突。

具体案例:验证架构师设计一个虚拟序列ai_boot_vseq,运行在虚拟Sequencer上,在body()中并行启动axi_weight_seq、dma_feature_seq和irq_monitor_seq。三者通过uvm_event同步:DMA搬运完成后触发事件,AXI序列等待事件后才发起下一次权重读取,精准还原芯片真实启动流程。

场景三:覆盖率驱动的动态激励收敛

阶段:覆盖率分析与激励调整闭环

解决的问题:随机激励跑得再多,总有少数“冷门状态”覆盖不到。人工分析覆盖率报告再手动改约束,迭代周期太长。

具体案例:高级验证工程师定期统计覆盖率,发现浮点单元的低功耗状态覆盖率不足,于是在回归中把相关Sequence的priority调高,并在uvm_do宏上追加更紧的约束。下一轮回归中,高优先级序列自动占据更多执行机会,覆盖率快速收敛到目标值。

Python 演示

python63 行
import heapq  class SequenceItem:    def __init__(self, name, priority=1):        self.name = name        self.priority = priority  class Sequencer:    def __init__(self):        self.pending = []        self.order = 0     def request(self, item):        heapq.heappush(self.pending, (-item.priority, self.order, item))        self.order += 1     def grant(self):        if self.pending:            return heapq.heappop(self.pending)[2]        return None  class Sequence:    def __init__(self, items):        self.items = items     def run(self, seqr):        for item in self.items:            seqr.request(item)  class Driver:    def __init__(self, seqr):        self.seqr = seqr     def work(self):        count = 0        while True:            item = self.seqr.grant()            if item is None:                break            print("Driver execute:", item.name, "priority =", item.priority)            count += 1        return count  def main():    seqr = Sequencer()    seq = Sequence([        SequenceItem("write-cfg", priority=2),        SequenceItem("read-status", priority=1),        SequenceItem("erase-chip", priority=3),        SequenceItem("write-cfg", priority=2),    ])    seq.run(seqr)    drv = Driver(seqr)    print("total done:", drv.work())  if __name__ == "__main__":    main()
目的:用Python堆队列模拟UVM的Sequencer仲裁,展示高优先级Item先被Driver执行的调度过程。
  1. heapq.heappush(self.pending, (-item.priority, self.order, item))利用堆实现优先级队列,取负号让最大优先级先弹出。
  2. seq.run(seqr)让Sequence把每个Item依次“请求”给Sequencer,对应UVM中start_item的行为。
  3. item = self.seqr.grant()Driver从堆顶取走当前优先级最高的Item,对应UVM的授权握手。
  4. while True:` + `break队列清空后结束工作循环,对应验证结束前所有激励耗尽。
  5. if __name__ == "__main__":标准Python入口,保证模块被导入时不会意外执行。

常见误区

误区我以为Sequence就是一串要发给DUT的数据,写Sequence就是往里面塞几个Item然后发送,和发一帧网络报文没什么区别。

正解Sequence是“菜谱”而不是“菜”。它描述的是产生激励的流程和逻辑,而真正被送到DUT端口的数据包是SequenceItem。Sequence可以包含循环、条件分支、事件等待、子序列调用,它是生成Item的工厂,不是Item本身。

为何常见:因为日常说到“发激励”时,大家习惯把Sequence、Item混为一谈,初学者从代码里看到uvm_do(item)就误以为Sequence直接就是那笔数据。

误区我给Sequence设置了`priority=100`,为什么跑仿真时它还是排在后面?UVM的优先级难道不生效吗?

正解UVM Sequencer默认使用SEQ_ARB_FIFO仲裁算法,只按请求到达顺序授权,完全忽略priority字段。必须显式调用set_arbitration(SEQ_ARB_STRICT_FIFO)等严格仲裁模式,优先级才会生效。就像餐厅默认先来先上菜,店长没开“VIP优先”模式时,加急单也插不了队。

为何常见:因为面向对象的直觉是“字段存在就应该生效”,但仲裁策略是独立配置的,字段只是候选条件,算法的优先级更高。

误区我以为只要来了一个更高优先级的Sequence,它就能随时打断正在执行的Item,把总线抢过来。

正解一旦Item被Sequencer授权给Driver并开始驱动,它就进入了“不可抢占”阶段,任何高优先级的新请求都只能排在后面。抢占只发生在队列内部,相当于大厨手里的菜已经下锅,VIP来了也得等这盘出锅。

为何常见:对“优先级”这个词的日常理解是“谁大谁说话”,而验证里为了保证时序完整性,必须维持握手的原子性。

误区虚拟序列既然叫“序列”,我就在它的`body()`里直接写信号驱动代码,像Driver一样把波形打到接口上。

正解虚拟序列运行在虚拟Sequencer上,它本身没有连接任何Driver,不能直接驱动DUT。它的职责是编排:启动各个子序列、协调它们之间的时序、等待所有子序列完成。真正驱动信号的是挂在真实Sequencer下的Driver。

为何常见:因为虚拟序列的名字里带“序列”,让人误以为它是功能加强版序列,其实它的“虚拟”恰恰说明它不接触物理信号,只做调度。

面试题

Q1. UVM中,Sequence与SequenceItem的关系最像下列哪一项?

单选题 · easy

  • A.菜谱与做好的菜← 正确答案
  • B.厨师与炒菜的锅
  • C.传菜员与点菜单
  • D.收银台与会员卡

答案:A。Sequence是描述生成流程的“菜谱”,SequenceItem是流程产出的具体“菜品订单”,二者是生产者与产物的关系。厨师与锅、传菜员与菜单都是并列协作关系,比喻不准确。

Q2. Sequencer最核心的职责是什么?

单选题 · easy

  • A.生成随机激励数据
  • B.连接DUT上的管脚
  • C.仲裁请求消息调度← 正确答案
  • D.统计功能覆盖率

答案:C。Sequencer充当传菜调度员,核心职责是接收Sequence的请求、按仲裁算法授权、并把Item交给Driver。生成数据是Sequence的事,连接管脚是Driver的事,统计覆盖率是covergroup的事。

Q3. UVM里Sequence默认使用哪种仲裁算法?

单选题 · mid

  • A.先到先得调度算法← 正确答案
  • B.严格高优先级挑选
  • C.完全随机挑选算法
  • D.权重加权平均调度

答案:A。UVM Sequencer默认仲裁算法是SEQ_ARB_FIFO,即先到先得,完全不看优先级。严格高优先级对应SEQ_ARB_STRICT_FIFO等,随机对应SEQ_ARB_RANDOM,权重对应SEQ_ARB_WEIGHTED,都需要显式配置。

Q4. 高优先级订单在什么情况下不能插队?

单选题 · mid

  • A.已经在队列中排队时
  • B.已被授权给驱动器后← 正确答案
  • C.刚由顶层序列生成时
  • D.正在等待仲裁结果时

答案:B。Item一旦被授权给Driver并开始驱动,就处于“不可抢占”状态,就像菜已下锅不能倒回。在队列中、刚生成、等待仲裁时高优先级请求都可以通过仲裁挤到前面。

Q5. 虚拟序列的主要作用是什么?

单选题 · mid

  • A.提升单条序列的执行效率
  • B.跨多个序列器协调子序列← 正确答案
  • C.替代驱动器产生波形信号
  • D.加快随机约束的求解过程

答案:B。虚拟序列运行在虚拟Sequencer上,像总厨统筹单一样协调多个真实Sequencer上的子序列。它不驱动波形、不改变随机求解,也不直接提升单条序列效率,而是解决多路激励的编排问题。

Q6. 正确启动一个Sequence的方法是?

单选题 · easy

  • A.调用启动接口传入序列器← 正确答案
  • B.通过构造函数自动运行
  • C.调用主体函数手动执行
  • D.在复位释放时自动触发

答案:A。标准做法是调用seq.start(sequencer),并把目标Sequencer作为参数传入。构造函数只负责对象初始化,body()必须在start()触发后才会被回调,不会自动运行。

Q7. 关于请求授权握手,下列说法正确的有?

多选题 · mid

  • A.序列先发起请求再等待授权← 正确答案
  • B.授权后才把消息交给驱动器← 正确答案
  • C.消息不经仲裁也可直接下发
  • D.完成通知对于握手可有可无

答案:AB。握手顺序是“请求→仲裁授权→Driver执行→完成通知”。任何Item都必须经过Sequencer仲裁,不能绕过;item_done是握手闭环的关键,缺失会导致finish_item无法正常返回,因此C和D错误。

Q8. 关于lock与grab的区别,正确的是?

单选题 · hard

  • A.抢占锁的优先级高于普通锁← 正确答案
  • B.普通锁获得后不可被插队
  • C.两者在仲裁上没有明显区别
  • D.只有虚拟序列才能调用锁

答案:A。grab()的请求优先级高于所有lock()请求,可以打破现有锁的排队顺序,适合紧急干预场景。普通锁内部按FIFO排队,但可能被grab插队;锁是Sequence方法而非虚拟序列专属,因此B、C、D错误。

Q9. 下列属于UVM内置仲裁算法的有?

多选题 · mid

  • A.先到先得仲裁算法← 正确答案
  • B.严格优先级仲裁算法← 正确答案
  • C.权重随机仲裁算法← 正确答案
  • D.总线共享仲裁算法

答案:ABC。UVM内置了SEQ_ARB_FIFO(先到先得)、SEQ_ARB_STRICT_FIFO/SEQ_ARB_STRICT_RANDOM(严格优先级)、SEQ_ARB_WEIGHTED(权重随机)等算法。总线共享是总线协议概念,不属于UVM仲裁算法。

Q10. 关于虚拟序列,下列说法正确的有?

多选题 · extreme

  • A.本质上仍是普通序列类← 正确答案
  • B.只能直接驱动一个核心
  • C.需要在虚拟序列器上启动← 正确答案
  • D.可以启动多个真实子序列← 正确答案

答案:ACD。虚拟序列仍然是继承uvm_sequence的普通序列,只是运行在虚拟Sequencer上,通过启动多个子序列实现跨核协调。它本身不直接驱动任何核心,因此B错误,A、C、D正确。