# 【2】組合電路、序向電路和 Control Flow 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. Combinational Logic 2. Control Flow 3. Sequential Logic 4. 範例2: FIR filter --- # 隨筆 * scala 變數和 chisel 變數 * 在 chisel 中宣告 scala 變數會被視作**變數**操作 * 在 chisel 中宣告 chisel 變數會被視作**硬體節點** * 因此 scala 變數和 chisel 變數不能互相運算,除非進行轉型 --- # 名詞解釋 * AXI-4 介面: * Advanced eXtensible Interface,高級可擴充接口 * 由 Arm 定義的接口介面協定 ![image](https://hackmd.io/_uploads/Byy9U4wUp.png) ![image](https://hackmd.io/_uploads/Bk1uLVw8p.png) ![image](https://hackmd.io/_uploads/Hy8SL4vI6.png) ![image](https://hackmd.io/_uploads/HkPKLEDI6.png) --- # 組合電路 ## 加減乘的範例 * Chisel 並無法自適應**數值資料**的位寬**(因為資料不是線,無法去計算自適應的位寬)**,因此要**注意線寬和暫存器位寬的宣告**以及**溢位的處理** * 在 Chisel 宣告乘法硬體時,其轉成 verilog 會直接使用 ```*```。這樣在合成硬體時會導致無法預期的硬體產生(就是成本或是時間會比預期的高),因此建議 call 其他矽智財供應商的硬體,或是自己刻一顆乘法器來用 * 教材範例 ```scala= // Module 宣告 class MyOperators extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_add = Output(UInt(4.W)) val out_sub = Output(UInt(4.W)) val out_mul = Output(UInt(4.W)) }) io.out_add := 1.U + 4.U io.out_sub := 2.U - 1.U io.out_mul := 4.U * 2.U } println(getVerilog(new MyOperators)) // test test(new MyOperators) {c => c.io.out_add.expect(5.U) c.io.out_sub.expect(1.U) c.io.out_mul.expect(8.U) } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module MyOperators( input clock, input reset, input [3:0] io_in, output [3:0] io_out_add, output [3:0] io_out_sub, output [3:0] io_out_mul ); wire [1:0] _T_3 = 2'h2 - 2'h1; // @[cmd9.sc 10:21] wire [4:0] _T_4 = 3'h4 * 2'h2; // @[cmd9.sc 11:21] assign io_out_add = 4'h5; // @[cmd9.sc 9:21] assign io_out_sub = {{2'd0}, _T_3}; // @[cmd9.sc 10:21] assign io_out_mul = _T_4[3:0]; // @[cmd9.sc 11:14] endmodule ``` ```shell Elaborating design... Done elaborating. test MyOperators Success: 0 tests passed in 2 cycles in 0.002838 seconds 704.78 Hz SUCCESS!! ``` * 修改教材範例導致溢位 ```scala= // Module 宣告 class MyOperators extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_add = Output(UInt(4.W)) val out_sub = Output(UInt(4.W)) val out_mul = Output(UInt(4.W)) }) io.out_add := 1.U + 4.U io.out_sub := 2.U - 1.U io.out_mul := 4.U * 16.U } println(getVerilog(new MyOperators)) // test test(new MyOperators) {c => c.io.out_add.expect(5.U) c.io.out_sub.expect(1.U) c.io.out_mul.expect(64.U) } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module MyOperators( input clock, input reset, input [3:0] io_in, output [3:0] io_out_add, output [3:0] io_out_sub, output [3:0] io_out_mul ); wire [1:0] _T_3 = 2'h2 - 2'h1; // @[cmd11.sc 10:21] wire [7:0] _T_4 = 3'h4 * 5'h10; // @[cmd11.sc 11:21] assign io_out_add = 4'h5; // @[cmd11.sc 9:21] assign io_out_sub = {{2'd0}, _T_3}; // @[cmd11.sc 10:21] assign io_out_mul = _T_4[3:0]; // @[cmd11.sc 11:14] endmodule ``` ```shell Elaborating design... Done elaborating. test MyOperators Success: 0 tests passed in 1 cycles in 0.004698 seconds 212.87 Hz // error block chiseltest.internal.FailedExpectException: io_out_mul=0 (0x0) did not equal expected=64 (0x40) (no lines in chisel_test_1702206887258) chiseltest.internal.TestEnvInterface.testerExpect(TestEnvInterface.scala:107) chiseltest.internal.TestEnvInterface.testerExpect$(TestEnvInterface.scala:70) chiseltest.RawTester.testerExpect(RawTester.scala:15) chiseltest.backends.treadle.TreadleBackend.expectBits(TreadleBackend.scala:92) chiseltest.package$testableData.expectWithStale(package.scala:166) chiseltest.package$testableData.expect(package.scala:193) ammonite.$sess.cmd7$Helper.$anonfun$res7_0$2(cmd7.sc:4) ammonite.$sess.cmd7$Helper.$anonfun$res7_0$2$adapted(cmd7.sc:1) chiseltest.backends.treadle.TreadleBackend.$anonfun$run$1(TreadleBackend.scala:152) chiseltest.internal.ThreadedBackend$TesterThread$$anon$1.$anonfun$run$1(ThreadedBackend.scala:507) chiseltest.backends.treadle.TreadleBackend.doTimescope(TreadleBackend.scala:109) chiseltest.internal.ThreadedBackend$TesterThread$$anon$1.run(ThreadedBackend.scala:507) java.lang.Thread.run(Thread.java:750) ``` ## Mux 和 Concatenation 的範例 * Chisel 提供 ```Mux(flag, if_true_value, else_value)``` 和 ```Cat(high_wire, low_wire)``` 來進行 Mux 和 Concatenation 的操作 * 話說我測試時發現在 Chisel 中,```Bool(true) != Bool(true)```。這件事真的超級奇怪的,後來我[參考這篇](https://stackoverflow.com/questions/68884285/why-booltrue-booltrue-in-chisel3)解決了問題 * 我的理解(不一定正確)是因為 Chisel 在轉譯到 verilog 時,```==``` 是考慮**內容**是否完全相同 * 因此```Bool(true)``` 和```Bool(true)```是不相等的是因為 Chisel 是去判斷兩者是否為**相同硬體**,儘管內容是完全相同的 * 而```Bool(true).litValue``` 和```Bool(true).litValue``` 就會是相等的,因為 Chisel 此時是去判斷兩者**內容是否相同** * 硬體(例如線)的判斷結果仍舊是硬體 * scala 的```if```只能用來判斷 scala 的 true/false,不能用來判斷 Chisel 的 Bool(true)/Bool(false) * 測試 ```scala= class test extends Module { val io = IO(new Bundle { val in1 = Input(UInt(16.W)) val in2 = Input(UInt(16.W)) val in3 = Input(UInt(16.W)) val out = Output(UInt(16.W)) }) println(io.in1 >= io.in2) println(io.in1 >= io.in2 && io.in1 >= io.in3) println(true.B) println(true.B == true.B) println(true.B.litValue == true.B.litValue) io.out := io.in3 } test(new test()) { c => c.io.out.peek() } ``` ```shell Elaborating design... Bool(OpResult in test) Bool(OpResult in test) Bool(true) false true Done elaborating. test test Success: 0 tests passed in 2 cycles in 0.002083 seconds 960.31 Hz ``` * 教材範例 * 使用 Mux() 函式 ```scala= class MyOperatorsTwo extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_mux = Output(UInt(4.W)) val out_cat = Output(UInt(4.W)) }) val s = true.B io.out_mux := Mux(s, 3.U, 0.U) // should return 3.U, since s is true io.out_cat := Cat(2.U, 1.U) // concatenates 2 (b10) with 1 (b1) to give 5 (101) } println(getVerilog(new MyOperatorsTwo)) test(new MyOperatorsTwo) { c => c.io.out_mux.expect(3.U) c.io.out_cat.expect(5.U) } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module MyOperatorsTwo( input clock, input reset, input [3:0] io_in, output [3:0] io_out_mux, output [3:0] io_out_cat ); assign io_out_mux = 4'h3; // @[cmd13.sc 9:20] assign io_out_cat = 4'h5; // @[Cat.scala 30:58] endmodule ``` ```shell Elaborating design... Done elaborating. test MyOperatorsTwo Success: 0 tests passed in 2 cycles in 0.001779 seconds 1124.08 Hz SUCCESS!! ``` * 使用 if 來測試生成結果 1 * 使用 true.B 去判斷 * 錯誤結果 ```scala= class MyOperatorsTwo extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_mux = Output(UInt(4.W)) }) val s = true.B if(s==true.B) io.out_mux := 3.U else io.out_mux := 0.U } println(getVerilog(new MyOperatorsTwo)) test(new MyOperatorsTwo) { c => c.io.out_mux.expect(3.U) } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module MyOperatorsTwo( input clock, input reset, input [3:0] io_in, output [3:0] io_out_mux ); assign io_out_mux = 4'h0; // @[cmd29.sc 9:19] endmodule ``` ```shell Elaborating design... Done elaborating. test MyOperatorsTwo Success: 0 tests passed in 1 cycles in 0.004702 seconds 212.67 Hz chiseltest.internal.FailedExpectException: io_out_mux=0 (0x0) did not equal expected=3 (0x3) (no lines in chisel_test_1702211003734) chiseltest.internal.TestEnvInterface.testerExpect(TestEnvInterface.scala:107) chiseltest.internal.TestEnvInterface.testerExpect$(TestEnvInterface.scala:70) chiseltest.RawTester.testerExpect(RawTester.scala:15) chiseltest.backends.treadle.TreadleBackend.expectBits(TreadleBackend.scala:92) chiseltest.package$testableData.expectWithStale(package.scala:166) chiseltest.package$testableData.expect(package.scala:193) ammonite.$sess.cmd29$Helper.$anonfun$res29_2$2(cmd29.sc:15) ammonite.$sess.cmd29$Helper.$anonfun$res29_2$2$adapted(cmd29.sc:14) chiseltest.backends.treadle.TreadleBackend.$anonfun$run$1(TreadleBackend.scala:152) chiseltest.internal.ThreadedBackend$TesterThread$$anon$1.$anonfun$run$1(ThreadedBackend.scala:507) chiseltest.backends.treadle.TreadleBackend.doTimescope(TreadleBackend.scala:109) chiseltest.internal.ThreadedBackend$TesterThread$$anon$1.run(ThreadedBackend.scala:507) java.lang.Thread.run(Thread.java:750) ``` * 使用 if 來測試生成結果 2 * 使用 true.B.litValue 去判斷 * 正確結果 ```scala= class MyOperatorsTwo extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_mux = Output(UInt(4.W)) }) val s = true.B if(s.litValue==true.B.litValue) io.out_mux := 3.U else io.out_mux := 0.U } println(getVerilog(new MyOperatorsTwo)) test(new MyOperatorsTwo) { c => c.io.out_mux.expect(3.U) } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module MyOperatorsTwo( input clock, input reset, input [3:0] io_in, output [3:0] io_out_mux ); assign io_out_mux = 4'h3; // @[cmd30.sc 8:46] endmodule ``` ```shell Elaborating design... Done elaborating. test MyOperatorsTwo Success: 0 tests passed in 2 cycles in 0.001322 seconds 1512.57 Hz SUCCESS!! ``` ## Exercise ### Ex.1 MAC * 參考解答 ```scala= class MAC extends Module { val io = IO(new Bundle { val in_a = Input(UInt(4.W)) val in_b = Input(UInt(4.W)) val in_c = Input(UInt(4.W)) val out = Output(UInt(8.W)) }) io.out := (io.in_a * io.in_b) + io.in_c } println(getVerilog(new MAC)) test(new MAC) { c => val cycles = 100 import scala.util.Random for (i <- 0 until cycles) { val in_a = Random.nextInt(16) val in_b = Random.nextInt(16) val in_c = Random.nextInt(16) c.io.in_a.poke(in_a.U) c.io.in_b.poke(in_b.U) c.io.in_c.poke(in_c.U) c.io.out.expect((in_a * in_b + in_c).U) } } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module MAC( input clock, input reset, input [3:0] io_in_a, input [3:0] io_in_b, input [3:0] io_in_c, output [7:0] io_out ); wire [7:0] _T = io_in_a * io_in_b; // @[cmd9.sc 9:24] wire [7:0] _GEN_0 = {{4'd0}, io_in_c}; // @[cmd9.sc 9:35] assign io_out = _T + _GEN_0; // @[cmd9.sc 9:35] endmodule ``` ```shell Elaborating design... Done elaborating. test MAC Success: 0 tests passed in 2 cycles in 0.014303 seconds 139.83 Hz SUCCESS!! ``` ### Ex.2 Arbiter * You will likely need binary operators to complete this exercise. * 此題考慮 fifo_queue_size = 1,但不主動儲存 data,僅控制輸入 fifo_data 的輸出方向。 * 當 fifo_valid = ture 代表 FIFO 有 data 進入,此時可以判斷 pe0_ready 和 pe1_ready 來決定輸出方向 * 如同常見的 verilog 設計,可以都先將 data 輸出到 pe0_data 和 pe1_data,再藉由 pe0_valid 和 pe1_valid 的控制來決定後端是否接收這筆 data * 參考解答 ```scala= class Arbiter extends Module { val io = IO(new Bundle { // FIFO val fifo_valid = Input(Bool()) val fifo_ready = Output(Bool()) val fifo_data = Input(UInt(16.W)) // PE0 val pe0_valid = Output(Bool()) val pe0_ready = Input(Bool()) val pe0_data = Output(UInt(16.W)) // PE1 val pe1_valid = Output(Bool()) val pe1_ready = Input(Bool()) val pe1_data = Output(UInt(16.W)) }) io.fifo_ready := io.pe0_ready || io.pe1_ready io.pe0_valid := io.fifo_valid && io.pe0_ready io.pe1_valid := io.fifo_valid && io.pe1_ready && !io.pe0_ready io.pe0_data := io.fifo_data io.pe1_data := io.fifo_data } println(getVerilog(new Arbiter)) test(new Arbiter) { c => import scala.util.Random val data = Random.nextInt(65536) c.io.fifo_data.poke(data.U) for (i <- 0 until 8) { c.io.fifo_valid.poke((((i >> 0) % 2) != 0).B) c.io.pe0_ready.poke((((i >> 1) % 2) != 0).B) c.io.pe1_ready.poke((((i >> 2) % 2) != 0).B) c.io.fifo_ready.expect((i > 1).B) c.io.pe0_valid.expect((i == 3 || i == 7).B) c.io.pe1_valid.expect((i == 5).B) if (i == 3 || i ==7) { c.io.pe0_data.expect((data).U) } else if (i == 5) { c.io.pe1_data.expect((data).U) } } } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module Arbiter( input clock, input reset, input io_fifo_valid, output io_fifo_ready, input [15:0] io_fifo_data, output io_pe0_valid, input io_pe0_ready, output [15:0] io_pe0_data, output io_pe1_valid, input io_pe1_ready, output [15:0] io_pe1_data ); assign io_fifo_ready = io_pe0_ready | io_pe1_ready; // @[cmd11.sc 19:35] assign io_pe0_valid = io_fifo_valid & io_pe0_ready; // @[cmd11.sc 20:35] assign io_pe0_data = io_fifo_data; // @[cmd11.sc 22:17] assign io_pe1_valid = io_fifo_valid & io_pe1_ready & ~io_pe0_ready; // @[cmd11.sc 21:51] assign io_pe1_data = io_fifo_data; // @[cmd11.sc 23:17] endmodule ``` ```shell Elaborating design... Done elaborating. test Arbiter Success: 0 tests passed in 2 cycles in 0.004412 seconds 453.28 Hz SUCCESS!! ``` * 如果是低功耗設計,可以將該硬體設計成只有確定輸出方向後才將 data 輸出到 pe 資料端 * 低功耗設計 ```scala= class Arbiter extends Module { val io = IO(new Bundle { // FIFO val fifo_valid = Input(Bool()) val fifo_ready = Output(Bool()) val fifo_data = Input(UInt(16.W)) // PE0 val pe0_valid = Output(Bool()) val pe0_ready = Input(Bool()) val pe0_data = Output(UInt(16.W)) // PE1 val pe1_valid = Output(Bool()) val pe1_ready = Input(Bool()) val pe1_data = Output(UInt(16.W)) }) io.fifo_ready := io.pe0_ready || io.pe1_ready io.pe0_valid := io.fifo_valid && io.pe0_ready io.pe1_valid := io.fifo_valid && io.pe1_ready && !io.pe0_ready io.pe0_data := Mux(io.pe0_valid, io.fifo_data, 0.U(16.W)) io.pe1_data := Mux(io.pe1_valid, io.fifo_data, 0.U(16.W)) } println(getVerilog(new Arbiter)) test(new Arbiter) { c => import scala.util.Random val data = Random.nextInt(65536) c.io.fifo_data.poke(data.U) for (i <- 0 until 8) { c.io.fifo_valid.poke((((i >> 0) % 2) != 0).B) c.io.pe0_ready.poke((((i >> 1) % 2) != 0).B) c.io.pe1_ready.poke((((i >> 2) % 2) != 0).B) c.io.fifo_ready.expect((i > 1).B) c.io.pe0_valid.expect((i == 3 || i == 7).B) c.io.pe1_valid.expect((i == 5).B) if (i == 3 || i ==7) { c.io.pe0_data.expect((data).U) } else if (i == 5) { c.io.pe1_data.expect((data).U) } } } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module Arbiter( input clock, input reset, input io_fifo_valid, output io_fifo_ready, input [15:0] io_fifo_data, output io_pe0_valid, input io_pe0_ready, output [15:0] io_pe0_data, output io_pe1_valid, input io_pe1_ready, output [15:0] io_pe1_data ); assign io_fifo_ready = io_pe0_ready | io_pe1_ready; // @[cmd12.sc 19:35] assign io_pe0_valid = io_fifo_valid & io_pe0_ready; // @[cmd12.sc 20:35] assign io_pe0_data = io_pe0_valid ? io_fifo_data : 16'h0; // @[cmd12.sc 22:23] assign io_pe1_valid = io_fifo_valid & io_pe1_ready & ~io_pe0_ready; // @[cmd12.sc 21:51] assign io_pe1_data = io_pe1_valid ? io_fifo_data : 16'h0; // @[cmd12.sc 23:23] endmodule ``` ```shell Elaborating design... Done elaborating. test Arbiter Success: 0 tests passed in 2 cycles in 0.005407 seconds 369.89 Hz SUCCESS!! ``` * 可以注意到參考解答和低功耗最大的不同就是: 低功耗在不使用時會設定成低電位(16'h0) ```shell # 參考解答 assign io_pe0_data = io_fifo_data; // @[cmd11.sc 22:17] assign io_pe1_data = io_fifo_data; // @[cmd11.sc 23:17] # 低功耗 assign io_pe0_data = io_pe0_valid ? io_fifo_data : 16'h0; // @[cmd12.sc 22:23] assign io_pe1_data = io_pe1_valid ? io_fifo_data : 16'h0; // @[cmd12.sc 23:23] ``` ### **參數化**加法器 * 參數化**構成**硬體是 chisel 獨特且強大的地方 * 當 x 和 y 是線(wire)時 * ```x + y```指令自適應的位寬為 max(w(x),w(y)) * ```x +% y```指令自適應的位寬為 max(w(x),w(y))+1 * 前面有提到當 x 和 y 是數值時會有無法自適應的問題 ```scala= class ParameterizedAdder(saturate: Boolean) extends Module { val io = IO(new Bundle { val in_a = Input(UInt(4.W)) val in_b = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) val sum = io.in_a +& io.in_b if (saturate) { io.out := Mux(sum > 15.U, 15.U, sum) } else { io.out := sum } } println(getVerilog(new ParameterizedAdder(true))) println(getVerilog(new ParameterizedAdder(false))) for (saturate <- Seq(true, false)) { test(new ParameterizedAdder(saturate)) { c => // 100 random tests val cycles = 100 import scala.util.Random import scala.math.min for (i <- 0 until cycles) { val in_a = Random.nextInt(16) val in_b = Random.nextInt(16) c.io.in_a.poke(in_a.U) c.io.in_b.poke(in_b.U) if (saturate) { c.io.out.expect(min(in_a + in_b, 15).U) } else { c.io.out.expect(((in_a + in_b) % 16).U) } } // ensure we test saturation vs. truncation c.io.in_a.poke(15.U) c.io.in_b.poke(15.U) if (saturate) { c.io.out.expect(15.U) } else { c.io.out.expect(14.U) } } } println("SUCCESS!!") ``` ```shell // saturate = true Elaborating design... Done elaborating. module ParameterizedAdder( input clock, input reset, input [3:0] io_in_a, input [3:0] io_in_b, output [3:0] io_out ); wire [4:0] sum = io_in_a + io_in_b; // @[cmd16.sc 8:21] wire [4:0] _T_1 = sum > 5'hf ? 5'hf : sum; // @[cmd16.sc 10:18] assign io_out = _T_1[3:0]; // @[cmd16.sc 10:12] endmodule ``` ```shell // saturate = false Elaborating design... Done elaborating. module ParameterizedAdder( input clock, input reset, input [3:0] io_in_a, input [3:0] io_in_b, output [3:0] io_out ); wire [4:0] sum = io_in_a + io_in_b; // @[cmd16.sc 8:21] assign io_out = sum[3:0]; // @[cmd16.sc 12:12] endmodule ``` ```shell Elaborating design... Done elaborating. test ParameterizedAdder Success: 0 tests passed in 2 cycles in 0.006756 seconds 296.04 Hz Elaborating design... Done elaborating. test ParameterizedAdder Success: 0 tests passed in 2 cycles in 0.003958 seconds 505.36 Hz SUCCESS!! ``` --- # Control Flow * chisel 和 verilog 在 control flow 設計上有比較大的分歧 * 0 ## Reassignment * 在電路生成中,如果發生重複指派的操作 * verilog 會生成一系列相互取代的電路 * chisel 會去精簡它再進行 verilog 的生成 ## when, elsewhen 和 otherwise * chisel 的主要邏輯格式 * 可以輸入硬體作為判斷條件 * 硬體之間的判斷結果也是硬體 * 和 scala 的 if 差別在於 * values are not returned by the blocks associated with ```when``` (教材寫的,我也沒看懂) * 我自己的理解就跟在 Mux() 那段實驗的結論一樣,chisel 的 ```when``` * 可以輸入**硬體**判斷 * 可以輸入 chisel true.B/false.B 判斷 * 無法輸入 scala true/false 判斷 * scala 的 ```if``` * 可以輸入 scala true/false 判斷 * 無法輸入**硬體**判斷 * 無法輸入 chisel true.B/false.B 判斷 * 語法 ```scala= when(someBooleanCondition) { // things to do when true }.elsewhen(someOtherBooleanCondition) { // things to do on this condition }.otherwise { // things to do if none of th boolean conditions are true } // 這樣的寫法也是合法的 when(select == 0.U){ } .elsewhen(select == 0.1){ } .otherwise{ } ``` ## Wire() * 就是 verilog 的 wire * 使用前需要宣告,並且需要宣告位寬和變數型態 ```scala= wire [15:0] row10 ``` ## Enum() * chisel 用來宣告狀態機的狀態的資料型態() * 例如有四種狀態: idle, coding, writing 和 grad。就可以用下列的語法宣告 ```scala= val idle :: coding :: writing :: grad :: Nil = Enum(4) ``` --- # 序向電路 * chisel 編譯是要看變數的型態,一般變數就長成組合電路,reg 型態就長在 always 裡面 * verilog 的組合電路就是蝦雞巴一次長 * verilog 的序向電路也只是當下狀態和下一個狀態的差別 * 用 verilog 的講法就是含暫存器電路,並且統常有 always 區塊 * Chisel 的 clock 是隱藏的並且作用於全部的電路,因此不需要另外撰寫 always 區塊去吃 clock * 但 Chisel 仍舊有能力生成非同步 clock 電路 * 在測試工具中 * step(1) 用來滴答一次 clock。同理,step(n)將滴答 n 次 clock ## Reg() * Chisel 的暫存器宣告函式 * 有一個區塊被劃分為在 ifdef Randomize,用來在模擬開始之前將暫存器初始化為某個隨機變量 * 預設 register 更新於 posedge clock * RegNext() 是可以自適應位寬版本的 * 合法: 宣告 reg 的大小和型態 Reg() ```scala val myReg = Reg(UInt(2.W)) ``` * 不合法: 2.U 本身就是硬體,不能作為宣告 reg 使用 ```scala val myReg = Reg(2.U) ``` * 但將值賦予給 reg 是合法的 ```scala myReg = 2.U ``` * 範例 ```scala= class RegisterModule extends Module { val io = IO(new Bundle { val in = Input(UInt(12.W)) val out = Output(UInt(12.W)) }) val register = Reg(UInt(12.W)) register := io.in + 1.U io.out := register } println(getVerilog(new RegisterModule())) test(new RegisterModule) { c => for (i <- 0 until 100) { c.io.in.poke(i.U) c.clock.step(1) c.io.out.expect((i + 1).U) } } println("SUCCESS!!") ``` ```shell Elaborating design... Done elaborating. module RegisterModule( input clock, input reset, input [11:0] io_in, output [11:0] io_out ); `ifdef RANDOMIZE_REG_INIT reg [31:0] _RAND_0; `endif // RANDOMIZE_REG_INIT reg [11:0] register; // @[cmd3.sc 7:21] assign io_out = register; // @[cmd3.sc 9:10] always @(posedge clock) begin register <= io_in + 12'h1; // @[cmd3.sc 8:21] end // Register and memory initialization `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif `ifndef RANDOM `define RANDOM $random `endif `ifdef RANDOMIZE_MEM_INIT integer initvar; `endif `ifndef SYNTHESIS `ifdef FIRRTL_BEFORE_INITIAL `FIRRTL_BEFORE_INITIAL `endif initial begin `ifdef RANDOMIZE `ifdef INIT_RANDOM `INIT_RANDOM `endif `ifndef VERILATOR `ifdef RANDOMIZE_DELAY #`RANDOMIZE_DELAY begin end `else #0.002 begin end `endif `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{`RANDOM}}; register = _RAND_0[11:0]; `endif // RANDOMIZE_REG_INIT `endif // RANDOMIZE end // initial `ifdef FIRRTL_AFTER_INITIAL `FIRRTL_AFTER_INITIAL `endif `endif // SYNTHESIS endmodule ``` ```shell Elaborating design... Done elaborating. test RegisterModule Success: 0 tests passed in 102 cycles in 0.028856 seconds 3534.82 Hz SUCCESS!! ``` * 稍微整理一下,去除用於初始化暫存器值的程式碼後我們得到 * 但很顯然目前生成的 verilog code 無法直接用於 HDL EDA 的使用 * 像是初始化暫存器的方式非常獨特,不確定是否能被大部分的 EDA 編譯 ```mips= module RegisterModule( input clock, input reset, input [11:0] io_in, output [11:0] io_out ); reg [31:0] _RAND_0; reg [11:0] register; // @[cmd3.sc 7:21] assign io_out = register; // @[cmd3.sc 9:10] always @(posedge clock) begin register <= io_in + 12'h1; // @[cmd3.sc 8:21] end endmodule ``` * 我自己的初始化方式則是 ```mips= module RegisterModule( input clock, input reset, input [11:0] io_in, output [11:0] io_out ); reg [11:0] register; assign io_out = register; always @(posedge clock) begin if(reset) register <= 12'h0; else register <= io_in + 12'h1; end endmodule ``` ## RegInit() * 宣告並初始化暫存器用的 * Reg() 初始值是隨機值 * RegNext() 是根據輸入吐值,因此沒有初始化的問題 * 語法 ```scala val myReg = RegInit(UInt(12.W), 0.U) val myReg = RegInit(0.U(12.W)) ``` * 範例(這次就比較像是上面我的 verilog 寫法了) * 但在呼叫重設之前,暫存器仍被初始化為隨機垃圾 ```scala class RegInitModule extends Module { val io = IO(new Bundle { val in = Input(UInt(12.W)) val out = Output(UInt(12.W)) }) val register = RegInit(0.U(12.W)) register := io.in + 1.U io.out := register } println(getVerilog(new RegInitModule)) ``` ```mips // 我已經整理過了 Elaborating design... Done elaborating. module RegInitModule( input clock, input reset, input [11:0] io_in, output [11:0] io_out ); reg [31:0] _RAND_0; reg [11:0] register; // @[cmd9.sc 7:25] wire [11:0] _T_1 = io_in + 12'h1; // @[cmd9.sc 8:21] assign io_out = register; // @[cmd9.sc 9:10] always @(posedge clock) begin if (reset) begin // @[cmd9.sc 7:25] register <= 12'h0; // @[cmd9.sc 7:25] end else begin register <= _T_1; // @[cmd9.sc 8:12] end end endmodule ``` ## 顯式時鐘與重設 * 尚不完全支援多時脈設計 * chisel 的 clock 有自己的型態 Clock() * 常用函式例如: ```withClock() {}```, ```withReset() {}``` 和 ```withClockAndReset() {}``` * 他們被宣告在```import chisel3.experimental.{withClock, withReset, withClockAndReset}``` * **吃相同時脈的顯式時鐘函式,在生成 verilog 時會長在同一個 always 內** * withClock(){} * 此函式內的 clock 由輸入控制 * withReset(){} * 此函式內的所有內容都將由輸入控制 reset * withClockAndReset() {} * 就......同時做兩件事 * 範例 ```scala= // we need to import multi-clock features import chisel3.experimental.{withClock, withReset, withClockAndReset} class ClockExamples extends Module { val io = IO(new Bundle { val in = Input(UInt(10.W)) val alternateReset = Input(Bool()) val alternateClock = Input(Clock()) val alternateClock2 = Input(Clock()) val outImplicit = Output(UInt()) val outAlternateReset = Output(UInt()) val outAlternateClock = Output(UInt()) val outAlternateBoth = Output(UInt()) }) val imp = RegInit(0.U(10.W)) imp := io.in io.outImplicit := imp withReset(io.alternateReset) { // everything in this scope with have alternateReset as the reset val altRst = RegInit(0.U(10.W)) altRst := io.in io.outAlternateReset := altRst } withClock(io.alternateClock) { val altClk = RegInit(0.U(10.W)) altClk := io.in io.outAlternateClock := altClk } withClockAndReset(io.alternateClock, io.alternateReset) { val alt = RegInit(0.U(10.W)) alt := io.in io.outAlternateBoth := alt } withClock(io.alternateClock2) { val altClk = RegInit(0.U(10.W)) altClk := io.in io.outAlternateClock := altClk } } println(getVerilog(new ClockExamples)) ``` ```mips= module ClockExamples( input clock, input reset, input [9:0] io_in, input io_alternateReset, input io_alternateClock, input io_alternateClock2, output [9:0] io_outImplicit, output [9:0] io_outAlternateReset, output [9:0] io_outAlternateClock, output [9:0] io_outAlternateBoth ); reg [31:0] _RAND_0; reg [31:0] _RAND_1; reg [31:0] _RAND_2; reg [31:0] _RAND_3; reg [9:0] imp; // @[cmd23.sc 15:20] reg [9:0] REG; // @[cmd23.sc 21:25] reg [9:0] REG_2; // @[cmd23.sc 33:22] reg [9:0] REG_3; // @[cmd23.sc 39:25] assign io_outImplicit = imp; // @[cmd23.sc 17:18] assign io_outAlternateReset = REG; // @[cmd23.sc 23:26] assign io_outAlternateClock = REG_3; // @[cmd23.sc 41:26] assign io_outAlternateBoth = REG_2; // @[cmd23.sc 35:25] always @(posedge clock) begin if (reset) begin // @[cmd24.sc 15:20] imp <= 10'h0; // @[cmd24.sc 15:20] end else begin imp <= io_in; // @[cmd24.sc 16:7] end if (io_alternateReset) begin // @[cmd24.sc 21:25] REG <= 10'h0; // @[cmd24.sc 21:25] end else begin REG <= io_in; // @[cmd24.sc 22:12] end end always @(posedge io_alternateClock) begin if (io_alternateReset) begin // @[cmd24.sc 27:22] REG_1 <= 10'h0; // @[cmd24.sc 27:22] end else begin REG_1 <= io_in; // @[cmd24.sc 28:9] end end always @(posedge io_alternateClock2) begin if (reset) begin // @[cmd24.sc 33:25] REG_2 <= 10'h0; // @[cmd24.sc 33:25] end else begin REG_2 <= io_in; // @[cmd24.sc 34:12] end end endmodule ``` ## Exercise ### Ex.1 參數化移位暫存器 * 可以根據 n 長出對應位寬的移位暫存器 ```scala= class MyOptionalShiftRegister(val n: Int, val init: BigInt = 1) extends Module { val io = IO(new Bundle { val en = Input(Bool()) val in = Input(Bool()) val out = Output(UInt(n.W)) }) val state = RegInit(init.U(n.W)) val nextState = (state << 1) | io.in when (io.en) { state := nextState } io.out := state } ``` --- # 範例2: FIR 濾波器 * ```++```: scala 中用於聯集集合的運算符 * ```Seq()```: scala 原始的集合類型,常被 chisel 使用 * ```Seq.fill(shapes)(value)```: * 用 value 填滿形狀為 shapes 的 Seq * ```Seq.zip()```: 拆開 Seq 組成所需的 pair * ```Seq.tabulate()()```: * 第一個輸入值決定 Seq 的形狀 * ```.foreach```: * ```def foreach(f: (A) => Unit): Unit``` * 將函數套用到列表的所有元素 * ```Decoupled(gen: Data)```: * 在使用前我們可以將輸出資料/數據類型打包起來,接著輸入到 Decoupled() 後,Decoupled() 就會為 gen 產生對應的 ready 和 valid 訊號。並且預設是輸出 * 假設將 A 模組的數據輸出打包成 "ioType" * B 模組接收 A 模組的數據,此時就可以在 B 模組內使用 Decoupled()。接受 A 模組的輸出資料並產生對應的 ready 和 valid 訊號 ```scala= class Bus extends Bundle { val data1 = Input(Bits(16.W)) val data2 = Output(Bits(16.W)) val addr = Bits(16.W) } class testBus extends Module{ val io = IO( new Bundle{ val com = Decoupled(new Bus) }) io.com.valid := io.com.ready io.com.bits.data := 1.U io.com.bits.addr := 1.U } ``` ```mips= module testBus( input clock, input reset, input io_com_ready, output io_com_valid, output [15:0] io_com_bits_data, output [15:0] io_com_bits_addr ); assign io_com_valid = io_com_ready; // @[cmd14.sc 11:18] assign io_com_bits_data = 16'h1; // @[cmd14.sc 12:22] assign io_com_bits_addr = 16'h1; // @[cmd14.sc 13:22] endmodule ``` * ```def test[T <: A](): T```: * A 是之前已經定義過的 abstract class * 使用名稱 T 表示 A 這個 abstract class * ```Flipped()```: 翻轉訊號的方向 ```scala= class QueueModule[T <: Data](ioType: T) extends MultiIOModule { val in = IO(Flipped(Decoupled(ioType))) val out = IO(Decoupled(ioType)) out <> in } println(getVerilog(new QueueModule(UInt(9.W)))) ``` ```mips= module QueueModule( input clock, input reset, output in_ready, input in_valid, input [8:0] in_bits, input out_ready, output out_valid, output [8:0] out_bits ); assign in_ready = out_ready; // @[cmd45.sc 4:7] assign out_valid = in_valid; // @[cmd45.sc 4:7] assign out_bits = in_bits; // @[cmd45.sc 4:7] endmodule ``` * ```<>```: 表示整體連接,這裡就是把 in 和 out 的三個端口連接起來 * ```Queue(enq:DecoupleIO, entries:Int)```: * chisel 的硬體模組 * 生成一個 entries 元素的 enq 對列 * ```enqueueNow()```: * 添加一個元素到 Decoupled 輸入接口 * ```expectDequeueNow()```: * 從 Decoupled 輸出接口移出一個元素 * ```enqueueSeq()```: * ```enqueueNow()```的序列版本 * 持續從一個 seq 添加元素到 Decoupled 輸入接口,一次一個,直到序列內用完 * 如果輸入的 seq 深度大於 Decoupled 深度就會出問題 * ```expectDequeueSeq()```: * ```expectDequeueNow()```的序列版本 * 從 Decoupled 輸出接口移出元素,一次一個,並且和 seq 的下一個元素進行比較 * 如果輸入的 seq 深度大於 Decoupled 深度就會出問題 * ```fork{}```: ```scala= fork { c.in.enqueueSeq(testVector) }.fork { c.out.expectDequeueSeq(testVector) }.join() ``` ## 備註 * PeekPokeTester 會卡 bug --- # 參考 * [Chisel-bootcamp](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master) * [神人對 chisel test 的詳解](https://blog.csdn.net/weixin_43681766/article/details/123931579) * [scala list 語法](https://www.runoob.com/scala/scala-lists.html) * [AXI4 參考資料](https://xilinx.eetrend.com/blog/2023/100568431.html)