# 【7】FIRRTL 簡介 contributed by AgainTW --- # 章節 * [【1】Scala 和 Chisel 語法簡記](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/BJdW9obUa) * [【2】組合電路、序向電路和 Control Flow](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/HyWJBxmUa) * [【3】Generators](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/SJZ7kz7L6) * [【4】高階函式與設計](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/rk0Ckf7Lp) * [【5】物件導向設計](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/B1hB1aTLT) * [【6】Generators: Types](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/SJ8tka6U6) * [【7】FIRRTL 簡介](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/HJOjkppIT) * [【8】Chisel 到 Verilog 的中間表示](https://hackmd.io/@nfUUgsYRTGy81y5d9AYOyg/r1URy6aIT) --- # Outline 1. FIRRTL 介紹 2. FIRRTL 如何運作 --- # 隨筆 * Chisel 無法做到所有的事 --- # Chisel 與 FIRRTL ## 什麼是 FIRRTL * 全名: Flexible Internal Representation for RTL * Firrtl 是數位電路的中間表示(intermediate representation, IR) * 是一種抽象語法樹(Abstract Syntax Tree, AST) * 內部是節點樹的資料結構 * 該資料結構中沒有循環 * 不像一般程式語言是長字串 * 但這種抽象語法樹的**序列化**語法仍是人類可讀的 ## Chisel 與 FIRRTL * 根節點是 **Circuit** * 是任何 Firrtl 資料結構的根節點 * 只有一個電路 * 包含模組定義列表和頂級模組的名稱 * 它有 3 個子節點 * info: Info * 解析器可以在其中插入文件資訊(如行號和列號),也可以插入 NoInfo 標記 * Modules: Seq[DefModule] * 宣告 Firrtl 中的模組化單元 * main: String * 像是一些邏輯運算或是已宣告物件的引用 * FirrtlNode 宣告方式: ```scala Circuit(info: Info, modules: Seq[DefModule], main: String) ``` * 具體語法: ```scala circuit Adder: ... //List of modules ``` * 內存中表示: ```scala Circuit(NoInfo, Seq(...), "Adder") ``` ## Modules 節點 * 永遠不會直接嵌套(nested) * 宣告模組的實例具有自己的特定語法和 AST 表示 * 每個模組都有 * 一個名稱 * 一個連接埠清單 * 一個包含其實現的主體 * FirrtlNode 宣告方式: ```scala Module(info: Info, name: String, ports: Seq[Port], body: Stmt) extends DefModule ``` * 具體語法: ```scala module Adder: ... // list of ports ... // statements ``` * 內存中表示: ```scala Module(NoInfo, "Adder", Seq(...), ) ``` ### Port 節點 * 定義模組 io 的部分 * 具有 * 名稱 * 方向(輸入或輸出) * 類型 * FirrtlNode 宣告方式: ```scala class Port(info: Info, name: String, direction: Direction, tpe: Type) ``` * 具體語法: ```scala input x: UInt ``` * 內存中表示: ```scala Port(NoInfo, "x", INPUT, UIntType(UnknownWidth)) ``` ## Statement 節點 * 用於描述模組內的元件以及它們如何互動 * 常見的子節點 * Block of Statements: 一組陳述。通常用作模組宣告中的主體欄位 * Wire Declaration: 連線宣告 * Register Declaration: 暫存器宣告 * Connection: 表示從來源到接收器的定向連接 * 其他: 像是 DefMemory, DefNode, IsInvalid, Conditionally ### Wire 節點 * 包含名稱和類型 * 它既可以是來源(連接**自**),也可以是接收器(連接**至** ) * FirrtlNode 宣告方式: ```scala DefWire(info: Info, name: String, tpe: Type) ``` * 具體語法: ```scala wire w: UInt ``` * 內存中表示: ```scala DefWire(NoInfo, "w", UIntType(UnknownWidth)) ``` ### Register 節點 * 包含名稱、類型、時脈訊號、重設訊號和重設值 * FirrtlNode 宣告方式: ```scala DefRegister(info: Info, name: String, tpe: Type, clock: Expression, reset: Expression, init: Expression) ``` ### Connection 節點 * 它遵循最後連接語義(如 Chisel 中所述) * FirrtlNode 宣告方式: ```scala Connect(info: Info, loc: Expression, expr: Expression) ``` ### 其他節點 * [參考](https://github.com/chipsalliance/firrtl/blob/master-deprecated/src/main/scala/firrtl/ir/IR.scala) ## Expression 節點 * 宣告的組件或邏輯和算術運算的節點宣告 * 屬於 Expression 的節點有: * Reference: 已聲明元件的引用 * DoPrim: 加減乘除等隱式原語操作 * 其他: SubField, SubIndex, SubAccess, Mux ### Reference 節點 * 已聲明元件的引用,例如線路、暫存器或連接埠 * 它有一個名稱和類型欄位 * 但不包含指向實際聲明的指標,而僅包含字串形式的名稱 * FirrtlNode 宣告方式: ```scala Reference(name: String, tpe: Type) ``` ### DoPrim 節點 * 隱式原語操作,例如 Add, Sub, And, Or 或子字選擇(Bits) * 操作類型由該 ```op: PrimOp``` 欄位指示 * FirrtlNode 宣告方式: ```scala DoPrim(op: PrimOp, args: Seq[Expression], consts: Seq[BigInt], tpe: Type) ``` ### 其他節點 * [參考](https://github.com/chipsalliance/firrtl/blob/master-deprecated/src/main/scala/firrtl/ir/IR.scala) ## 連結 Chisel 與 FIRRTL * chisel 中序列化 FIRRTL 的輸出語法 ```scala println(chisel3.Driver.emit(() => new your_module())) ``` * chisel 中 FIRRTL AST 的取得方式 ```scala val firrtlSerialization = chisel3.Driver.emit(() => new your_module()) val firrtlAST = firrtl.Parser.parse(firrtlSerialization.split("\n").toIterator, Parser.GenInfo("file.fir")) println(firrtlAST) ``` * 範例 ```scala= class DelayBy2(width: Int) extends Module { val io = IO(new Bundle { val in = Input(UInt(width.W)) val out = Output(UInt(width.W)) }) val r0 = RegNext(io.in) val r1 = RegNext(r0) io.out := r1 } val chisel2verilog = getVerilog(new DelayBy2(4)) val firrtlSerialization = chisel3.Driver.emit(() => new DelayBy2(4)) val firrtlAST = firrtl.Parser.parse(firrtlSerialization.split("\n").toIterator, Parser.GenInfo("file.fir")) println(chisel2verilog) println(firrtlSerialization) println(firrtlAST) println(stringifyAST(firrtlAST)) ``` ```mips // verilog code module DelayBy2( input clock, input reset, input [3:0] io_in, output [3:0] io_out ); `ifdef RANDOMIZE_REG_INIT reg [31:0] _RAND_0; reg [31:0] _RAND_1; `endif // RANDOMIZE_REG_INIT reg [3:0] r0; // @[cmd4.sc 6:25] reg [3:0] r1; // @[cmd4.sc 7:21] assign io_out = r1; // @[cmd4.sc 8:12] always @(posedge clock) begin r0 <= io_in; // @[cmd4.sc 6:25] r1 <= r0; // @[cmd4.sc 7:21] end endmodule ``` ```scala // 序列化 FIRRTL circuit DelayBy2 : module DelayBy2 : input clock : Clock input reset : UInt<1> output io : { flip in : UInt<4>, out : UInt<4>} reg r0 : UInt, clock with : reset => (UInt<1>("h0"), r0) @[cmd4.sc 6:25] r0 <= io.in @[cmd4.sc 6:25] reg r1 : UInt, clock with : reset => (UInt<1>("h0"), r1) @[cmd4.sc 7:21] r1 <= r0 @[cmd4.sc 7:21] io.out <= r1 @[cmd4.sc 8:12] ``` ```shell // FIRRTL AST Circuit( @[file.fir 1:0],ArrayBuffer(Module( @[file.fir 2:2],DelayBy2,ArrayBuffer(Port( @[file.fir 3:4],clock,Input,ClockType), Port( @[file.fir 4:4],reset,Input,UIntType(IntWidth(1))), Port( @[file.fir 5:4],io,Output,BundleType(ArrayBuffer(Field(in,Flip,UIntType(IntWidth(4))), Field(out,Default,UIntType(IntWidth(4))))))),Block(ArrayBuffer(DefRegister( @[file.fir 7:4],r0,UIntType(UnknownWidth),Reference(clock,UnknownType,UnknownKind,UnknownFlow),UIntLiteral(0,IntWidth(1)),Reference(r0,UnknownType,UnknownKind,UnknownFlow)), Connect( @[file.fir 9:4],Reference(r0,UnknownType,UnknownKind,UnknownFlow),SubField(Reference(io,UnknownType,UnknownKind,UnknownFlow),in,UnknownType,UnknownFlow)), DefRegister( @[file.fir 10:4],r1,UIntType(UnknownWidth),Reference(clock,UnknownType,UnknownKind,UnknownFlow),UIntLiteral(0,IntWidth(1)),Reference(r1,UnknownType,UnknownKind,UnknownFlow)), Connect( @[file.fir 12:4],Reference(r1,UnknownType,UnknownKind,UnknownFlow),Reference(r0,UnknownType,UnknownKind,UnknownFlow)), Connect( @[file.fir 13:4],SubField(Reference(io,UnknownType,UnknownKind,UnknownFlow),out,UnknownType,UnknownFlow),Reference(r1,UnknownType,UnknownKind,UnknownFlow)))))),DelayBy2) ``` ```shell // FIRRTL AST 整理後 Circuit( | @[file.fir1:0], | ArrayBuffer( | | Module( | | | @[file.fir2:2], | | | DelayBy2, | | | ArrayBuffer( | | | | Port( | | | | | @[file.fir3:4], | | | | | clock, | | | | | Input, | | | | | ClockType | | | | ), | | | | Port( | | | | | @[file.fir4:4], | | | | | reset, | | | | | Input, | | | | | UIntType( | | | | | | IntWidth( | | | | | | | 1 | | | | | | ) | | | | | ) | | | | ), | | | | Port( | | | | | @[file.fir5:4], | | | | | io, | | | | | Output, | | | | | BundleType( | | | | | | ArrayBuffer( | | | | | | | Field( | | | | | | | | in, | | | | | | | | Flip, | | | | | | | | UIntType( | | | | | | | | | IntWidth( | | | | | | | | | | 4 | | | | | | | | | ) | | | | | | | | ) | | | | | | | ), | | | | | | | Field( | | | | | | | | out, | | | | | | | | Default, | | | | | | | | UIntType( | | | | | | | | | IntWidth( | | | | | | | | | | 4 | | | | | | | | | ) | | | | | | | | ) | | | | | | | ) | | | | | | ) | | | | | ) | | | | ) | | | ), | | | Block( | | | | ArrayBuffer( | | | | | DefRegister( | | | | | | @[file.fir7:4], | | | | | | r0, | | | | | | UIntType( | | | | | | | UnknownWidth | | | | | | ), | | | | | | Reference( | | | | | | | clock, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ), | | | | | | UIntLiteral( | | | | | | | 0, | | | | | | | IntWidth( | | | | | | | | 1 | | | | | | | ) | | | | | | ), | | | | | | Reference( | | | | | | | r0, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ) | | | | | ), | | | | | Connect( | | | | | | @[file.fir9:4], | | | | | | Reference( | | | | | | | r0, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ), | | | | | | SubField( | | | | | | | Reference( | | | | | | | | io, | | | | | | | | UnknownType, | | | | | | | | UnknownKind, | | | | | | | | UnknownFlow | | | | | | | ), | | | | | | | in, | | | | | | | UnknownType, | | | | | | | UnknownFlow | | | | | | ) | | | | | ), | | | | | DefRegister( | | | | | | @[file.fir10:4], | | | | | | r1, | | | | | | UIntType( | | | | | | | UnknownWidth | | | | | | ), | | | | | | Reference( | | | | | | | clock, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ), | | | | | | UIntLiteral( | | | | | | | 0, | | | | | | | IntWidth( | | | | | | | | 1 | | | | | | | ) | | | | | | ), | | | | | | Reference( | | | | | | | r1, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ) | | | | | ), | | | | | Connect( | | | | | | @[file.fir12:4], | | | | | | Reference( | | | | | | | r1, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ), | | | | | | Reference( | | | | | | | r0, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ) | | | | | ), | | | | | Connect( | | | | | | @[file.fir13:4], | | | | | | SubField( | | | | | | | Reference( | | | | | | | | io, | | | | | | | | UnknownType, | | | | | | | | UnknownKind, | | | | | | | | UnknownFlow | | | | | | | ), | | | | | | | out, | | | | | | | UnknownType, | | | | | | | UnknownFlow | | | | | | ), | | | | | | Reference( | | | | | | | r1, | | | | | | | UnknownType, | | | | | | | UnknownKind, | | | | | | | UnknownFlow | | | | | | ) | | | | | ) | | | | ) | | | ) | | ) | ), | DelayBy2 ) ``` --- # FIRRTL 如何運作 ## 了解 IR 子節點 * 為了將 chisel 轉換成 verilog,我們需要一種中間表示式的資料結構。 * 在 chisel 中,我們選擇 FIRRTL 作為 IR * 為了得到 IR,我們需要設計一個名為「語法分析器(Syntactic analysis)」的東西 * 就是一個不斷遍歷 IR 和 chisel 的函式 * 收集 chisel 的資訊或用新的 IR 節點取代原先的 IR 節點 * 一般的語法分析器是真的去讀 token,但我們有 scala 作為 DSL、作為 chisel 的載體 * 所以我們不需要設計詞法分析器(Lexical analysis)或甚至是自己設計詞法 * 因為 scala 強大的物件導向特性 * 所以我們只要設計一個不斷遍歷 IR 和 chisel 的函式即可 * 以下條列每個 IR 可能的子節點 | Node | Children | |:----------:|:---------------------------:| | Circuit | DefModule | | DefModule | Port, Statement | | Port | Type, Direction | | Statement | Statement, Expression, Type | | Expression | Expression, Type | | Type | Type, Width | | Width | | | Direction | | ## FIRRTL 語法分析器 ### Scala 中 IR 的實現 * 使用 Scala 中的字串序列來表示 IR 結構 * 範例中,s 是根節點為 ```Seq``` 和子節點為 ```"a"```, ```"b"```, ```"c"``` 的樹 ```scala val s = Seq("a", "b", "c") ``` ### Scala 字串序列的 map 操作 * 我們用來實現遍歷 Circuit 的函數 * Scala 中的字串序列提供 map 的操作 * 使用 map 我們可以實現以下操作 ```scala val s = Seq("a", "b", "c") def f(x: String): String = x + x // repeated declaration for clarity val t = s.map(f) println(t) // Seq("aa", "bb", "cc") ``` ### 實現 FIRRTL 的 map 操作 * IR 節點通常有多種類型的子節點。例如,Conditionally 有 Expression 和 Statement 等節點 * 在這種情況下,map 可根據其應用的函數取得匹配的"子節點",並且固定返回類型 * Scala 作為一種具有 EOP 特色的程式語言 * Expression: 不返回結果,執行只是為了產生副作用。通常由運算子和操作數組成 * Statements: 總是返回結果,通常根本沒有副作用 * 以表達式為導向的程式語言(Expression-Oriented Programming, EOP)有以下特色 * “Statements do not return results and are executed solely for their side effects, while expressions always return a result and often do not have side effects at all.” * 為了遍歷 Firrtl 樹,我們可以使用**前序遍歷**(常見)或是後序遍歷 * 以下範例簡單描述了 scala 中實現 FIRRTL 的 map 操作 * [詳細參考 IRLookup.scala](https://github.com/chipsalliance/firrtl/blob/1.6.x/src/main/scala/firrtl/analyses/IRLookup.scala) ```scala val c = Conditionally(info, e, s1, s2) // e: Expression, s1, s2: Statement, info: FileInfo def fExp(e: Expression): Expression = ... def fStmt(s: Statement): Statement = ... c.map(fExp) // Conditionally(fExp(e), s1, s2) c.map(fStmt) // Conditionally(e, fStmt(s1), fStmt(s2)) ``` --- # Question * --- # 參考 * [Chisel-bootcamp](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master) * [Scala spec](https://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html) * [firrtl Dialect](https://circt.llvm.org/docs/Dialects/FIRRTL/) * [0](https://github.com/jijingg/Spinal-bootcamp/blob/dev/6.1-env-chisel.ipynb) * [0](https://chipyard.readthedocs.io/en/stable/Customization/Firrtl-Transforms.html#where-to-add-transforms) * [0](https://github.com/chipsalliance/firrtl/blob/master-deprecated/src/main/scala/firrtl/transforms/IdentityTransform.scala) * [0](https://stackoverflow.com/questions/46794019/class-needs-to-be-abstract-since-method-in-trait-is-not-defined-error) * [0](https://github.com/chipsalliance/firrtl/blob/master-deprecated/src/main/scala/tutorial/lesson2-ir-fields/AnalyzeCircuit.scala) * [0](https://github.com/chipsalliance/firrtl/wiki/transform-writing-manual) ```scala ```