小林是位小说家,想把故事推向全球。出版社要求:先用他习惯的中文写自由草稿,再翻译成一种语法严格的“标准语”,编辑们在这个标准语版本上做删改、统一术语、压缩篇幅,最后才转成英文、法文、德文出版。这样做的好处是巨大的:所有修改只需在“标准语”上做一次,各国版本自动同步正确。 硬件设计也是同样的故事。你用Chisel描述处理器、AI加速器时,写下的不是最终交付物,而是一份“自由草稿”。它会被翻译成硬件世界的“标准语”——FIRRTL。FIRRTL(Flexible Intermediate Representation for RTL)位于Chisel与Verilog之间,承担着“可优化、可变换、多后端”的中间层角色。等所有优化和变换完成,再由编译器把它转成Verilog甚至门级网表。 读完本文,你会像走进出版社编辑部,看清各个环节如何协作:从FIRRTL语法、三层IR,到Pass机制和自定义Pass。掌握了这条流水线,你就能像资深编辑一样,为硬件设计定制属于自己的“出版工艺”。
对AI从业者的价值:理解FIRRTL就能看穿Chisel编译器的内部机理,并能编写自定义编译Pass做硬件定制优化,这是构建敏捷芯片设计工具链和AI专用加速器的核心能力。
目录
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 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存在的最大价值。
- lowering(降低)把高层次的Chisel表示拆成更底层的IR语句,就像出版社把自由中文草稿改写成语法固定的标准语。
- CHIRRTLChisel直接吐出的初始IR,还保留很多高层抽象,相当于第一版粗略翻译稿。
- expansion(展开)把隐式类型、隐式连接全部显式化,得到High层IR,相当于通读后补齐所有线索。
- optimization(优化)在Mid层运行各种Pass,统一修改不同章节,相当于编辑在标准语上删冗调序。
- emitting(生成)Low层IR已经非常接近RTL,直接转成Verilog,相当于标准语终稿交到各国译者手中。
核心语法:标准语的固定句型
标准世界语有固定词序和词尾,FIRRTL也有自己的“固定句型”。如果打开一个FIRRTL文件,你看到的不是电路连线图,而是一篇结构清晰的文档:module(模块)是文章的章节,port(端口)是章节封面上标注的输入输出接口,statement(语句)是组成内容的句子,when(条件)则是“如果……那么……”的条件从句。
FIRRTL的模块语法非常简洁。每个模块都有一组端口,端口说明方向(input/output)和类型(Clock时钟、UInt无符号整数、SInt有符号整数等)。模块内部用connect语句把信号连接起来,用reg声明寄存器,用when表达条件行为。下面是一个微型示例:
FIRRTL和Verilog最大的不同在于“严格”:每个线网的驱动源唯一,多处赋值时采用“最后连接生效”(last connect)语义,条件块之间不会出现看不见的竞争。模块结构可以形式化地用三元组表示:
小林在标准语版本上改稿时,会特别留意语法错误;FIRRTL的严格语法也在编译早期就拦截大量问题。这种“用固定句型把意思说死”的风格,让后续每个Pass都能放心地修改电路,而不用担心理解偏差。
- 模块名 \(m\)模块的唯一标识,相当于文章的章节标题。
- 端口向量 \(\vec{p}\)按顺序列出所有输入输出端口,定义模块与外界的通信接口。
- 语句向量 \(\vec{s}\)模块内所有connect/when/reg等语句的有序集合,构成模块的具体行为。
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里大改特改。小林把稿子交给编辑部时最安心的一点是:每个情节都有明确来源,不会因为版本混乱而出现“一句话有两个作者”的灾难。
- 信号名 \(n\)需要被求值的那个线网/节点/寄存器,像是故事中的角色。
- 最后连接 \(\mathrm{last}(n)\)在同一作用域内最后一次给 \(n\) 赋值的连接语句,相当于小说的最终定稿。
- 源表达式 \(\mathrm{src}(\cdot)\)连接语句右侧的驱动值,角色最终听到的那句话。
三层IR:从初稿到印刷稿
编辑部拿到小林的初译稿后,会做三次精修:第一次通读确保意思完整(High),第二次压缩句式去掉冗余表达(Middle),第三次按印刷规范排版(Low)。FIRRTL把这个过程固化成了三层IR,让每一次变换都只做少量而可控的工作。
CHIRRTL是Chisel直接生成的芯片级IR,包含大量高层语义,比如内存抽象、字段读写、隐式类型。High FIRRTL把隐式类型和连接都补充完整;Middle FIRRTL会消除复杂的存储抽象,把内存端口拆成具体的读写信号;Low FIRRTL则进一步展开节点,把各种语法糖还原成最朴素的连接和寄存器更新,最终能直接对应RTL。
这种分层设计意义重大:高层IR方便做语义级优化,低层IR方便做结构级变换和Verilog生成。每一层都是对上一层的精确降低,形式上就是一系列变换函数:
小林看着编辑部的三次精修流程,终于明白为什么公司很少直接拿初译稿去排版。逐层精修让他可以在不同阶段提出不同要求——前期改剧情,后期改标点;硬件设计者也一样:在正确的那一层做正确的事,才能既高效又安全。
- 当前层 \(F_i\)i等于CHIRRTL、High或Mid时,分别表示当前正在处理的IR层。
- 降低变换 \(\mathcal{L}_i\)把一个IR层转成下一层IR的具体Pass,相当于一道编辑工序。
- 下一层 \(F_{i+1}\)降低后得到的更偏RTL风格的IR,继续等待下一道工序。
编译Pass机制:编辑部的流程管理
编辑部最怕漫无目的地乱改,所以每道工序都有标准作业程序:先核对事实,再统一措辞,最后调整排版。FIRRTL编译器内部也有一套流程管理机制,把所有修改电路的工序统称为Pass,每个Pass只做一种变换,并通过依赖关系排成流水线。
在FIRRTL编译框架中,Pass被抽象成Transform接口:输入FirrtlCircuit,输出FirrtlCircuit。编译器通过依赖图决定Pass的执行顺序,只有当某个Pass在元数据/IR形态上依赖前置结果时,后者才允许运行。还有Token机制:一个Pass可以要求编译器先前置运行它需要的其他Pass,就像编辑器在排版前强制要求先完成校对。
依赖关系可以用有向图建模:
有了这套流程管理,小林再也不用担心某个热心的编辑跳过校对直接改排版。对于编译开发者来说,依赖图就是编辑部的SOP看板,让成百上千个Pass能像齿轮一样精确咬合,稳定运行在每一次硬件构建中。
- Pass集合 \(P\)所有可运行的编译Pass,等价于编辑部里所有标准工序。
- 依赖边集合 \(E\)若两条工序之间存在先后要求,就在图中添加一条有向边。
- 有向边 \(p_i \to p_j\)表示必须先完成 \(p_i\),才能执行 \(p_j\),防止编辑顺序颠倒。
自定义Pass:写一条自己的编辑规则
小林写了本侦探小说,发现每位编辑都喜欢把“其实”改成“事实上”,很烦。于是他向出版社申请:让我注册一条自己的编辑规则吧!FIRRTL正好支持这种扩展——你可以编写自定义Pass,像注册一条语法规则一样插入编译流水线。
在Scala中编写自定义Pass,通常继承Transform类,重写execute方法:接收CircuitState,遍历IR中的Module、Connect、When等节点,执行想要的修改,最后返回新的CircuitState。若在依赖列表里声明“我需要在类型检查之后运行”,编译器就会自动排序。这样既能安全地修改IR,又不会破坏其他Pass。
自定义Pass本质上是对电路的语义保持变换。它把输入电路映射到输出电路,但要求行为完全不变,只改变结构和代价:
小林注册的“删掉其实”规则上线后,所有英文版也同时受益。自定义Pass的威力正是如此:你只写一次变换逻辑,就能在所有目标工艺的生成流程中生效,真正把“作者意图”变成“编辑规范”。
- 变换函数 \(\Psi\)你定义的自定义Pass,把一份电路稿变成另一份电路稿。
- 输入电路 \(C\)Pass运行前的原始电路,相当于待编辑的稿件。
- 行为等价输出电路 \(C'\) 在功能上和 \(C\) 完全一致,只允许结构和性能变化。
集成编译流程:让标准语正式上线
出版社的各种规则最终要在统一排版系统中运行。FIRRTL生态的主要入口是firtool工具——一个用C++实现的快速编译器。它读取Chisel生成的.fir文件,像排版系统读取标准语文本一样,输出Verilog文件和相关注解。
在日常Chisel工程中,设计者写的Scala代码经过circt/firrtl插件,会自动生成FIRRTL文件;随后firtool接手,通过命令行选项指定优化级别、是否输出SystemVerilog、是否保留注释等。整个过程中,IR阶段对用户是透明的,但理解它有助于定位编译问题、调节优化行为。
firtool可以看成多个Pass的组合函数:
对小林来说,出版社统一排版系统让他只需交一份标准语稿,剩下的事全都自动化。硬件设计者面对firtool也有同样感受:写一次Chisel,就能为ASIC、FPGA等多个后端生成不同风格的结果。这种“一次编写、多处发布”的体验,正是中间表示带来的巨大红利。
- 单个Pass \(\pi_i\)编译流水线中的一道工序,例如类型检查、常数传播、Verilog生成。
- 复合运算 \(\circ\)表示函数按从右到左的顺序执行,前一个Pass的输出成为后一个Pass的输入。
- 整体firtool所有Pass复合后得到的完整编译器行为,等价于把标准语文本逐级变成目标语言版。
优化Pass实例:编辑部的实战战绩
编辑常做的优化是删掉所有“真的、其实、非常”,不改变意思但让书变薄。FIRRTL中也有一批类似的Pass,专门在IR阶段消除无用逻辑。我们来看两个最典型的例子:常数传播与Mux常量选择化简。
常数传播会顺着连接关系追踪常数。若一个信号被赋值为0,另一个信号是它与某值的与操作,Pass可以直接把结果替换为0,而不用真正生成一个与门电路。Mux优化则对选择端为常数的多路选择器做化简:选择端恒为1时,整个Mux就是数据端x。
这两类优化可以用重写规则表示:
小林发现,编辑删掉每一个“其实”后,书页数少了,读者却觉得更爽快。硬件优化Pass也一样:常数传播、死代码消除、Mux化简看起来不起眼,却在芯片面积和功耗上带来实实在在的收益。这就是在“标准世界语”阶段动手修改的魔力——一次优化,全世界受益。
- 与常数0求与任何信号和常量0做与运算都恒等于0,这是常数传播的基础规则之一。
- 化简结果为0IR阶段直接生成常量0,避免创建无用的与门。
- 选择端为1的Mux当Mux的选择信号恒为1时,输出永远等于x输入。
- 化简为x将三输入Mux替换为直接连线的x信号,减少一个多路选择器。
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 演示
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()
class FirrtlModule:用类模拟一个FIRRTL模块,lines存储IR语句。def add_line(self, line):添加一条“连线语句”。const_map[dst] = int(src)把常数存入符号表。if len(tokens) == 3 and ...识别a + b形式并折叠。new_lines.append(dst + " <- " + folded)用常数替换原表达式。def dead_code_eliminate(self, outputs):根据输出集合去除无用语句。for token in src.replace(...).split():提取右侧表达式中用到的信号名。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的全称是?
- 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生态中的位置,正确的是?
- A.Chisel直接生成的最终网表
- B.Chisel与Verilog间的中间层← 正确答案
- C.Verilog的一种超集语法
- D.综合器内嵌的优化算法
答案:B。Chisel生成的是FIRRTL,再由FIRRTL编译器生成Verilog,因此它是中间层而非最终网表。用出版流程类比,Chisel是作者的母语草稿,FIRRTL是标准世界语,Verilog才是目标语言版。
Q3. FIRRTL模块的组成,最准确的是?
- A.模块名、端口表、内部语句序列← 正确答案
- B.模块名、时钟信号、电阻网络
- C.端口表、寄存器堆、访存单元
- D.语句序列、仿真模型、测试平台
答案:A。FIRRTL模块由模块名、端口向量和语句向量构成,其中的语句包括connect、when、reg等。电阻网络属于物理设计,仿真模型和测试平台不属于模块内部结构。可以类比小说章节的标题、章节开头的人物清单和正文段落。
Q4. 下列哪些属于FIRRTL电路中的基本表示?
- A.线网与信号连接关系← 正确答案
- B.寄存器和状态更新← 正确答案
- C.场效应管栅极寄生电容
- D.组合逻辑与条件控制分支← 正确答案
答案:ABD。FIRRTL表示的是RTL级电路:线网、寄存器、组合逻辑和when条件分支都在其中。场效应管栅极电容是晶体管级物理参数,不属于IR范畴,就像出版流程里的油墨成分不属于标准语语法一样。
Q5. 高层FIRRTL与低层FIRRTL的主要区别是?
- A.高层更抽象,低层更贴近RTL← 正确答案
- B.高层是Scala,低层是Python
- C.高层只能仿真,低层只能综合
- D.高层面向模拟,低层面向数字
答案:A。High FIRRTL保留内存抽象、字段读写等高层次语义,Low FIRRTL则展开成接近RTL的朴素表达。两者都是IR文件,与语言实现、仿综合无关。对应出版流程,一个是初译稿,一个是排版终校稿。
Q6. 在Pass依赖图中,若Pass A产生Pass B需要的元数据,则正确的执行顺序是?
- A.B必须在A之前运行
- B.A必须在B之前运行← 正确答案
- C.A和B可并行运行
- D.二者顺序不影响结果
答案:B。依赖图的意义就是强制保证“前置Pass先运行”。如果B依赖A的元数据,那么顺序只能是A→B。这类似于排版必须在校对完成之后进行,否则标点错误会直接进入成品。
Q7. 自定义Transform时,以下哪些做法是正确的?
- A.遍历IR并返回新的电路← 正确答案
- B.修改Verilog文件再重解析
- C.在依赖列表声明前置依赖← 正确答案
- D.直接跳过类型检查再变换
答案:AC。自定义Pass应在IR层面遍历并返回新电路,同时声明自己依赖的前置Pass。直接修改Verilog再重解析绕过了IR框架,跳过类型检查则会让后续Pass面临不一致的IR。这就像编辑必须在标准语版本上修改并登记工序,而不是去改外文版。
Q8. FIRRTL从CHIRRTL降低到Low层时,通常会做哪些事?
- A.隐藏类型信息以缩小体积
- B.移除内存端口包装等高层抽象← 正确答案
- C.把所有端口展平为位向量
- D.将组合逻辑转成状态机
答案:B。降低的核心是“消除高层抽象”。例如把统一内存抽象展开成具体的读/写端口信号,让IR逐步贴近RTL。类型信息不会隐藏,端口不会一律展平为位向量,组合逻辑更不会变状态机。就像编辑把“添加一段:主角回忆”改为具体的时间线插叙。
Q9. 哪些优化可以在FIRRTL Pass中完成?
- A.常数传播与折叠← 正确答案
- B.冗余死代码消除← 正确答案
- C.晶体管尺寸调整
- D.Mux常量选择化简← 正确答案
答案:ABD。常数传播、死代码消除和Mux化简都是典型的IR级优化。晶体管尺寸调整属于物理设计阶段,与IR无关,相当于调整纸张切边和印刷用量是印刷厂的问题,和文字编辑无关。
Q10. firtool工具的主要作用是什么?
- A.编译FIRRTL并输出Verilog← 正确答案
- B.将Verilog源码编译为机器码
- C.将FIRRTL还原成Scala源码
- D.直接产出FPGA比特流文件
答案:A。firtool是FIRRTL编译器的主入口,它读取Chisel生成的.fir文件,经过一系列Pass后生成Verilog。它不编译机器码、不还原Scala,也不直接生成FPGA比特流,就像排版系统只负责把标准语排成书稿,不负责印刷和发行。