# 【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 定義的接口介面協定




---
# 組合電路
## 加減乘的範例
* 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)