Archive:

搭建自己的 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 连接即可找到该非零元素。

\[\exists! i, hotIn[i]\neq 0 \Rightarrow \exists! v[i]\neq 0\]

裁决器

裁决器负责将一个 $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 即可。

同样通过查询手册,得到对应的针脚信息如下。

Segment Pins

在经过类似于 第一章 的针脚绑定与烧写流程后,我们可以用开关来控制 FPGA 板卡 LED 数码管的显示。

Display