# 【1】Scala 和 Chisel 語法簡記 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. scala 語法 2. chisel 語法 3. 範例1: FIR filter --- # 隨筆 * 程式碼重複使用的能力是 Chisel 的優點之一 * Chisel 是一種 HCL(Hardware Construction Language),它被設計用來**生成** HDL(Hardware Description Language) * Verilog 則是一種 HDL,它可以描述數位電路的功能與行為 --- # 名詞解釋 * DSL: Domain-specific language。 Scala 就是託管 DSL 的良好語言 --- # scala 語法 * scala 的變數型態通常是宣告在右邊 ```scala= // 變數的型態定義 val max: Long = (1 << counterBits) - 1 // 函數的輸入型態和回傳值型態定義 def myMethod(count: Int, wrap: Boolean, wrapValue: Int = 24): Unit = { ... } ``` ## var 和 val * 前面有提到 ```val``` 是定義常數。相反的,```var``` 是用來定義變數 * chisel 希望盡可能使用 ```val``` 來定義,需要修改時再使用 ```:=``` 修改值。這樣是為了增加除錯的能力 ## 條件句 * 這邊只記錄比較特別的語法 ```scala= val likelyCharactersSet = if (alphabet.length == 26) "english" else "not english" println(likelyCharactersSet) ``` ## def * 跟 python 蠻像的 * 在 def 內定義的 def 只能在該 def 內使用 ## list * 直接看舉例 ```scala= val x = 7 val y = 14 val list1 = List(1, 2, 3) val list2 = x :: y :: y :: Nil // An alternate notation for assembling a list val list3 = list1 ++ list2 // Appends the second list to the first list val m = list2.length val s = list2.size val headOfList = list1.head // Gets the first element of the list val restOfList = list1.tail // Get a new list with first element removed val third = list1(2) // Gets the third element of a list (0-indexed) ``` ```shell // 輸出 x: Int = 7 y: Int = 14 list1: List[Int] = List(1, 2, 3) list2: List[Int] = List(7, 14, 14) list3: List[Int] = List(1, 2, 3, 7, 14, 14) m: Int = 3 s: Int = 3 headOfList: Int = 1 restOfList: List[Int] = List(2, 3) third: Int = 3 ``` ## for * 第一種 ```scala= for (i <- 0 to 7) { print(i + " ") } println() ``` ```shell 0 1 2 3 4 5 6 7 ``` * 第二種 ```scala= for (i <- 0 until 7) { print(i + " ") } println() ``` ```shell 0 1 2 3 4 5 6 ``` * 第三種 ```scala= for(i <- 0 to 10 by 2) { print(i + " ") } println() ``` ```shell 0 2 4 6 8 10 ``` --- # chisel 語法 * ```Module``` 是所有硬體模組都必須擴充的內建 Chisel 類別。 * Module 的輸入與輸出由特殊的 ```io val``` 定義。它必須被命名為 io 並且是一個 IO 物件或實例 ```scala= val io = IO(new Bundle { val in = Input(...) val out = Output(...) }) ``` * 使用 getVerilog() 可以將 chisel 構造好的硬體轉譯成 Verilog。這個過程稱之為 "elaboration"(闡述) * 使用 getFirrtl() 可以將 chisel 構造好的硬體轉譯成 getFirrtl。Firrtl 是 chisel 轉譯成 verilog 前的中間描述式 ## 硬體測試 * ```poke```: 用來設定測試輸入 * ```expect```: 用來**判斷**測試輸出 * ```peek```: 用來取得測試輸出 ```scala= // Scala Code: `test` runs the unit test. // test takes a user Module and has a code block that applies pokes and expects to the // circuit under test (c) test(new Passthrough()) { c => c.io.in.poke(0.U) // Set our input to value 0 c.io.out.expect(0.U) // Assert that the output correctly has 0 c.io.in.poke(1.U) // Set our input to value 1 c.io.out.expect(1.U) // Assert that the output correctly has 1 c.io.in.poke(2.U) // Set our input to value 2 c.io.out.expect(2.U) // Assert that the output correctly has 2 } println("SUCCESS!!") // Scala Code: if we get here, our tests passed! ``` * 可以在 chisel module 中插入 C 的 printf 用以在測試時確認輸出 ```scala= class PrintingModule extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) io.out := io.in printf("Print during simulation: Input is %d\n", io.in) // chisel printf has its own string interpolator too printf(p"Print during simulation: IO is $io\n") println(s"Print during generation: Input is ${io.in}") } test(new PrintingModule ) { c => c.io.in.poke(3.U) c.clock.step(5) // circuit will print println(s"Print during testing: Input is ${c.io.in.peek()}") } ``` ```shell Elaborating design... Print during generation: Input is UInt<4>(IO in unelaborated PrintingModule) Done elaborating. Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during testing: Input is UInt<4>(3) Print during simulation: Input is 0 Print during simulation: IO is AnonymousBundle(in -> 0, out -> 0) test PrintingModule Success: 0 tests passed in 7 cycles in 0.008715 seconds 803.24 Hz ``` --- # 範例1: FIR filter ## MovingAverage3 定義 * UInt: chisel 提供的變數型態 * Input(), Output(): 提供**有方向**的 port 定義 * ```.W```: 用於定義位寬 * ```.U```: 用於將 Scala 整數轉換為 Chisel 的硬體整數 * RegNext(): reg 函數,可以在下一個 clock 將輸入吐出 ```scala= // 3-point moving average implemented in the style of a FIR filter class MovingAverage3(bitWidth: Int) extends Module { // 使用 Bundle(類似 C 的 struct) 定義輸入輸出介面 val io = IO(new Bundle { val in = Input(UInt(bitWidth.W)) val out = Output(UInt(bitWidth.W)) }) val z1 = RegNext(io.in) // Create a register whose input is connected to the argument io.in val z2 = RegNext(z1) // Create a register whose input is connected to the argument z1 io.out := (io.in * 1.U) + (z1 * 1.U) + (z2 * 1.U) // `1.U` is an unsigned literal with value 1 } ``` ## 視覺化 RTL code * 在視覺化的呈現中 * register: 以**金色**呈現 ```scala= // same 3-point moving average filter as before visualize(() => new MovingAverage3(8)) ``` ![image](https://hackmd.io/_uploads/r1A-0i-Ua.png) ## 序列化輸入輸出的 FIR filter * chisel 比 verilog 強大的地方就是 Module 不是固定的硬體,它可以被設計成根據序列輸入產生對應的硬體 * 例如我們設計了可以根據輸入變化的硬體 * 因為輸入可以是可變長度的 * until: scala 的迴圈語法之一。[參考資料](https://www.delftstack.com/zh-tw/howto/scala/foreach-loop-in-scala/#google_vignette) * Vec(): Chisel 集合類型的集合類型,類似矩陣,可以以矩陣的形式儲存相同類型的物件(它只能包含 Chisel 硬體元素)。但**它並不是真實儲存於記憶體的矩陣**,實際上,Vec() 收集輸入的 wire,就有點類似集線器(?) * ```=```: 用於 val 定義,初始化變數的狀態 * ```:=```: 初始化後的 wire 不能再使用 ```=``` 賦值,引此 chisel 提供 ```:=``` 的重新賦值方法。[參考資料](https://blog.csdn.net/Frederick_Bala/article/details/108375915) * ```<-```: scala 語法。用於迴圈中計數變量赋值 * ```List.tabulate()()```: scala list 語法。第一個參數接收**列表**,第二個參數相當於一個或多個 for 迴圈。[參考資料](https://blog.csdn.net/weixin_44941795/article/details/104991875) * ```.reduce()```: scala list 語法。取得集合(Array、List 等)中的所有元素,並使用二元運算將它們組合起來以產生單一值。 ```scala= // Generalized FIR filter parameterized by the convolution coefficients class FirFilter(bitWidth: Int, coeffs: Seq[UInt]) extends Module { // 定義輸入輸出介面 val io = IO(new Bundle { val in = Input(UInt(bitWidth.W)) val out = Output(UInt()) }) // Create the serial-in, parallel-out shift register // chisel 強大的地方 val zs = Reg(Vec(coeffs.length, UInt(bitWidth.W))) zs(0) := io.in for (i <- 1 until coeffs.length) { zs(i) := zs(i-1) } // Do the multiplies val products = VecInit.tabulate(coeffs.length)(i => zs(i) * coeffs(i)) // Sum up the products io.out := products.reduce(_ +& _) } ``` * 因此輸入不同輸入就能產生不同的硬體 ```scala= // same 3-point moving average filter as before visualize(() => new FirFilter(8, Seq(1.U, 1.U, 1.U))) ``` ![image](https://hackmd.io/_uploads/SkW9L2WUa.png) ```scala= // 5-point FIR filter with a triangle impulse response visualize(() => new FirFilter(8, Seq(1.U, 2.U, 3.U, 2.U, 1.U))) ``` ![image](https://hackmd.io/_uploads/HyeoLh-LT.png) --- # 參考 * [Chisel-bootcamp](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master)