Chisel组合逻辑

这一节将会介绍如何使用Chisel组件来实现组合逻辑。

本节将会演示三种基本的Chisel类型(UInt,无符号整数;SInt,有符号整数;Bool,布尔值)可以如何连接和操作。

需要注意的是所有的Chisel变量都声明为Scala的val,绝对不要用Scala中的var来实现硬件构造,因为构造本身在定义后就不会变化了,只有它的值可以在硬件上运行时变化。连线可以用于参数化的类型。

常见运算符

加法实现

首先构造一个Module,这个就不详细介绍了:

import chisel3._class MyModule extends Module {val io = IO(new Bundle {val in  = Input(UInt(4.W))val out = Output(UInt(4.W))})
}

基于这个,我们可以在数据上使用各种运算符:

import chisel3._class MyModule extends Module {val io = IO(new Bundle {val in = Input(UInt(4.W))val out = Output(UInt(4.W))})io.out := io.inval two = 1 + 1println(two)val utwo = 1.U + 1.Uprintln(utwo)
}object MyModule extends App {println(getVerilogString(new MyModule()))
}

输出如下:

可以看到第一个加法val two = 1 + 1打印的结果是2,而第二个加法val utwo = 1.U + 1.U打印的结果是MyModule.utwo: OpResult[UInt<1>]。原因在于第一个是将两个Scala整数相加,而后者是将两个Chisel的UInt相加,所以打印时视为一个硬件节点,输出指针和类型名称。注意,1.U是将Scala的Int1转换成Chisel的UInt字面值。

还有虽然测试的内容和输入输出无关,但也要把输出给连起来,不然会报错:

如果运算符两边的数据类型不匹配的话也会报错,比如:

class MyModule extends Module {val io = IO(new Bundle {val in  = Input(UInt(4.W))val out = Output(UInt(4.W))})val two = 1.U + 1println(two)io.out := io.in
}

会导致类型不匹配的错误:

所以执行操作的时候要清除不同类型的区别,Scala是一个强类型的语言,因此所有的转换都必须是显式的。

减、乘、除实现

再看看其他操作符:

import chisel3._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.Uio.out_sub := 2.U - 1.Uio.out_mul := 4.U * 2.U
}object MyOperators extends App {println(getVerilogString(new MyOperators()))
}

输出结果为:

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] _io_out_sub_T_1 = 2'h2 - 2'h1; // @[MyModule.scala 12:21]wire [4:0] _io_out_mul_T = 3'h4 * 2'h2; // @[MyModule.scala 13:21]assign io_out_add = 4'h5; // @[MyModule.scala 11:14]assign io_out_sub = {{2'd0}, _io_out_sub_T_1}; // @[MyModule.scala 12:14]assign io_out_mul = _io_out_mul_T[3:0]; // @[MyModule.scala 13:14]
endmodule

MyModuleTest.scala内容如下:

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpecclass MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {behavior of "MyOperators"it should "get right results" in {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!!")}
}

测试结果通过。

多路选择器(Mux)和拼接(Concatenation)

Chisel内置了多路选择运算符Mux和拼接运算符Cat

其中,Mux类似于传统的三元运算符,参数依次为(条件, 为真时的值, 为假时的值),建议用true.Bfalse.B来创建Chisel中的布尔值。

Cat的两个参数依次为高位(MSB)和低位(LSB),但只能接受两个参数,如果要拼接多个值那就需要嵌套多个Cat或使用更高级的特性。

用法示例如下:

import chisel3._
import chisel3.util._class MyOperators 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.Bio.out_mux := Mux(s, 3.U, 0.U)io.out_cat := Cat(2.U, 1.U)
}object MyOperators extends App {println(getVerilogString(new MyOperators()))
}

输出如下:

module MyOperators(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; // @[MyModule.scala 12:14]assign io_out_cat = 4'h5; // @[MyModule.scala 13:14]
endmodule

注意到生成的Verilog代码压根儿就没有mux或者concat的组合逻辑实现,而是两个常数赋值。

这是因为FIRRTL转换过程中简化了电路,消除了一些显然的逻辑。

测试:

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpecclass MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {behavior of "MyOperators"it should "get right results" in {test(new MyOperators) {c =>c.io.out_mux.expect(3.U)c.io.out_cat.expect(5.U)}println("SUCCESS!!")}
}

测试通过。

关于Chisel操作符的列表可以参照Chisel cheatsheet,完整的列表和实现细节可以参照Chisel API。

练习

实现乘加操作(MAC)

实现乘加操作,输入是4bit无符号整数A,B和C,输出为8bit无符号整数(A * B) + C,并通过测试:

test(new MAC) { c =>val cycles = 100import scala.util.Randomfor (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!!")

直接算就完事了,解答如下:

import chisel3._
import chisel3.util._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
}object MyOperators extends App {println(getVerilogString(new MAC()))
}

输出如下:

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] _io_out_T = io_in_a * io_in_b; // @[MyModule.scala 12:22]wire [7:0] _GEN_0 = {{4'd0}, io_in_c}; // @[MyModule.scala 12:33]assign io_out = _io_out_T + _GEN_0; // @[MyModule.scala 12:33]
endmodule

测试通过。

仲裁器(Arbiter)

上面的仲裁器用于将FIFO中的数据仲裁到两个并行处理单元中,规则如下:

  1. 如果两个处理单元都空着,那就优先发送到PE0;
  2. 如果至少有一个可用,那仲裁器应该告诉FIFO已经准备好接受数据;
  3. 在断言数据有效之前,等待PE断言它已经准备好;
  4. 提示:可能需要二元运算符来实现;

模板和测试如下:

class Arbiter extends Module {val io = IO(new Bundle {// FIFOval fifo_valid = Input(Bool())val fifo_ready = Output(Bool())val fifo_data  = Input(UInt(16.W))// PE0val pe0_valid  = Output(Bool())val pe0_ready  = Input(Bool())val pe0_data   = Output(UInt(16.W))// PE1val pe1_valid  = Output(Bool())val pe1_ready  = Input(Bool())val pe1_data   = Output(UInt(16.W))})/*在这里填上相应的代码*/
}test(new Arbiter) { c =>import scala.util.Randomval 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!!")

观察一下:

  1. 有来自FIFO的两个输入,分别是FIFO有效和FIFO的数据,一个到FIFO的输出告知已经准备好;
  2. 到PE分别有两个输出,是数据和告知PE数据是否有效,一个PE的输入告知是否准备好;

那么我们的思路如下:

  1. 通过信号pe0_readype1_ready确定fifo_ready(是否有空闲的PE);
  2. 如果FIFO数据有效,PE0的如果准备好了就让PE0上(设置pe0_valid);
  3. 如果FIFO数据有效,且PE0没有准备好,PE1准备好了,那就让PE1上(设置pe1_valid);
  4. 同时提供数据,但只有设置了pe_valid的才会有用;

解答如下:

import chisel3._
import chisel3.util._class Arbiter extends Module {val io = IO(new Bundle {// FIFOval fifo_valid = Input(Bool())val fifo_ready = Output(Bool())val fifo_data  = Input(UInt(16.W))// PE0val pe0_valid  = Output(Bool())val pe0_ready  = Input(Bool())val pe0_data   = Output(UInt(16.W))// PE1val pe1_valid  = Output(Bool())val pe1_ready  = Input(Bool())val pe1_data   = Output(UInt(16.W))})io.fifo_ready := io.pe0_ready || io.pe1_readyio.pe0_valid := io.fifo_valid & io.pe0_readyio.pe1_valid := io.fifo_valid & io.pe1_ready & !io.pe0_readyio.pe0_data := io.fifo_dataio.pe1_data := io.fifo_data
}object Arbiter extends App {println(getVerilogString(new Arbiter()))
}

输出如下:

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; // @[MyModule.scala 22:33]assign io_pe0_valid = io_fifo_valid & io_pe0_ready; // @[MyModule.scala 23:33]assign io_pe0_data = io_fifo_data; // @[MyModule.scala 25:15]assign io_pe1_valid = io_fifo_valid & io_pe1_ready & ~io_pe0_ready; // @[MyModule.scala 24:48]assign io_pe1_data = io_fifo_data; // @[MyModule.scala 26:15]
endmodule

测试通过。

参数化加法器

这一部分的练习会体现出Chisel的强大特性——参数化的能力。

这里要求构造一个参数化的加法器,能够在发生溢出时饱和,也能够得到阶段结果。比如对于4bit的整数加法,15+15既可以得到15,也可以得到14,就看给的参数是啥。

模板如下:

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))})/*在这里填上相应的代码*/
}for (saturate <- Seq(true, false)) {test(new ParameterizedAdder(saturate)) { c =>// 100 random testsval cycles = 100import scala.util.Randomimport scala.math.minfor (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. truncationc.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!!")

观察上面的模板,传递进来的参数叫做saturate,类型为Scala中的布尔型,而不是Chisel中的布尔型。所以这里要创建的不是一个又能饱和又能截断的硬件,而是一个生成器,要么生成一个饱和加法器,要么生成一个截断加法器,这是在编译的时候就已经确定了的。

然后需要注意的是输入输出都是4bit的UInt,Chisel有内置的宽度推理,根据cheatsheet里面说的,常规加法结果的位宽等于两个输入最宽的那个,也就是说项目的计算只能得到一个4bit的连线:

val sum = io.in_a + io.in_b

为了检查结果是否需要饱和,需要把加法结果放入一个5bit的连线中。

根据cheatsheet的描述,可以使用+&操作符:

val sum = io.in_a +& io.in_b

最后,如果把一个4bit的UInt连线到一个5bit的UInt,那就会自动截断最高有效位,利用这个特性就可以很容易地为非饱和加法器得到截断结果。

解答如下:

import chisel3._
import chisel3.util._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_bif (saturate) {io.out := Mux(sum > 15.U, 15.U, sum)} else {io.out := sum}
}object ParameterizedAdder extends App {println(getVerilogString(new ParameterizedAdder(true)))println(getVerilogString(new ParameterizedAdder(false)))
}

生成的Verilog如下:

// saturation
module ParameterizedAdder(input        clock,input        reset,input  [3:0] io_in_a,input  [3:0] io_in_b,output [3:0] io_out
);assign io_out = 4'hf; // @[MyModule.scala 13:12]
endmodule// truncation
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; // @[MyModule.scala 11:21]assign io_out = sum[3:0]; // @[MyModule.scala 15:12]
endmodule

测试通过。

Chisel3 Cheat Sheet

最后附上Chisel3的Cheat Sheet:


Chisel教程——03.Chisel中的组合逻辑(结尾附上Chisel3 Cheat Sheet)相关推荐

  1. Chisel教程——02.Chisel环境配置和第一个Chisel模块的实现与测试

    Chisel环境配置和第一个Chisel模块的实现与测试 动机 现在已经对Scala有一定的了解了,可以开始构造一些硬件了.Chisel的全称为Constructing Hardware In a S ...

  2. Chisel教程——08.Chisel参数化生成器(从Scala讲起)

    Chisel参数化生成器(从Scala讲起) 动机 要想使得Chisel模块成为代码生成器,就必须要有一些东西来告诉生成器如何执行这个工作.这一节会介绍模块的参数化,涉及多种方法和Scala语言特性. ...

  3. 吃透Chisel语言.32.Chisel进阶之硬件生成器(一)——Chisel中的参数化

    Chisel进阶之硬件生成器(一)--Chisel中的参数化 Chisel区别于其他硬件描述语言的最强大的地方在于,我们可以用Chisel写硬件生成器.对于老一点的硬件描述语言,比如VHDL和Veri ...

  4. Unity 2D游戏开发教程之游戏中精灵的跳跃状态

    Unity 2D游戏开发教程之游戏中精灵的跳跃状态 精灵的跳跃状态 为了让游戏中的精灵有更大的活动范围,上一节为游戏场景添加了多个地面,于是精灵可以从高的地面移动到低的地面处,如图2-14所示.但是却 ...

  5. ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用

    ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用 原文:ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用 文章来源: Slark.N ...

  6. C#.NET验证码智能识别学习笔记---03#.Net中@符号的意思

    C#.NET验证码智能识别学习笔记---03#.Net中@符号的意思 技术qq交流群:JavaDream:251572072  教程下载,在线交流:it.yunsit.cn  @是取消字符串中的转意符 ...

  7. cad2016中选择全图字体怎么操作_cad教程分享CAD中如何删除顽固图层?

    Autocad教程公众号,专注于cad教程.cad教程视频的分享,欢迎关注,下载你所需的教程资源! 如你还未关注,请点击文章标题下方蓝色字体的"Autocad教程"进行关注. ca ...

  8. CSerialPort教程(3) - MFC中使用CSerialPort

    CSerialPort教程(3) - MFC中使用CSerialPort 如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 文章目录 C ...

  9. ArcGIS二次开发基础教程(03):保存文档和导出地图

    ArcGIS二次开发基础教程(03):保存文档和导出地图 保存文档 保存: //这里的path为全局变量 在打开文件获添加数据时赋值原路径 //判断打开文件是否为mxd文件 是则保存 不是则另存为 i ...

最新文章

  1. 遍历百万级Redis的键值的大结局
  2. 远程备份SQL Server 2005数据库
  3. oracle缺少key xe.reg,【Oracle XE系列之一】Windows 7 64位安裝Oracle XE(32位)數據庫(REG_XE報錯、字符集、修改8080端口等)...
  4. Oracel中连接的总结(一)
  5. SQL Server 2008之DMF
  6. WPF 动态更换图片路径
  7. Nginx负载均衡策略有哪些?知识点总结+面试题解析
  8. cocos2d-x C++ 原始工程引擎运行机制解析
  9. 【总结记录】面向对象设计OOP三大特性(封装、继承、多态)、七大基本原则的理解(结合代码、现实实例理解)
  10. 你知道线程池是如何退出程序的吗?
  11. PowerDesigner导出rtf文件
  12. GitHub新手使用教学(从安装到使用)
  13. 闰年2月29号 通过apache的ftp工具从ftp上下载文件失败
  14. 60w风扇用多大电容_家里的40W电风扇怎么接电容?该用多大电容?怎么判断好坏?...
  15. 啤酒与尿布?挖掘商品之间的关联性分析(1):机器学习之Apriori算法
  16. 聚力国家工业信息安全发展,未来智安加入工业信息安全产业发展联盟
  17. int类型以及指针的类型所占字节的大小,到底是由什么决定的?
  18. Git 右键不显示Git功能图标
  19. Phonics 自然拼读法 sm sn sl sw sp st sc sk all ew y 小结与回顾
  20. Oracle物化视图的使用总结

热门文章

  1. 捷联式惯性导航基础知识(一)
  2. 15个常用excel函数公式_Excel求和公式函数的使用方法教程
  3. STM32CubeMX+HAL库定时器介绍
  4. 关于对android中的inset标签的简单解读
  5. MapReduce项目案例4——乘用车辆和商用车辆销售数据分析
  6. MST1163,摩托车大功率闪光器
  7. 提升代码质量的方法:领域模型、设计原则、设计模式
  8. 计算机音乐一决高下,节奏大师详细讲解 用音乐来一决高下吧!
  9. 求职与学历、考研与学历
  10. win10静态arp绑定