# 【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
```