Chisel教程——03.Chisel中的组合逻辑(结尾附上Chisel3 Cheat Sheet)
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的Int
1转换成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.B
和false.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中的数据仲裁到两个并行处理单元中,规则如下:
- 如果两个处理单元都空着,那就优先发送到PE0;
- 如果至少有一个可用,那仲裁器应该告诉FIFO已经准备好接受数据;
- 在断言数据有效之前,等待PE断言它已经准备好;
- 提示:可能需要二元运算符来实现;
模板和测试如下:
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!!")
观察一下:
- 有来自FIFO的两个输入,分别是FIFO有效和FIFO的数据,一个到FIFO的输出告知已经准备好;
- 到PE分别有两个输出,是数据和告知PE数据是否有效,一个PE的输入告知是否准备好;
那么我们的思路如下:
- 通过信号
pe0_ready
和pe1_ready
确定fifo_ready
(是否有空闲的PE); - 如果FIFO数据有效,PE0的如果准备好了就让PE0上(设置pe0_valid);
- 如果FIFO数据有效,且PE0没有准备好,PE1准备好了,那就让PE1上(设置pe1_valid);
- 同时提供数据,但只有设置了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)相关推荐
- Chisel教程——02.Chisel环境配置和第一个Chisel模块的实现与测试
Chisel环境配置和第一个Chisel模块的实现与测试 动机 现在已经对Scala有一定的了解了,可以开始构造一些硬件了.Chisel的全称为Constructing Hardware In a S ...
- Chisel教程——08.Chisel参数化生成器(从Scala讲起)
Chisel参数化生成器(从Scala讲起) 动机 要想使得Chisel模块成为代码生成器,就必须要有一些东西来告诉生成器如何执行这个工作.这一节会介绍模块的参数化,涉及多种方法和Scala语言特性. ...
- 吃透Chisel语言.32.Chisel进阶之硬件生成器(一)——Chisel中的参数化
Chisel进阶之硬件生成器(一)--Chisel中的参数化 Chisel区别于其他硬件描述语言的最强大的地方在于,我们可以用Chisel写硬件生成器.对于老一点的硬件描述语言,比如VHDL和Veri ...
- Unity 2D游戏开发教程之游戏中精灵的跳跃状态
Unity 2D游戏开发教程之游戏中精灵的跳跃状态 精灵的跳跃状态 为了让游戏中的精灵有更大的活动范围,上一节为游戏场景添加了多个地面,于是精灵可以从高的地面移动到低的地面处,如图2-14所示.但是却 ...
- ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用
ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用 原文:ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用 文章来源: Slark.N ...
- C#.NET验证码智能识别学习笔记---03#.Net中@符号的意思
C#.NET验证码智能识别学习笔记---03#.Net中@符号的意思 技术qq交流群:JavaDream:251572072 教程下载,在线交流:it.yunsit.cn @是取消字符串中的转意符 ...
- cad2016中选择全图字体怎么操作_cad教程分享CAD中如何删除顽固图层?
Autocad教程公众号,专注于cad教程.cad教程视频的分享,欢迎关注,下载你所需的教程资源! 如你还未关注,请点击文章标题下方蓝色字体的"Autocad教程"进行关注. ca ...
- CSerialPort教程(3) - MFC中使用CSerialPort
CSerialPort教程(3) - MFC中使用CSerialPort 如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 文章目录 C ...
- ArcGIS二次开发基础教程(03):保存文档和导出地图
ArcGIS二次开发基础教程(03):保存文档和导出地图 保存文档 保存: //这里的path为全局变量 在打开文件获添加数据时赋值原路径 //判断打开文件是否为mxd文件 是则保存 不是则另存为 i ...
最新文章
- 遍历百万级Redis的键值的大结局
- 远程备份SQL Server 2005数据库
- oracle缺少key xe.reg,【Oracle XE系列之一】Windows 7 64位安裝Oracle XE(32位)數據庫(REG_XE報錯、字符集、修改8080端口等)...
- Oracel中连接的总结(一)
- SQL Server 2008之DMF
- WPF 动态更换图片路径
- Nginx负载均衡策略有哪些?知识点总结+面试题解析
- cocos2d-x C++ 原始工程引擎运行机制解析
- 【总结记录】面向对象设计OOP三大特性(封装、继承、多态)、七大基本原则的理解(结合代码、现实实例理解)
- 你知道线程池是如何退出程序的吗?
- PowerDesigner导出rtf文件
- GitHub新手使用教学(从安装到使用)
- 闰年2月29号 通过apache的ftp工具从ftp上下载文件失败
- 60w风扇用多大电容_家里的40W电风扇怎么接电容?该用多大电容?怎么判断好坏?...
- 啤酒与尿布?挖掘商品之间的关联性分析(1):机器学习之Apriori算法
- 聚力国家工业信息安全发展,未来智安加入工业信息安全产业发展联盟
- int类型以及指针的类型所占字节的大小,到底是由什么决定的?
- Git 右键不显示Git功能图标
- Phonics 自然拼读法 sm sn sl sw sp st sc sk all ew y 小结与回顾
- Oracle物化视图的使用总结