• 吃透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独热解码器的示意图:

    在这里插入图片描述

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

    ab
    000001
    010010
    100100
    111000

    我们当然可以用上一篇文章中学到的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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    import chisel3.util._
    
    • 1

    然后就可以用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}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个语法也是很好懂的,根据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}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    result := 1.U << sel
    
    • 1

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

    编码器(Encoder)实现

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

    在这里插入图片描述

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

    ab
    000100
    001001
    010010
    100011
    ?????

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

    下面的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}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结语

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

  • 相关阅读:
    这个简单的小功能,半年为我们产研团队省下213个小时
    《C和指针》笔记24: 指针和间接访问
    .NET 7 预览版 1 发布
    【微服务】springboot 整合dubbo3开发rest应用
    Redis数据结构:集合
    UDP文件传输工具之UDP怎么限流
    真福利!阿里师兄抄送的内部并发编程核心知识手册
    Java总结String类
    2022-08-04 C++并发编程(七)
    十分钟教你在 k8s 中部署一个前后端应用
  • 原文地址:https://blog.csdn.net/weixin_43681766/article/details/125829752