吃透Chisel语言.20.Chisel组合电路(二)——Chisel编码器与解码器实现

Chisel组合电路(二)——Chisel编码器与解码器实现

上一篇文章讲了Chisel中组合电路的基本写法,还介绍了Chisel中的条件语句when/elsewhen/otherwise结构,需要注意的是Chisel中的条件语句块并非条件执行,而是对应着多路选择器。这一篇文章我们将会用Chisel实现编码器和解码器,同时引入Chisel中的switch语句。

解码器(Decoder)实现

解码器是用于将一个n位二进制数解码为m位信号的电路,其中m不大于 2 n 2^n 2n。我们这里要实现的是2-4的独热解码器,用于生成二位二进制数对应的独热(One-hot)编码,独热编码中有且仅有一位是1,其余都是0

下图就是2-4独热解码器的示意图:

吃透Chisel语言.20.Chisel组合电路(二)——Chisel编码器与解码器实现_第1张图片

如果用真值表来描述这个解码器的功能的话,真值表如下:

a b
00 0001
01 0010
10 0100
11 1000

我们当然可以用上一篇文章中学到的when语句来实现:

result := 0.U

when(sel === 0.U) {
    result := 1.U
}.elsewhen (sel === 1.U) {
    result := 2.U
}.elsewhen (sel === 2.U) {
    result := 4.U
}.otherwise {
    result := 8.U
}

虽然看起来还行,但是不太简洁。由于when的判断条件只有一个信号,那我们是否可以像其他语言一样有个switch或者case语句呢?Chisel中就提供了switch结构,要使用switch语句的话首先需要包含chisel3.util包:

import chisel3.util._

然后就可以用switch语句来描述了:

result := 0.U

switch(sel) {
    is (0.U) { result := 1.U}
    is (1.U) { result := 2.U}
    is (2.U) { result := 4.U}
    is (3.U) { result := 8.U}
}

这个语法也是很好懂的,根据sel信号来赋不同的解码值给result信号。需要注意的是,即使我们在switch语句中枚举了所有的可能值,Chisel这种还是需要赋一个默认值,比如上面的代码中就赋了0.Uresult。但这个赋值不会被激活,因此会在后端工具被优化掉。这么做是为了避免组合电路(Chisel中的Wire)中不完全赋值的情况,没定义的情况会在其他硬件描述语言比如Verilog中生成不想要的锁存器(latch),所以Chisel中不允许不完全赋值的情况。

不过上面的例子看起来不是那么好懂,把十进制数值写成二进制会让这个解码器功能看起来更清晰。下面是用二进制描述的完整代码:

class Decoder_2_4 extends Module {
    val io = IO(new Bundle {
        val sel = Input(UInt(2.W))
        val result = Output(UInt(4.W))
    })

    io.result := 0.U

    switch(io.sel) {
        is("b00".U) { io.result := "b0001".U}
        is("b01".U) { io.result := "b0010".U}
        is("b10".U) { io.result := "b0100".U}
        is("b11".U) { io.result := "b1000".U}
    }
}

这种表述看起来清晰多了,就跟真值表一样,输出如下:

module Decoder_2_4(
  input        clock,
  input        reset,
  input  [1:0] io_sel,
  output [3:0] io_result
);
  wire [3:0] _GEN_0 = 2'h3 == io_sel ? 4'h8 : 4'h0; // @[hello.scala 12:15 14:20 18:33]
  wire [3:0] _GEN_1 = 2'h2 == io_sel ? 4'h4 : _GEN_0; // @[hello.scala 14:20 17:33]
  wire [3:0] _GEN_2 = 2'h1 == io_sel ? 4'h2 : _GEN_1; // @[hello.scala 14:20 16:33]
  assign io_result = 2'h0 == io_sel ? 4'h1 : _GEN_2; // @[hello.scala 14:20 15:33]
endmodule

关于编码器其实就是这样了,不过上面的代码还可以更简单一点。可以注意到,0/1/2/3对应的输出分别是0001左移0/1/2/3位,因此我们可以直接用一个根据sel信号的0001的移位来实现,用Chisel中的移位操作符<<就可以实现:

result := 1.U << sel

解码器通常用作多路复用器的构建块,将解码器的输出作为使能信号,经过一个与门作为另一个多路选择器的数据输入。不给过在Chisel中不需要手动构造一个多路选择器,因为Mux在Chisel库中就是有的。解码器还能用于地址解码,然后将输出作为选择信号,比如用于连接到处理器的不同IO设备的选择。

编码器(Encoder)实现

这里的编码器是上一节的解码器翻转过来的样子,也就是从一个四位独热编码到二位二进制编码信号的编码电路。下图就展示了4-2独热编码器的示意图:

吃透Chisel语言.20.Chisel组合电路(二)——Chisel编码器与解码器实现_第2张图片

对应的真值表也和解码器是反过来的:

a b
0001 00
0010 01
0100 10
1000 11
??? ??

不同的是最底下多了一行,因为独热编码器只定义了上面的四种情况,即输入必须为独热编码,对于其他的任何输入,输出都是未定义的。因为我们没办法描述一个有未定义输出的函数,我们必须使用默认赋值来捕获所有未定义的输入模式。

下面的Chisel代码赋了默认值00,然后用switch语句赋值为所有合法输入给输出赋值:

b := "b00".U

switch(a) {
    is ("b0001".U) { b := "b00".U}
    is ("b0010".U) { b := "b01".U}
    is ("b0100".U) { b := "b10".U}
    is ("b1000".U) { b := "b11".U}
}

结语

这一篇文章以解码器和编码器为例实践了组合电路模块的构造,同时介绍了switch语句的使用。我们自己也可以尝试编写更复杂的组合电路,比如四位二进制输入到七段数码管编码的编码/解码功能模块,进一步可用于16进制数字的显示。下一部分我们将进入时序电路的学习,从基本的寄存器开始讲述,再到计数器、计时器、移位寄存器,最后是内存。时序电路讲完,基础的内容就讲完了,后面会开始介绍更高阶的内容,敬请期待。

你可能感兴趣的:(吃透Chisel语言!!!,Chisel,risc-v,计算机体系结构,fpga开发,CPU设计实现)