搭建自己的 CPU / 组合逻辑
组合逻辑
组合逻辑指那些不包含存储能力的电路。由于它们并不具有存储功能,因此可以被表示 成布尔表达式的形式。
out := in_a & in_b
解码器
解码器将一个 $n$ 位的输入转换成一个 $m$ 位的信号输出,其中 $m\leq 2^n$。 下表为一个 4 位解码器的输入与对应输出。
输入 | 输出 |
---|---|
00 | 0001 |
01 | 0010 |
10 | 0100 |
11 | 1000 |
一个解码器可以用下面的 Chisel 代码描述
/*sel: 输入
result: 输出*/
/* Verbose Version */
val result = 0.U
switch (sel) {
is ("b00".U) { result := "b0001".U}
is ("b01".U) { result := "b0010".U}
is ("b10".U) { result := "b0100".U}
is ("b11".U) { result := "b1000".U}
}
/* Simple Version */
result := 1.U << sel
解码器可以与 AND 门组合构成 Mux 选择器,也可以在处理器与内存的通信时用于解码地址 信号。
编码器
编码器执行与解码器相反的操作,用于将一个 $m$ 位的信号输入编码为一个 $n$ 位的数字输出。 下表位一个 4 位编码器的真值表。
输入 | 输出 |
---|---|
0001 | 00 |
0010 | 01 |
0100 | 10 |
1000 | 11 |
???? | ?? |
对于小的编码器,我们可以用与解码器类似的思路进行硬编码,然而对于大规模的编码器, 这样显然不可取,因此我们可以利用 Scala 的循环来生成编码器。
/*hotIn: 输入
v: 临时存储
encOut: 输出*/
val v = Wire(Vec (16, UInt (4.W)))
v(0) := 0.U
for (i <- 1 until 16) {
v(i) := Mux(hotIn(i), i.U, 0.U) | v(i - 1)
}
val encOut = v(15)
上面的式子中,由于 hotIn
为 one-hot 编码,只有一位非零,导致结果 v
中也只有
一个元素非零,我们只要将 v
的各个元素以 OR 连接即可找到该非零元素。
裁决器
裁决器负责将一个 $n$ 位信号源转换成 one-hot 编码,一个公平的裁决器需要使用寄存器 来记住自己上次选择的信号,在这里,我们假定低位信号具有优先权。
对于小型的裁决器,我们可以利用类似的硬编码思路进行编程。
val grant = WireDefault ("b0000".U(3.W))
switch (request) {
is ("b000".U) { grant := "b000".U}
is ("b001".U) { grant := "b001".U}
is ("b010".U) { grant := "b010".U}
is ("b011".U) { grant := "b001".U}
is ("b100".U) { grant := "b100".U}
is ("b101".U) { grant := "b001".U}
is ("b110".U) { grant := "b010".U}
is ("b111".U) { grant := "b001".U}
}
对于大型裁决器,就需要用到循环来生成裁决器的电路了。
/*grant(i) == true: i 获得了权限
notGranted(i) == true: 权限还未给予 0~i 中任何一个*/
val grant = VecInit.fill(n)(false.B)
val notGranted = VecInit.fill(n)(false.B)
grant (0) := request (0)
notGranted (0) := !grant (0)
for (i <- 1 until n) {
grant(i) := request(i) && notGranted (i -1)
notGranted (i) := !grant(i) && notGranted (i -1)
}
似乎这里可以
val notGranted = false.B
,因为我们每次只需要记录是否已被 grant,在 生成的电路层面应当两者等价,但程序会更清晰?
组合逻辑:数码管输出
FPGA 板卡上通常都带有数码管,可以 用于表示 16 进制数(尽管如 B,D 等必须用小写表示)。
7段共阴极数码管显示 0 ~9,A ~ F,其编码依次为:3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH, 77H,7CH,39H,5EH,79H,71H。我们可以用 Chisel 实现一个将 4 bit 开关信号,转换成显示 16 进制数 的电路。
package decoder
import chisel3._
import chisel3.util._
class Decoder extends Module {
val io = IO(new Bundle {
val inSw = Input(UInt(4.W))
val outSeg = Output(UInt(8.W))
})
val HIGH_IS_ON = false;
val codeTable = Array(
"b00111111".U, // 0
"b00000110".U, // 1
"b01011011".U, // 2
"b01001111".U, // 3
"b01100110".U, // 4
"b01101101".U, // 5
"b01111101".U, // 6
"b00000111".U, // 7
"b01111111".U, // 8
"b01101111".U, // 9
"b01110111".U, // A
"b01111100".U, // b
"b00111001".U, // C
"b01011110".U, // d
"b01111001".U, // E
"b01110001".U // F
)
val hwCodeTable = Wire(Vec(16, UInt(8.W)));
for (i <- 0 to 15) {
hwCodeTable(i) := codeTable(i)
}
if (HIGH_IS_ON)
io.outSeg := hwCodeTable(io.inSw)
else
io.outSeg := ~hwCodeTable(io.inSw)
}
object DecoderMain extends App {
println("Generating the decoder hardware")
emitVerilog(new Decoder(), Array("--target-dir", "generated"))
}
值得注意的一点是,在 DE10-Lite 板卡上,数码管的 “亮” 是在对应位置输出低电平,因此我们定义了
HIGH_IS_ON
来控制这类行为,如果你的板卡的 “亮” 对应的为 高电平,将HIGH_IS_ON
设为 true 即可。
同样通过查询手册,得到对应的针脚信息如下。
在经过类似于 第一章 的针脚绑定与烧写流程后,我们可以用开关来控制 FPGA 板卡 LED 数码管的显示。