小林是位小说家,想把故事推向全球。出版社要求:先用他习惯的中文写自由草稿,再翻译成一种语法严格的“标准语”,编辑们在这个标准语版本上做删改、统一术语、压缩篇幅,最后才转成英文、法文、德文出版。这样做的好处是巨大的:所有修改只需在“标准语”上做一次,各国版本自动同步正确。 硬件设计也是同样的故事。你用Chisel描述处理器、AI加速器时,写下的不是最终交付物,而是一份“自由草稿”。它会被翻译成硬件世界的“标准语”——FIRRTL。FIRRTL(Flexible Intermediate Representation for RTL)位于Chisel与Verilog之间,承担着“可优化、可变换、多后端”的中间层角色。等所有优化和变换完成,再由编译器把它转成Verilog甚至门级网表。 读完本文,你会像走进出版社编辑部,看清各个环节如何协作:从FIRRTL语法、三层IR,到Pass机制和自定义Pass。掌握了这条流水线,你就能像资深编辑一样,为硬件设计定制属于自己的“出版工艺”。

岗位关联

对AI从业者的价值:理解FIRRTL就能看穿Chisel编译器的内部机理,并能编写自定义编译Pass做硬件定制优化,这是构建敏捷芯片设计工具链和AI专用加速器的核心能力。

目录
  1. 可视化演示
  2. 初见FIRRTL:硬件设计中的“标准世界语”
  3. 核心语法:标准语的固定句型
  4. 电路表示:篇章里的信息流
  5. 三层IR:从初稿到印刷稿
  6. 编译Pass机制:编辑部的流程管理
  7. 自定义Pass:写一条自己的编辑规则
  8. 集成编译流程:让标准语正式上线
  9. 优化Pass实例:编辑部的实战战绩
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

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

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

初见FIRRTL:硬件设计中的“标准世界语”

在出版流程中,小林最关心的是自己的故事不丢情节,而出版社最关心的是如何用统一方式管理几十种语言的版本。于是他们约定:先把中文草稿翻译成“标准世界语”,所有编辑操作都在这个版本上进行。FIRRTL就是硬件设计世界的“标准世界语”。它由Chisel编译器自动产生,不是给人手写的语言,却是整个Chisel编译工具链真正的心脏。

从技术上说,FIRRTL全称是 Flexible Intermediate Representation for RTL,即“灵活的RTL中间表示”。它和LLVM IR的角色很像:前端负责把Chisel源码变成IR,后端负责把IR变成Verilog/门级网表,中间留下一个标准化的改造空间。这样一来,设计者不用关心目标工艺,编译器也只需面向一种稳定的中间格式工作。

FIRRTL的设计目标可以概括为三点:第一,保留足够的语义信息,让高级变换(如内存抽象、条件分支优化)可行;第二,结构严格,每个信号只有一个驱动源、类型和位宽显式可查;第三,可扩展,任何人都能插入自定义Pass。正是这三点,让FIRRTL成为连接高层次设计与低层实现之间的桥梁。整个IR链可以写成:

回到小说家小林。他很快发现,只要编辑部对标准语版本完成一次精修,几十种语言的译本都会同步受益。硬件设计者同样不必再为Verilog的细节分心,Chisel之上的创意和FIRRTL之下的优化被彻底分开——这正是FIRRTL存在的最大价值。

Chisel AST  →lowering  CHIRRTL  →expansion  Fhigh  →optimization  Fmid  →lowering  Flow  →emitting  Verilog\displaystyle \text{Chisel AST} \;\xrightarrow{\text{lowering}}\; \text{CHIRRTL} \;\xrightarrow{\text{expansion}}\; F_{\text{high}} \;\xrightarrow{\text{optimization}}\; F_{\text{mid}} \;\xrightarrow{\text{lowering}}\; F_{\text{low}} \;\xrightarrow{\text{emitting}}\; \text{Verilog}
这个链式公式表示:Chisel源码先降低成CHIRRTL,再逐层精修,最终生成Verilog。
  1. lowering(降低)把高层次的Chisel表示拆成更底层的IR语句,就像出版社把自由中文草稿改写成语法固定的标准语。
    lowering\text{lowering}
  2. CHIRRTLChisel直接吐出的初始IR,还保留很多高层抽象,相当于第一版粗略翻译稿。
    CHIRRTL\text{CHIRRTL}
  3. expansion(展开)把隐式类型、隐式连接全部显式化,得到High层IR,相当于通读后补齐所有线索。
    FhighF_{\text{high}}
  4. optimization(优化)在Mid层运行各种Pass,统一修改不同章节,相当于编辑在标准语上删冗调序。
    FmidF_{\text{mid}}
  5. emitting(生成)Low层IR已经非常接近RTL,直接转成Verilog,相当于标准语终稿交到各国译者手中。
    Verilog\text{Verilog}

核心语法:标准语的固定句型

标准世界语有固定词序和词尾,FIRRTL也有自己的“固定句型”。如果打开一个FIRRTL文件,你看到的不是电路连线图,而是一篇结构清晰的文档:module(模块)是文章的章节,port(端口)是章节封面上标注的输入输出接口,statement(语句)是组成内容的句子,when(条件)则是“如果……那么……”的条件从句。

FIRRTL的模块语法非常简洁。每个模块都有一组端口,端口说明方向(input/output)和类型(Clock时钟、UInt无符号整数、SInt有符号整数等)。模块内部用connect语句把信号连接起来,用reg声明寄存器,用when表达条件行为。下面是一个微型示例:

FIRRTL和Verilog最大的不同在于“严格”:每个线网的驱动源唯一,多处赋值时采用“最后连接生效”(last connect)语义,条件块之间不会出现看不见的竞争。模块结构可以形式化地用三元组表示:

小林在标准语版本上改稿时,会特别留意语法错误;FIRRTL的严格语法也在编译早期就拦截大量问题。这种“用固定句型把意思说死”的风格,让后续每个Pass都能放心地修改电路,而不用担心理解偏差。

M=(m,  p⃗,  s⃗)\displaystyle M = (m,\; \vec{p},\; \vec{s})
这个公式表示一个FIRRTL模块由模块名、端口向量和语句向量三部分构成。
  1. 模块名 \(m\)模块的唯一标识,相当于文章的章节标题。
    mm
  2. 端口向量 \(\vec{p}\)按顺序列出所有输入输出端口,定义模块与外界的通信接口。
    p⃗\vec{p}
  3. 语句向量 \(\vec{s}\)模块内所有connect/when/reg等语句的有序集合,构成模块的具体行为。
    s⃗\vec{s}
text9 行
module Top:  input clk : Clock  input a   : UInt<8>  input b   : UInt<8>  output y  : UInt<8>  node s = add(a, b)  reg r : UInt<8>, clk  r <= s  y <= r

电路表示:篇章里的信息流

小说里每个名词都有指代,电路里的每个信号也是一样。线网(wire)像随时可被引用的便签,节点(node)像一次算好的快捷指代,寄存器(reg)则像角色的长期记忆——只有时钟沿到来时才更新。而连接(connect)则是在说:“这个词的含义由右边那句话来决定。”

FIRRTL区分组合逻辑和时序逻辑。组合逻辑只看当前输入,像小说中人物不加思考的本能反应;时序逻辑依赖状态,像人物会记住过去发生的事。寄存器驱动就是典型的时序逻辑:当前拍赋给寄存器的值,要到下一个时钟沿才成为它的输出。

FIRRTL的信号驱动关系有一个关键规则——最后连接生效。同一个信号即使被多次赋值,最终生效的也只有最后一次,这避免了Verilog中常见的多驱动竞争。可以写成:

正因为FIRRTL把“信息流”表达得像文章一样有唯一主语,编译器才敢在各种Pass里大改特改。小林把稿子交给编辑部时最安心的一点是:每个情节都有明确来源,不会因为版本混乱而出现“一句话有两个作者”的灾难。

v(n)=v(src(last(n)))\displaystyle \mathsf{v}(n) = \mathsf{v}\bigl(\mathrm{src}(\mathrm{last}(n))\bigr)
这个公式说明:信号 \(n\) 的当前值,等于它最后一次连接语句右侧源表达式的值。
  1. 信号名 \(n\)需要被求值的那个线网/节点/寄存器,像是故事中的角色。
    nn
  2. 最后连接 \(\mathrm{last}(n)\)在同一作用域内最后一次给 \(n\) 赋值的连接语句,相当于小说的最终定稿。
    last(n)\mathrm{last}(n)
  3. 源表达式 \(\mathrm{src}(\cdot)\)连接语句右侧的驱动值,角色最终听到的那句话。
    src(⋅)\mathrm{src}(\cdot)

三层IR:从初稿到印刷稿

编辑部拿到小林的初译稿后,会做三次精修:第一次通读确保意思完整(High),第二次压缩句式去掉冗余表达(Middle),第三次按印刷规范排版(Low)。FIRRTL把这个过程固化成了三层IR,让每一次变换都只做少量而可控的工作。

CHIRRTL是Chisel直接生成的芯片级IR,包含大量高层语义,比如内存抽象、字段读写、隐式类型。High FIRRTL把隐式类型和连接都补充完整;Middle FIRRTL会消除复杂的存储抽象,把内存端口拆成具体的读写信号;Low FIRRTL则进一步展开节点,把各种语法糖还原成最朴素的连接和寄存器更新,最终能直接对应RTL。

这种分层设计意义重大:高层IR方便做语义级优化,低层IR方便做结构级变换和Verilog生成。每一层都是对上一层的精确降低,形式上就是一系列变换函数:

小林看着编辑部的三次精修流程,终于明白为什么公司很少直接拿初译稿去排版。逐层精修让他可以在不同阶段提出不同要求——前期改剧情,后期改标点;硬件设计者也一样:在正确的那一层做正确的事,才能既高效又安全。

Fi+1=Li(Fi),i∈{CHIRRTL,  High,  Mid}\displaystyle F_{i+1} = \mathcal{L}_{i}(F_{i}),\quad i \in \{\text{CHIRRTL},\; \text{High},\; \text{Mid}\}
这个公式表示:每层IR都由上一个IR经过一个降低变换得到,直到低层。
  1. 当前层 \(F_i\)i等于CHIRRTL、High或Mid时,分别表示当前正在处理的IR层。
    FiF_i
  2. 降低变换 \(\mathcal{L}_i\)把一个IR层转成下一层IR的具体Pass,相当于一道编辑工序。
    Li\mathcal{L}_i
  3. 下一层 \(F_{i+1}\)降低后得到的更偏RTL风格的IR,继续等待下一道工序。
    Fi+1F_{i+1}

编译Pass机制:编辑部的流程管理

编辑部最怕漫无目的地乱改,所以每道工序都有标准作业程序:先核对事实,再统一措辞,最后调整排版。FIRRTL编译器内部也有一套流程管理机制,把所有修改电路的工序统称为Pass,每个Pass只做一种变换,并通过依赖关系排成流水线。

在FIRRTL编译框架中,Pass被抽象成Transform接口:输入FirrtlCircuit,输出FirrtlCircuit。编译器通过依赖图决定Pass的执行顺序,只有当某个Pass在元数据/IR形态上依赖前置结果时,后者才允许运行。还有Token机制:一个Pass可以要求编译器先前置运行它需要的其他Pass,就像编辑器在排版前强制要求先完成校对。

依赖关系可以用有向图建模:

有了这套流程管理,小林再也不用担心某个热心的编辑跳过校对直接改排版。对于编译开发者来说,依赖图就是编辑部的SOP看板,让成百上千个Pass能像齿轮一样精确咬合,稳定运行在每一次硬件构建中。

G=(P,E),E={(pi,pj)∣pi precedes pj}\displaystyle \mathcal{G}=(P,E),\quad E=\{(p_i,p_j)\mid p_i \text{ precedes } p_j\}
这个公式表示所有Pass构成节点,边代表先后顺序,整个图由编译器拓扑排序后执行。
  1. Pass集合 \(P\)所有可运行的编译Pass,等价于编辑部里所有标准工序。
    PP
  2. 依赖边集合 \(E\)若两条工序之间存在先后要求,就在图中添加一条有向边。
    EE
  3. 有向边 \(p_i \to p_j\)表示必须先完成 \(p_i\),才能执行 \(p_j\),防止编辑顺序颠倒。
    pi→pjp_i \to p_j

自定义Pass:写一条自己的编辑规则

小林写了本侦探小说,发现每位编辑都喜欢把“其实”改成“事实上”,很烦。于是他向出版社申请:让我注册一条自己的编辑规则吧!FIRRTL正好支持这种扩展——你可以编写自定义Pass,像注册一条语法规则一样插入编译流水线。

在Scala中编写自定义Pass,通常继承Transform类,重写execute方法:接收CircuitState,遍历IR中的Module、Connect、When等节点,执行想要的修改,最后返回新的CircuitState。若在依赖列表里声明“我需要在类型检查之后运行”,编译器就会自动排序。这样既能安全地修改IR,又不会破坏其他Pass。

自定义Pass本质上是对电路的语义保持变换。它把输入电路映射到输出电路,但要求行为完全不变,只改变结构和代价:

小林注册的“删掉其实”规则上线后,所有英文版也同时受益。自定义Pass的威力正是如此:你只写一次变换逻辑,就能在所有目标工艺的生成流程中生效,真正把“作者意图”变成“编辑规范”。

Ψ(C)=C′,behavior(C′)=behavior(C)\displaystyle \Psi(C) = C',\quad \text{behavior}(C') = \text{behavior}(C)
这个公式说明:Pass的输出电路必须与输入电路行为等价,任何修改都不能改变设计语义。
  1. 变换函数 \(\Psi\)你定义的自定义Pass,把一份电路稿变成另一份电路稿。
    Ψ\Psi
  2. 输入电路 \(C\)Pass运行前的原始电路,相当于待编辑的稿件。
    CC
  3. 行为等价输出电路 \(C'\) 在功能上和 \(C\) 完全一致,只允许结构和性能变化。
    behavior(C′)=behavior(C)\text{behavior}(C') = \text{behavior}(C)

集成编译流程:让标准语正式上线

出版社的各种规则最终要在统一排版系统中运行。FIRRTL生态的主要入口是firtool工具——一个用C++实现的快速编译器。它读取Chisel生成的.fir文件,像排版系统读取标准语文本一样,输出Verilog文件和相关注解。

在日常Chisel工程中,设计者写的Scala代码经过circt/firrtl插件,会自动生成FIRRTL文件;随后firtool接手,通过命令行选项指定优化级别、是否输出SystemVerilog、是否保留注释等。整个过程中,IR阶段对用户是透明的,但理解它有助于定位编译问题、调节优化行为。

firtool可以看成多个Pass的组合函数:

对小林来说,出版社统一排版系统让他只需交一份标准语稿,剩下的事全都自动化。硬件设计者面对firtool也有同样感受:写一次Chisel,就能为ASIC、FPGA等多个后端生成不同风格的结果。这种“一次编写、多处发布”的体验,正是中间表示带来的巨大红利。

firtool=πn∘πn−1∘⋯∘π1\displaystyle \text{firtool} = \pi_n \circ \pi_{n-1} \circ \cdots \circ \pi_1
这个公式表示:firtool的整体行为等于所有内部Pass按顺序复合,前面Pass的输出馈给后面Pass。
  1. 单个Pass \(\pi_i\)编译流水线中的一道工序,例如类型检查、常数传播、Verilog生成。
    πi\pi_i
  2. 复合运算 \(\circ\)表示函数按从右到左的顺序执行,前一个Pass的输出成为后一个Pass的输入。
    ∘\circ
  3. 整体firtool所有Pass复合后得到的完整编译器行为,等价于把标准语文本逐级变成目标语言版。
    firtool\text{firtool}

优化Pass实例:编辑部的实战战绩

编辑常做的优化是删掉所有“真的、其实、非常”,不改变意思但让书变薄。FIRRTL中也有一批类似的Pass,专门在IR阶段消除无用逻辑。我们来看两个最典型的例子:常数传播与Mux常量选择化简。

常数传播会顺着连接关系追踪常数。若一个信号被赋值为0,另一个信号是它与某值的与操作,Pass可以直接把结果替换为0,而不用真正生成一个与门电路。Mux优化则对选择端为常数的多路选择器做化简:选择端恒为1时,整个Mux就是数据端x。

这两类优化可以用重写规则表示:

小林发现,编辑删掉每一个“其实”后,书页数少了,读者却觉得更爽快。硬件优化Pass也一样:常数传播、死代码消除、Mux化简看起来不起眼,却在芯片面积和功耗上带来实实在在的收益。这就是在“标准世界语”阶段动手修改的魔力——一次优化,全世界受益。

and(a,0)  ⟶  0,mux(1,x,y)  ⟶  x\displaystyle \text{and}(a, 0) \;\longrightarrow\; 0,\qquad \text{mux}(1, x, y) \;\longrightarrow\; x
这个公式表示:常数参与的与运算和选择器,可以在IR阶段直接化简成更小电路。
  1. 与常数0求与任何信号和常量0做与运算都恒等于0,这是常数传播的基础规则之一。
    and(a,0)\text{and}(a,0)
  2. 化简结果为0IR阶段直接生成常量0,避免创建无用的与门。
    00
  3. 选择端为1的Mux当Mux的选择信号恒为1时,输出永远等于x输入。
    mux(1,x,y)\text{mux}(1,x,y)
  4. 化简为x将三输入Mux替换为直接连线的x信号,减少一个多路选择器。
    xx

AI 应用场景

AI加速器数据通路优化

阶段:硬件设计 / 编译优化

解决的问题:AI加速器(如卷积引擎)中大量乘加逻辑存在冗余位宽和常数乘。传统做法是手写Verilog,难以在高层做细粒度优化。通过Chisel/FIRRTL,可以在IR阶段编写自定义Pass,自动将mul(x, 1)替换为x、将无关位宽截断,减少加速器面积。

具体案例:某AI芯片团队设计NVDLA风格的卷积单元时,在Chisel中描述通用MAC阵列,再用FIRRTL Pass扫描所有常数为2^N的乘法器,替换为移位寄存器,面积降低约18%,时序得到改善。

定制RTL生成流程

阶段:RTL生成和交付

解决的问题:FPGA原型验证需要快速生成带调试逻辑的RTL,而ASIC流程则需要干净网表。传统流程需要维护多套代码。借助FIRRTL,可以共享同一份Chisel源码,在IR阶段通过不同Pass组合输出不同风格Verilog。

具体案例:在自动生成AI推理引擎时,开发团队编写了一个“插入性能计数器”的自定义Pass,只在firtool命令带上该Pass时输出带计数器的版本;生产线和验证线共用一套Chisel源码。

形式化验证前置处理

阶段:验证与等效性检查

解决的问题:AI芯片中的协议逻辑非常复杂,直接从Verilog提取形式模型困难。FIRRTL的严格类型和最后连接语义使其成为更干净的中间模型,验证团队可基于IR建立参考模型。

具体案例:团队用FIRRTL中间层提取片上互联仲裁器的状态转换关系,自动对比优化前后两个IR的行为是否一致,快速发现了一个Pass引入的Mux交换错误。

Python 演示

python75 行
class FirrtlModule:    def __init__(self, name):        self.name = name        self.lines = []     def add_line(self, line):        self.lines.append(line)     def const_fold(self):        const_map = {}        new_lines = []        for line in self.lines:            dst, sep, src = line.partition(" <- ")            if not sep:                new_lines.append(line)                continue            if src.isdigit():                const_map[dst] = int(src)                new_lines.append(line)                continue            tokens = src.split()            if len(tokens) == 3 and tokens[0] in const_map and tokens[2] in const_map:                a, op, b = const_map[tokens[0]], tokens[1], const_map[tokens[2]]                if op == "+":                    val = a + b                elif op == "-":                    val = a - b                elif op == "*":                    val = a * b                elif op == "&":                    val = a & b                else:                    val = a                folded = str(val)                new_lines.append(dst + " <- " + folded)                const_map[dst] = val            else:                new_lines.append(line)        self.lines = new_lines     def dead_code_eliminate(self, outputs):        used = set()        for line in self.lines:            dst, sep, src = line.partition(" <- ")            for token in src.replace("+", " ").replace("-", " ").replace("*", " ").replace("&", " ").split():                used.add(token)        for out in outputs:            used.add(out)        kept = []        for line in self.lines:            dst, sep, src = line.partition(" <- ")            if dst in used or dst in outputs:                kept.append(line)        self.lines = kept     def show(self):        print("module " + self.name + ":")        for line in self.lines:            print("  " + line) if __name__ == "__main__":    m = FirrtlModule("AdderTop")    m.add_line("a <- 1")    m.add_line("b <- 2")    m.add_line("c <- a + b")    m.add_line("z <- c + b")    m.add_line("y <- c")    print("before:")    m.show()    m.const_fold()    print("after const_fold:")    m.show()    m.dead_code_eliminate(["y"])    print("after DCE:")    m.show()
目的:用Python模拟一个极简的FIRRTL中间表示,演示常数传播和死代码消除两条Pass如何逐步优化电路。
  1. class FirrtlModule:用类模拟一个FIRRTL模块,lines存储IR语句。
  2. def add_line(self, line):添加一条“连线语句”。
  3. const_map[dst] = int(src)把常数存入符号表。
  4. if len(tokens) == 3 and ...识别 a + b 形式并折叠。
  5. new_lines.append(dst + " <- " + folded)用常数替换原表达式。
  6. def dead_code_eliminate(self, outputs):根据输出集合去除无用语句。
  7. for token in src.replace(...).split():提取右侧表达式中用到的信号名。
  8. if dst in used or dst in outputs:只在被使用或作为输出时保留。

常见误区

误区我平时写Chisel直接就能得到Verilog,说明FIRRTL只是Scala压出来的一个中间文件,无所谓。

正解FIRRTL恰恰是Chisel编译流程的核心。设计者在Chisel里写的是“草稿”,所有真正可交付的Verilog都要从FIRRTL经过编译优化而来。如果绕过FIRRTL,就丢掉了类型检查、常数传播、内存抽象等大量高级优化机会,就像出版社跳过标准语翻译直接手工排版几十种外文。

为何常见:日常开发中,IDE和构建工具自动完成IR生成,用户只看到最后的.v文件,容易产生“中间层可有可无”的错觉。

误区FIRRTL文件里也有module、wire、reg,看起来和Verilog很接近,应该只是Verilog的另一种写法。

正解FIRRTL比Verilog严格得多,它有显式的类型系统、唯一的驱动源、最后连接生效语义和分层内存抽象,而这些Verilog都没有或只能靠约定。正是这些额外信息让编译器能够在IR层安全地做高级优化,就像标准世界语有严格语法,才能承受各国译者的二次转译。

为何常见:人眼看到相似关键词就划等号,忽略了两者在抽象层级和语义完备性上的巨大差异。

误区编译器既然有调度器,我自己写Pass时随便声明一下,运行顺序乱不了。

正解依赖图调度依赖的是每个Pass声明的依赖关系。如果声明漏掉、写错,或者Pass对IR形态的假设不正确,编译器可能把类型检查后的Pass放到没有检查的IR上运行,后果是难以定位的语义错误。你可以把它类比成编辑部:只有每个流程都正确登记“前置工序”,项目经理才能排出合理顺序。

为何常见:看到框架自动排序,误以为它有魔法,忽略了顺序信息正是由开发者提供的。

误区既然都叫FIRRTL,中间层应该只是优化与否的区别,内容大概相同。

正解High层还保留类似内存统一抽象、字段读写、多条隐式扩展规则,而Low层已经把这些全部展开成具体的线网和寄存器连接,是真正可以直接对应RTL的表示。每一层IR都少了一类高级语法,就像书籍的“初译稿”和“终校稿”篇幅和措辞已大不相同。

为何常见:IR文件被构建流程隐藏,很少有机会同时打开两层对比,因此容易认为只是版本号不同。

误区真正决定面积和时序的是逻辑综合工具,FIRRTL阶段做个把Pass没啥用。

正解综合工具做的是门级优化,而FIRRTL Pass处理的是结构级问题,如常数乘法器变移位、消除不被使用的信号、合并重复逻辑。这些高层变换如果在门级再做,往往为时已晚,白白浪费大量综合时间和面积。相当于编辑应在标准语阶段删除冗余章节,而非等外文版排版完再返工。

为何常见:硬件行业长期以综合、布局布线为主要优化战场,IR层优化是Chisel生态兴起后才被广泛关注的新能力。

面试题

Q1. FIRRTL的全称是?

单选题 · easy

  • A.Flexible Intermediate Representation for RTL← 正确答案
  • B.Final Integrated Register Transfer Logic
  • C.Fast IR for Radix-2 Tree Layout
  • D.Formal Instruction Representation for RTL

答案:A。FIRRTL是“Flexible Intermediate Representation for RTL”,即灵活的RTL中间表示。它强调灵活性和中间层定位,而不是“最终集成”或“快速算法”。就像标准世界语必须能适应各种语言,FIRRTL的“灵活”体现在多Pass可扩展上。

Q2. 关于FIRRTL在Chisel生态中的位置,正确的是?

单选题 · mid

  • A.Chisel直接生成的最终网表
  • B.Chisel与Verilog间的中间层← 正确答案
  • C.Verilog的一种超集语法
  • D.综合器内嵌的优化算法

答案:B。Chisel生成的是FIRRTL,再由FIRRTL编译器生成Verilog,因此它是中间层而非最终网表。用出版流程类比,Chisel是作者的母语草稿,FIRRTL是标准世界语,Verilog才是目标语言版。

Q3. FIRRTL模块的组成,最准确的是?

单选题 · mid

  • A.模块名、端口表、内部语句序列← 正确答案
  • B.模块名、时钟信号、电阻网络
  • C.端口表、寄存器堆、访存单元
  • D.语句序列、仿真模型、测试平台

答案:A。FIRRTL模块由模块名、端口向量和语句向量构成,其中的语句包括connect、when、reg等。电阻网络属于物理设计,仿真模型和测试平台不属于模块内部结构。可以类比小说章节的标题、章节开头的人物清单和正文段落。

Q4. 下列哪些属于FIRRTL电路中的基本表示?

多选题 · mid

  • A.线网与信号连接关系← 正确答案
  • B.寄存器和状态更新← 正确答案
  • C.场效应管栅极寄生电容
  • D.组合逻辑与条件控制分支← 正确答案

答案:ABD。FIRRTL表示的是RTL级电路:线网、寄存器、组合逻辑和when条件分支都在其中。场效应管栅极电容是晶体管级物理参数,不属于IR范畴,就像出版流程里的油墨成分不属于标准语语法一样。

Q5. 高层FIRRTL与低层FIRRTL的主要区别是?

单选题 · mid

  • A.高层更抽象,低层更贴近RTL← 正确答案
  • B.高层是Scala,低层是Python
  • C.高层只能仿真,低层只能综合
  • D.高层面向模拟,低层面向数字

答案:A。High FIRRTL保留内存抽象、字段读写等高层次语义,Low FIRRTL则展开成接近RTL的朴素表达。两者都是IR文件,与语言实现、仿综合无关。对应出版流程,一个是初译稿,一个是排版终校稿。

Q6. 在Pass依赖图中,若Pass A产生Pass B需要的元数据,则正确的执行顺序是?

单选题 · hard

  • A.B必须在A之前运行
  • B.A必须在B之前运行← 正确答案
  • C.A和B可并行运行
  • D.二者顺序不影响结果

答案:B。依赖图的意义就是强制保证“前置Pass先运行”。如果B依赖A的元数据,那么顺序只能是A→B。这类似于排版必须在校对完成之后进行,否则标点错误会直接进入成品。

Q7. 自定义Transform时,以下哪些做法是正确的?

多选题 · hard

  • A.遍历IR并返回新的电路← 正确答案
  • B.修改Verilog文件再重解析
  • C.在依赖列表声明前置依赖← 正确答案
  • D.直接跳过类型检查再变换

答案:AC。自定义Pass应在IR层面遍历并返回新电路,同时声明自己依赖的前置Pass。直接修改Verilog再重解析绕过了IR框架,跳过类型检查则会让后续Pass面临不一致的IR。这就像编辑必须在标准语版本上修改并登记工序,而不是去改外文版。

Q8. FIRRTL从CHIRRTL降低到Low层时,通常会做哪些事?

单选题 · hard

  • A.隐藏类型信息以缩小体积
  • B.移除内存端口包装等高层抽象← 正确答案
  • C.把所有端口展平为位向量
  • D.将组合逻辑转成状态机

答案:B。降低的核心是“消除高层抽象”。例如把统一内存抽象展开成具体的读/写端口信号,让IR逐步贴近RTL。类型信息不会隐藏,端口不会一律展平为位向量,组合逻辑更不会变状态机。就像编辑把“添加一段:主角回忆”改为具体的时间线插叙。

Q9. 哪些优化可以在FIRRTL Pass中完成?

多选题 · easy

  • A.常数传播与折叠← 正确答案
  • B.冗余死代码消除← 正确答案
  • C.晶体管尺寸调整
  • D.Mux常量选择化简← 正确答案

答案:ABD。常数传播、死代码消除和Mux化简都是典型的IR级优化。晶体管尺寸调整属于物理设计阶段,与IR无关,相当于调整纸张切边和印刷用量是印刷厂的问题,和文字编辑无关。

Q10. firtool工具的主要作用是什么?

单选题 · mid

  • A.编译FIRRTL并输出Verilog← 正确答案
  • B.将Verilog源码编译为机器码
  • C.将FIRRTL还原成Scala源码
  • D.直接产出FPGA比特流文件

答案:A。firtool是FIRRTL编译器的主入口,它读取Chisel生成的.fir文件,经过一系列Pass后生成Verilog。它不编译机器码、不还原Scala,也不直接生成FPGA比特流,就像排版系统只负责把标准语排成书稿,不负责印刷和发行。