【从嵌入式视角学习香山处理器】四、Chisel语言基础
文章目录
- 一、前言
- 二、Linux上对scala工程的操作
- 1. helloworld执行命令:
- 2. 有多个工程目录时,需要切换工程:
- 3. 编译报错:
- 4. 给vscode的scala插件设置JAVA_HOME路径:
- 三、(ch4)基本组成部分
- ch4.1:信号类型与常量:
- ch4.2:组合电路:
- ch4.3:状态寄存器:
- ch4.4:使用Bundle和Vec来构建
- 四、(ch5)搭建过程和测试
- ch5.1:使用sbt搭建你的项目
- ch5.2.1:PeekPokeTester
- ch5.2.2:使用scalaTester
- ch5.2.3:波形
- ch5.2.4:printf debugging
- 五、(ch6)组成部分
- ch6.1:chisel的组成部分是模块
- ch6.2:一个运算逻辑单元
- ch6.3:整体连接
- ch6.4:使用函数的轻量级组成部分
- 六、(ch7)组合搭建模块
- ch7.1:组合电路
- ch7.2:解码器
- ch7.3:编码器
- 七、(ch8)时序建造模块
- ch8.1:寄存器
- ch8.2:计数器
- ch8.2.1:向上和向下计数
- ch8.2.2:使用计数器产生时序
- ch8.2.3:nerd计数器
- ch8.2.4:一个计时器
- ch8.2.5:脉冲宽度调制
- ch8.3:位移寄存器
- ch8.3.1:使用并行输出的移位寄存器
- ch8.3.2:并行读取的移位寄存器
- ch8.4:存储器
- 八、(ch9)输入处理
- ch9.1:异步输入
- ch9.2:防抖动
- ch9.3:输入信号滤波
- ch9.4:使用函数合并输入处理
- ch9.5:练习: (略)
- 九、(ch10)有限状态机
- ch10.1:基本有限状态机 (Moore FSM为例)
- ch10.2:使用Mealy FSM产生快速输出
- ch10.3:Moore对比Mealy
- ch10.4:练习: (略)
- 十、(ch11)状态机通信
- ch11.1:一个灯光闪烁器的例子
- ch11.2:位1计数(器)的例子: (略)
- ch11.3:ready-valid接口
- 十一、(ch12)硬件生成器
- ch12.1:一点scala的内容:
- ch12.2.1:使用参数配置:
- ch12.2.2:使用类型参数的函数:
- ch12.2.3:具有类型参数的模块
- ch12.2.4:参数化的捆束(Bundle)
- ch12.3:生成组合逻辑
- ch12.4:使用继承
- ch12.5:使用函数式编程做硬件生成
- 十二、(ch13)示例设计
- ch13.1:fifo缓冲器
- ch13.2:一个串口端口
- ch13.3:设计fifo中的变量
- ch13.3.1:参数化fifo:(略)
- ch13.3.2:重新设计冒泡fifo
- ch13.3.4:具有寄存存储器的FIFO
- ch13.3.5:使用片上存储的FIFO
- ch13.4.1:继续探索冒泡fifo
- ch13.4.2:the UART
- ch13.4.3:探索fifo
- 十三、(ch14)设计一个处理器
- ch14.1:从alu开始
- ch14.2:译码指令(指令译码器)
- ch14.3:汇编指令(指令汇编器)
- ch14.4:练习:(略)
- 十四、(ch15)贡献chisel
- 十五、(ch16)总结
一、前言
这是记录学习chisel官方文档的笔记。原文档pdf下载链接在这里:
http://www.imm.dtu.dk/~masca/chisel-book-chinese.pdf
写这篇文章找到了另一篇官方的中文简介文章,可作为学习阶段反复阅读复习用的材料。
(下载链接:http://www.aiotek.pro/aiotek/doclib/chisel/chisel-getting-started-chinese.pdf)
二、Linux上对scala工程的操作
1. helloworld执行命令:
sbt "runMain <对象名(而不是文件名)>"
2. 有多个工程目录时,需要切换工程:
sbt
project
project <工程名>
ctrl+C
3. 编译报错:
[error] (run-main-0) java.lang.NoSuchMethodException: Hello.main([Ljava.lang.String;)
main就是object;代码中只有class是不够的;
[error] /home/cwq/6_chisel_book_and_example/1_cwq_example/2_Hello_hardware.scala:2:8: not found: object chisel3
缺少编译配置文件,默认是build.sbt;需要从别的工程里复制出来用;
4. 给vscode的scala插件设置JAVA_HOME路径:
确认JAVA_HOME路径的方法:https://www.cnblogs.com/huaisn/articles/14499330.html
三、(ch4)基本组成部分
ch4.1:信号类型与常量:
- 信号类型:Bits、UInt、SInt
- 常量:.W(表示信号类型或常量的宽度)、.U、.S
- 8.U(4.W)表示4bit宽度的常量8
- “hff”.U、“o377”.U、“b1111_1111”.U分别为十进制常量255.U在其它进制下的表示
- bool类型:true.B、false.B
ch4.2:组合电路:
- 算数操作符:(和其它语言一样的)加减乘除、取余、取反
- 逻辑操作符:与非或、异或、相等、不等
- 操作符的优先级取决于电路的赋值顺序(不同于其它语言):所以有必要使用括号
- chisel提供的复用器:
val result = Mux(<条件>, <条件为真的输出选择>, <条件为假的输出选择>)
ch4.3:状态寄存器:
- 寄存器定义:val reg = RegInit(0.U(8.W)),定义了一个八位寄存器,在复位初始化为0
- 寄存器用作计数器的示例:从0数到9,并重新返回0,以实现数10个数的目的
val cntReg = RegInit(0.U(8.W))
cntReg := Mux(cntReg === 10.U, 0.U, cntReg+1.U)
ch4.4:使用Bundle和Vec来构建
- Bundle:组合不同类型的信号
- Vec:组合可索引的相同类型的信号
- Bundle和Vec可以相互嵌套
- 定义一个Bundle类型、有初始值的寄存器:先创建Bundle类型的Wire变量,再给这个变量赋值,再用这个变量去定义寄存器
val initVal = Wire(new Channel())
initVal.data := 0.U
initVal.valid := false.B
val channelReg = RegInit(initVal)
四、(ch5)搭建过程和测试
ch5.1:使用sbt搭建你的项目
- 库文件通过build.sbt被引用
- 如果build.sbt设置latest.release则表示总是用最新的chisel版本,这意味着每次搭建都要联网查看maven仓库——实际上提倡无联网情况下的搭建
- “import <软件包名>._”表示包里的所有类都要被引用
- chisel工具流:参考文档中的fig5.2图,从.scala文件到生成.vcd波形文件和.v综合电路文件
ch5.2.1:PeekPokeTester
- chisel模块的单元测试:
sbt "runMain xxx"
ch5.2.2:使用scalaTester
- scala模块的单元测试:
sbt "testOnly xxx"
ch5.2.3:波形
- 在scalaTester下使用Driver.execute()代替Driver(),即可生成.vcd波形文件,用GTKWave(或ModelSim)可以打开
ch5.2.4:printf debugging
- printf是来源于C语言的另一种调试形式:在函数的任何地方都可以插入printf()函数
- printf支持C和scala两种风格
- 示例:略
五、(ch6)组成部分
ch6.1:chisel的组成部分是模块
- 模块的嵌套示例:fig6.1
- (重要)“硬件组件”在chisel代码里称为module,所以它们都用extends Module继承的方式来定义。并且里面一定要用IO(new Bundle())定义它的全部IO——Input和Output都在里面一起定义。
- (重要)“硬件组件”在chisel代码里称为module,所以它们都用extends Module继承的方式来定义。并且里面一定要用IO(new Bundle())定义它的全部IO——Input和Output都在里面一起定义。
ch6.2:一个运算逻辑单元
- 以一个简单的运算逻辑单元ALU为作为大Module的示例,讲解其内部fetch、decode、execute三个Module的互联关系
- 顺便引出:switch/is语句的使用,需要引入chisel3.util包
ch6.3:整体连接
- Bundle的整体双向互联,可用批量连接运算符"<>":Bundle中识别为同名的信号val,会互联到一起
ch6.4:使用函数的轻量级组成部分
- 函数(def):模块(class … extends Module)是构造硬件描述的通用方法。但是,也有一些“样板代码”可以在对模块进行声明、实例化、连接时使用(这就是函数)
- 示例1:用RegNext()函数构造延时一周期的新函数:
def delay(x:UInt) = RegNext(x)
- 示例2:调用上述函数,来定义一个“对输入变量延时两个周期后输出的变量”
def delay(x:UInt) = RegNext(x)
val delOut = delay(delay(defIn))
六、(ch7)组合搭建模块
ch7.1:组合电路
- 组合电路在chisel中的表示1:逻辑运算
- 最简单的就是定义一个变量名,其内容为布尔表达式
- val e = (a & b) | c
- val f= ~e
- 组合电路在chisel中的表示2:复用器(输出信号要定义为Wire(UInt()))
- 用chisel的when/.elsewhen/.otherwise表示二选一复用器的串联
- 用switch/is表示多选一复用器
- 说明:scala中也有if/else语句,但它不产生硬件,只是纯软件语句
ch7.2:解码器
- 以2/4解码器为例,演示switch/is语句在实现解码器中的用法
ch7.3:编码器
- 以4/2编码器为例,演示switch/is语句在实现编码器中的用法
七、(ch8)时序建造模块
“因为我们感兴趣的是同步设计,所以当我们说时序电路时,就意味着是同步时序电路”
ch8.1:寄存器
- 寄存器的时钟输入信号不需要定义:chisel已自动隐含添加
- 用输入d和输出q来定义寄存器:
val q = RegNext(d)
- 定义带reset信号的寄存器:
val valReg = RegInit(0.U(4.W))
- 定义带enable信号的寄存器:
val enableReg = Reg(UInt(4.W))
when(enable) { enableReg := inVal }
- 定义带reset和enable信号的寄存器:
val resetEnableReg = RegInit(0.U(4.W))
when(enable) { resetEnableReg := inVal }
ch8.2:计数器
- 最简单形式的计数器就是将寄存器的输出连接到加法器,而加法器的输出连接到寄存器的输入(D触发器的输入D)
ch8.2.1:向上和向下计数
- 用when条件语句,实现向上或向下计数到特定值后回到0
- 用复用器硬件,实现向上或向下计数到特定值后回到0
ch8.2.2:使用计数器产生时序
- 一个常见的实践是,在我们的电路中以f_tick频率产生单周期的tick(时钟脉冲)
ch8.2.3:nerd计数器
- 向下计数到-1的计数器:检测最高bit为1就表示计数到了-1
ch8.2.4:一个计时器
- 计时器:只计数一次的计数器
- 示例:fig8.9和listing8.1
- 示例:fig8.9和listing8.1
ch8.2.5:脉冲宽度调制
- 示例:看不懂。略过
ch8.3:位移寄存器
- 示例:串转并输出、并转串输入的实现,都是用Cat()来实现(Cat=concatenate)
ch8.3.1:使用并行输出的移位寄存器
- 示例:fig8.12,serIn从高位开始移入outReg[3:0]
val outReg = RegInit(0.U(4.W))
outReg := Cat(serIn, outReg(3, 1))
val q = outReg
ch8.3.2:并行读取的移位寄存器
- 示例:fig8.13,并行的loadReg[3:0]赋值给串行的寄存器serOut
when(load) {loadReg := d} otherwise {loadReg := Cat(0.U, loadReg(3, 1))}val serOut = loadReg(0)
ch8.4:存储器
- 存储器可以通过一系列的寄存器搭建。但基于寄存器的存储器硬件上非常昂贵,所以更大的存储器是通过sram搭建的
- 同步存储器:在输入端(读/写地址、写数据、写使能)设计了寄存器。这意味着设置地址后一个周期,读的数据就可用了。
- 用chisel库函数SyncReadMem构建的存储器模块只是最基本的存储器:可以指定byte数,但输入、输出data的宽度固定为1byte,另外还有一个写使能。剩下的定义需要外部重新封装。
- 有一个有趣的问题:当在进行写操作的同一个时钟周期,对同一个地址进行读操作,会读到什么值、我们对存储器的read-during-write行为感兴趣。
- 有三种可能:新值、旧值或未定义的值(新值和旧值不同bit的混合)。
- 发生在fpga上的可能性取决于fpga的类型,有时还可以指定。
- 示例:fig8.15,使用添加前递电路来使得read-during-write输出新值
class ForwardingMemory() extends Module {val io = IO(new Bundle {val rdAddr = Input(UInt(10.W))val rdData = Output(UInt(8.W))val wrEna = Input(Bool())val wrData = Input(UInt(8.W))val wrAddr = Input(UInt(10.W))})val mem = SyncReadMem(1024, UInt(8.W))val wrDataReg = RegNext(io.wrData )val doForwardReg = RegNext(io.wrAddr === io.rdAddr && io.wrEna)val memData = mem.read(io.rdAddr)when(io.wrEna) {mem.write(io.wrAddr, io.wrData)}io.rdData := Mux(doForwardReg, wrDataReg, memData)}
- ch8.5:练习
- 略
八、(ch9)输入处理
ch9.1:异步输入
- 异步输入因为没有时钟,所以直接输出到触发器,可能会违反触发器输入的建立和保持时间,导致触发器的多稳态,甚至震荡;
- 解决的方法是:使用“输入同步器”,即两个触发器串联(比如A和B),因为触发器是同步于时钟的,所以即使A输出可能是多稳态,但B输出可以是稳定的;
- 实现:
val btnSync = RegNext(RegNext(btn))
ch9.2:防抖动
- 示例:在100MHz下,每隔10ms采样一次,以确认电平的变化,实现防抖动(要用到计数器,产生防抖动周期)
val FAC = 100000000/100val btnDebReg = Reg(Bool())val cntReg = RegInit(0.U(32.W))val tick = cntReg === (FAC-1).U //相当于bool变量的定义:tick为cntReg寄存器和(FAC-1).U常量的比较结果(硬件);虽然后面没有显式地更新tick,但它在硬件运行过程中不断自动变化。cntReg := cntReg + 1 .Uwhen (tick) {cntReg := 0.UbtnDebReg := btnSync}
ch9.3:输入信号滤波
- 输入信号中有噪声,但不想用以上的两种方法来排除(输入同步器、防抖动滤波),所以这里提出第三种处理方法:使用投票电路;
- 实际这样的投票电路非常少用;
- 对信号进行相同周期间隔的三次采样,输出结果取两次相同的值(要用到计数器,产生采样周期);
- 示例:(略)
ch9.4:使用函数合并输入处理
- 第一次给出一个结合def定义函数、val定义变量的组合成的模块
- 这个示例实现的功能是:有滤波处理的计数器
- 对输入信号(按键输入)进行3次投票实现滤波:
def filter(v: Bool, t: Bool);
- 投票的时间间隔(周期)由另一个函数实现:
def tickGen(fac: Int);
- 对滤波后的信号寻找上升沿,定义一个计数器对该上升沿进行+1,实现了一个对外部信号的计数器
- 对输入信号(按键输入)进行3次投票实现滤波:
ch9.5:练习: (略)
九、(ch10)有限状态机
ch10.1:基本有限状态机 (Moore FSM为例)
- FSM:Finite-States Machine, 有限状态机,在chisel中是作为module内部的一部分;
- 状态机的核心语句:
- 状态定义1:用Enum枚举,状态名称自动被综合工具用二进制编码代替(当前chisel版本决定),如:val <状态1> :: <状态2> :: <状态3> :: Nil = Enum(<状态的个数,这里为3>)
- 状态定义2:用Enum枚举,状态名称使用定义chisel常量(当前chisel版本需要显式使用才行);这不是常用编码,示例略;
- 状态使用:“状态”定义为寄存器,如:
val stateReg = RegInit(<状态1>)
- 状态切换:用switch/is语句:实现硬件的多选一复用器;
ch10.2:使用Mealy FSM产生快速输出
- Moore FSM:输出由当前状态、当前输入决定,状态图的转换箭头用“<输入>”来标记;
- Mealy FSM:输出由当前输出、当前输入决定,状态图的转换箭头用“<输入>/<输出>”来标记;
- 示例:边沿检测电路
- 不用状态机表示时,最简单的方法是一行chisel代码:val risingEdge = din & !RegNext(din)
- 用Mealy状态机时,核心语句也是Enum、switch/is;
- Mealy状态机代码:略;
ch10.3:Moore对比Mealy
- 还是以最简单的“上升沿检测电路”为例,对比两者的优缺点
- Moore FSM:
- 优点:存在能切断组合路径的一个状态寄存器,所以不会发生FSM通信相关的两个问题(Mealy的缺点),这在稍微大一些的设计中尤为重要;
- 缺点1:硬件实现所需要的逻辑比Mealy多一倍;
- 缺点2:对输入信号的上升沿检测,最快也要同步到最近的一个时钟,不能同步于输入信号;
- Mealy FSM:
- 优点1:硬件实现所需要的逻辑比Moore少;
- 优点2:对输入信号的上升沿检测,能跟随输入信号,而不用等待、同步于时钟信号;
- 缺点1:Mealy内部用于FSM通信的组合路径,实际的设计会比较长;
- 缺点2:如果FSM通信构成一个圆圈,那么组合路径也会形成一个环回,这在同步设计中会是个错误;
- 总结1:Moore在FSM通信的组合中更好,因为它比Mealy更稳定;
- 总结2:除非关注在当前周期下FSM的反应,才会用Mealy(因为它的输出同步于输入信号、而不是时钟);
- 总结3:类似“上升沿检测电路”这种小电路,Mealy也很实用;
ch10.4:练习: (略)
十、(ch11)状态机通信
“通常问题会很复杂,以至于不能用单个fsm去描述。这种情况下,问题可以被分为两个或更多的更小、更简单的fsm。然后那些fsm使用信号去通信。一个fsm的输出是另一个fsm的输入,同时也观察其它fsm的输出。当我们分成一个大的fsm为许多简单fsm,这称为“分解fsm”。但是,fsm通信经常直接根据spec来设计,因为如果实现成单个fsm会是不可实现的大。”
ch11.1:一个灯光闪烁器的例子
- 示例的要求:
- 状态机输入一个周期的start时,触发灯光闪烁器的序列,输出为light信号,有on/off两种状态
- 一个序列闪烁三次
- 每次闪烁表示为:light=on,6个周期;light=off,4个周期
- 闪烁序列完成后,fsm变为light=off,等待下一次start触发开始
- 状态机1:
- 实现为单个状态机
- 计算一共会有27个状态;
- 状态机2:
- 实现为分解的两个状态机:master和timer
- master状态机:输出timerLoad信号,控制timer开始;输出timerSelect信号,选择计时时间为6或4;输入信号timerDone,表示timer状态机已完成计时
- timer状态机:根据master输入的timerLoad、timerSelect开始计时,完成后输出timerDone
- 状态机3:
- 优化状态机2,分解为三个状态机:master、timer、counter
- master状态机:(同上,)另外还有3个信号:输出cntLoad,表示闪烁剩余次数从2开始;输出cntDecr信号,表示timer状态机(经过master状态机)单次闪烁完成,次数可减1;输出cntDone信号,表示闪烁剩余次数归0
- timer状态机:(同上)
- counter状态机:根据master输入的cntLoad、cntDecr开始倒计数,闪烁次数归0后后输出timerDone
ch11.2:位1计数(器)的例子: (略)
ch11.3:ready-valid接口
- ready/valid接口是一个分别在发送端定义data/valid、接收端定义ready信号的简单控制流接口
- 为了让ready/valid接口可以集成到其它模块,ready和valid都不允许组合性依赖。因为这个接口比较常用,所以chisel定义了DecoupledIO线束,定义类似如下:
class DecoupledIO [T <: Data] (gen: T) extends Bundle {val ready = Input(Bool())val valid = Output(Bool())val bits = Output(gen)}
- ready/valid接口有一个问题:
- 即:“ready和valid在全部有效以后是否可能自动清零?”
- 这个问题可能发生在:发送端的valid或接受端的ready,在使能一段时间后就分别由于别的(意外)事件导致清零;然后数据无效,导致没有数据传输
- 解决:上述两种行为(情况)是否被允许,并不属于ready/valid接口的内容;但是它需要在接口的具体使用上被定义
- 方案1:使用IrrevocableIO类
- 使用DecoupledIO类的时候,chisel没有对ready/valid信号的交互行为做限制条件;
- 但IrrevocableIO类会有限制条件(只是一个习惯、而不是强制规范?)——是对于接收端的:
- “一个具体的ReadyValidIO的子类,当valid是高位,ready是低位,保证不会在bits数值改变的一个周期后改变;
也就是说,一旦valid升高,它就不会变低,直到下一个ready也升高。”
- 方案2:以AXI接口为参考
- 它对以下的4个总线操作使用了rady/valid接口:读地址、写地址、读数据、写数据;
- AXI提出的限制是:一旦ready或valid为高,就直到发生了数据传输才能拉低
十一、(ch12)硬件生成器
ch12.1:一点scala的内容:
- val变量:定义一个(硬件组件)表达式,但不能被赋值;(尝试重新赋值会在编译时报错)
- var变量:定义一个(硬件生成器?)表达式,且能被赋值;
- val和var变量的类型:隐式类型,由scala编译时自动推断;显式类型,可以类似这样定义:val number:Int=42
- “:=”:这种赋值是chisel的操作符,而不是scala的操作符;
- if/else语句:在进行电路生成的scala进行时执行,并不生成硬件复用器(复用器的生成方法是when/.elsewhen/.otherwise和switch/is语句);
ch12.2.1:使用参数配置:
- 示例:参数化位宽的加法器
val add8 = Module(new ParamAdder(8))val add16 = Module(new ParamAdder(16))
ch12.2.2:使用类型参数的函数:
- 示例1:二进一出、io类型支持自定义的复用器
def myMux[T <: Data](sel: Bool, tPath: T, fPath: T): T = {...}
- 上面的def函数表示:
- 整个函数头中T表示chisel类型系统的根类型Data
- 第二个参数tPath和第三个参数fPath都使用T类型
- 函数的返回值也使用T类型
- 示例2:二进一出、io类型支持自定义的复用器
def myMux[T <: Data](sel: Bool, tPath: T, fPath: T): T = {val ret = Wire(fPath.cloneType)...ret}
- 上面的def函数新增了:
- 用chisel内置的.cloneType来获取参数的类型,来作为返回值的类型(实际上这个用法很少用;Nutshell代码里就没有)
ch12.2.3:具有类型参数的模块
- 模块和函数的区别(?):
- 模块定义:
class xx(xx) extends Module {...}
- 函数定义:
def xx(xx) = {...}
- 模块定义:
- 示例:noc芯片(network-on-chip,核间的片上网络路由)
class NocRouter[T <: Data](data: T, n: Int) extends Module {val io = IO(new Bundle {val inPort = Input(Vec(n, data))val address = Input(Vec(n, UInt(8.W)))val outPort = Output(Vec(n, data))})}class Payload extends Bundle {val data = UInt(16.W)val flag = Bool()}val router = Module(new NocRouter(new Payload, 2))
- 上面的示例表示:
- 定义一个noc芯片,数据输入、输出端口(线束bundle)的类型是参数化、可自定义的(甚至连bundle的组数也是参数化的)
- noc芯片的输入、输出端口每一组bundle的类型,是通过先定义Bundle类,再把该类作为参数传给模块的(上例即class Payload)
ch12.2.4:参数化的捆束(Bundle)
- 当在Vec内部使用bundle时,需要对参数声明为私有的参数化类型?否则会一直使用到最上层调用时传参传来的类型
- 示例:
val router = Module(new NocRouter2(new Port(new Payload), 2))class NocRouter2[T :< Data](dt: T, n: Int) extends Module {val io = IO(new Bundle) {...val inPort = Input(Vec(n, dt))}}class Port [T <: Data](private val dt: T) extends Bundle {...val address = dt.cloneType //保证这里cloneType的结果就是Port()定义时选用的参数类型T?}
ch12.3:生成组合逻辑
- 从外部读取文本文件来生成逻辑表?
- 示例:(略)
ch12.4:使用继承
- 示例:对基本计数器定义一个必有的输出信号tick,然后基于对这个基本计数器的继承,来实现定义多种定时器
abstract class Ticker (n:Int) extends Module {val io = IO(new Bundle {val tick = Output(Bool())})}class UpTicker(n:Int) extends Ticker(n) {...io.tick := cntReg === N}class DownTicker(n:Int) extends Ticker(n) {...io.tick := cntReg === N}class NerdTicker(n:Int) extends Ticker(n) {...io.tick := false.Bwhen(...) {io.tick := true.B}}
- 顺便给出单元测试示例:PeekPokeTester(实际Nutshell和香山都没有用这个来进行单元测试)
import chisel3.iotesters.PeekPokeTesterimport org.scalatest._class TickerTester[T <: Ticker](dut: T, n: Int) extends PeekPokeTester(dut: T) {...step(1)}class TickerSpec extends FlatSpec with Matchers {"UpTicker 5" should "pass" in {chisel3.iotesters.Driver(() => new UpTicker(5)) { c =>new TickerTester(c, 5)} should be (true)}"DownTicker 7" should "pass" in{chisel3.iotesters.Driver(() => new DownTicker(7)) { c =>new TickerTester(c, 7)} should be (true)}"NerdTicker 11" should "pass" in{chisel3.iotesters.Driver(() => new NerdTicker(11)) { c =>new TickerTester(c, 11)} should be (true)}}
执行命令以开始单元测试:sbt "testOnly TickerSpec"
ch12.5:使用函数式编程做硬件生成
- 将实现了硬件生成的基本函数a作为一个参数,传给另一个函数作为参数b,以被调用来生成多个、或组合的新硬件模块
- 示例1:将基本的二进一出加法器作为向量操作函数vec的参数,来实现多进一出的加法链(向量加法器)
def add(a:UInt, b:UInt) = a + bval sum = vec.reduce(add)
- 示例2:(优化)把示例1直接写成一行语句(利用scala通配符"_")
val sum = vec.reduce(_ + _)
- 示例3:(优化)把示例2的组合性延迟降低
- 上述语句实现的一串加法链会产生多个时钟延迟;
- 如果我们不信任综合工具会正确重新排列这个加法链,我们可以用chisel的reduceTree方法去生成一个加法器的树
val sum = vec.reduceTree(_ + _)
十二、(ch13)示例设计
ch13.1:fifo缓冲器
- 示例1:单级fifo(寄存器)
- 单级fifo就是单个支持读写异步操作的数据寄存器
- 写入侧(enqueueing)的信号包括:输入写控制write、输出满标志full、输入数据din
- 读出侧(dequeueing)的信号包括:输入读控制read、输出空标志empty、输出数据dout
class WriterIO(size: Int) extends Bundle {val write = Input(Bool())val full = Output(Bool())val din = Input(UInt(size.W))}class ReaderIO(size: Int) extends Bundle {val read = Input(Bool())val empty = Output(Bool())val dout = Output(UInt(size.W))}class FifoRegister(size: Int) extends Module {val io = IO(newBundle{val enq = new WriterIO(size)val deq = new ReaderIO(size)val empty::full::Nil = Enum(2) //即使是单级fifo,也是一个小状态机val stateReg = RegInit(empty)val dataReg = RegInit(0.U(size.W))... //状态机实现})
- 示例2:冒泡fifo(单级fifo的数组的串联)
- 用scala的Array.Fill(){}来定义单级fifo串联的冒泡fifo
- 每个相邻单级fifo的输入、输出信号分别相连,以实现自动的数据搬移控制
class BubbleFifo(size: Int, depth: Int) extends Module {val io = IO(new Bundle {val enq = new WriterIO(size)val deq = new ReaderIO(size)})val buffers = Array.fill(depth) {Module(new FifoRegister(size))}for(i <- 0 until depth - 1) {buffers(i+1).io.enq.din := buffers(i).io.deq.doutbuffers(i+1).io.enq.write := ~buffers(i).io.deq.emptybuffers(i).io.deq.read := ~buffers(i+1).io.enq.full}io.enq <> buffers(0).io.enq //Bundle的整体双向互联,可用批量连接运算符"<>":Bundle中识别为同名的信号val,会互联到一起io.deq <> buffers(depth-1).io.deq}
ch13.2:一个串口端口
- 示例1:不带fifo的串口发送端tx
- 包括:11bit宽的移位寄存器、时钟到波特率的分频值寄存器、移位剩余bit数寄存器
class Tx(frequency: Int, baudRate: Int) extends Module {val io = IO(newBundle{val txd = Output(Bits(1.W))val channel = newChannel()})val BIT_CNT = ((frequency+baudRate/2)/baudRate - 1).asUInt()val shiftReg = RegInit(0x7ff.U) //移位寄存器:bit0输出到输出引脚tdx,即右移,低bit先发val cntReg = RegInit(0.U(20.W)) //分频系数寄存器:从时钟频率到串口波特率的分频val bitsReg = RegInit(0.U(4.W)) //移位bit数计数寄存器:从11个bit倒计数到0io.channel.ready := (cntReg === 0.U) && (bitsReg === 0.U)io.txd := shiftReg(0)when(cntReg === 0.U){cntReg := BIT_CNTwhen(bitsReg =/= 0.U) { //chisel中“不等于”的运算符是这样表示的:"=/="val shift = shiftReg>>1shiftReg := Cat(1.U,shift(9,0)) //寄存器的移位操作:总是用Cat(新bit值, 其余bit值)来实现的bitsReg := bitsReg
【从嵌入式视角学习香山处理器】四、Chisel语言基础相关推荐
- 【从嵌入式视角学习香山处理器】六、NutShell代码结构(乱画的框图)
文章目录 一.前言 二.简单粗暴版:最终成品的框图 三.不要太凌乱版:去掉连线后的框图 一.前言 这是从上一篇文章<[从嵌入式视角学习香山处理器]五.香山开发工作流实践1:主要子模块工程之间的关 ...
- pwn学习总结(四)—— 堆基础知识(持续更新)
pwn学习总结(四)-- 堆基础知识(持续更新) 前言 chunk 使用中(分配后) 空闲中(释放后) 堆块大小 空间复用 bins fastbin unsorted bin small bin 前言 ...
- 【数据库学习笔记】Day03 - SQL语言基础及数据库定义功能
[数据库学习笔记]Day03 - SQL语言基础及数据库定义功能 〇.本文所用数据库表格: 一.关系运算: 关系运算,数学名词,基本运算有两类:一类是传统的集合运算(并.差.交等),另一类是专门的关系 ...
- 学习大数据需要什么语言基础
Python易学,人人都可以掌握,如果零基础入门数据开发行业的小伙伴,可以从Python语言入手. Python语言简单易懂,适合零基础入门,在编程语言排名上升最快,能完成数据挖掘.机器学习.实时计算 ...
- Java 学习(一)Java语言基础
Java 语言基础(一) 前言 一.注释和标识符 1. 注释 2. 字符集 3. 标识符 4. 关键字(略) 二.数据类型 1.基本类型 2.引用类型 三. 常量和变量 1.常量 2.变量 四.操作符 ...
- java语言定义一个具备栈功能的类_Java学习笔记 第二章 Java语言基础
第二章 JAVA语言基础 一.关键字 1.关键字的定义和特点 定义:被Java语言赋予了特殊含义的单词 特点:关键字中所有的字母都为小写 2.用于定义数据类型的关键字 c;ass interface ...
- java入门学习笔记(二)—— Eclipse入门学习之快捷键、java语言基础知识之各类关键字及其用法简析
一.Eclipse入门学习 1. 快捷键 对于一个编辑器,快捷键必不可少,是十分好用且有效的工具. 对于一个初学者,首先掌握了如下快捷键. (很多通用的快捷键不多说) Ctrl + / -- 注释当前 ...
- QML学习【一】QML语言基础
QML语言基础 首先我们要理解QML语言是什么,我们可以了联想C++与STL的关系来理解QML与Qt Quick的关系.QML是一种说明性语言,支持ECMAScript表达式.如果说你有学习过QT那么 ...
- stm32滴答计时器_STM32嵌入式开发学习笔记(四):使用滴答计时器实现精准计时...
前面我们讲过,因为在STM32上没有系统时间的接口,因此无法调用sleep函数,在本文中,笔者将利用滴答计时器实现精准延时. 查阅技术手册,滴答计时器依赖于一个SysTick_Type类型寄存器,定义 ...
- 【C语言进阶深度学习记录】四 C语言中的类型转换
今天学习C语言中的类型转换,包括隐式类型转换和显示类型转换 文章目录 1 C语言中的数据类型转换 1.1 强制类型转换 1.11 强制类型转换代码分析 1.2 隐式类型转换 1.21 隐式类型转换代码 ...
最新文章
- PTA数据结构与算法题目集(中文)7-29
- Linux常用命令——hostname
- 67 个节省开发者时间的实用工具、库与资源(前端向)
- Android监视返回键
- [数据结构]快速排序
- 计算机对口升学试题英语,对口招生考试对口升学英语模拟试卷试题.docx
- 传智播客pscs6ppt_freeCodeCamp播客直播。 这是6集,您现在可以狂欢。
- Photoshop CS6将多张图片合成GIF动态图或视频,并将其保存导出
- python爬取json数据_Python爬取数据保存为Json格式的代码示例
- Qtcreator中经常使用快捷键总结
- 超详细的Java面试题总结(三)之Java集合篇常见问题
- shp数据制作3DTiles白膜
- MATLAB图像分割系统GUI设计
- ubuntu1804安装YouCompleteMe 配置vim
- sqlldr mysql_Oracle中的SQLLDR工具使用
- 连接跟踪子系统之helper
- mac清理软件哪个好用?五大Mac Cleaner介绍推荐
- laravel框架excel扩展包maatwebsite-excel升级3.1版本兼容处理
- 赫夫曼压缩解压(java)
- python识图找图_【python 图像识别】图像识别从菜鸟
热门文章
- 黑鲨重装计算机安装无法继续,示例黑鲨装机大师装机失败无法开机怎么办?
- 用云服务器搭建一个属于自己的网站(手把手教学)
- python--修改证件照的大小
- 计算机445 135 139端口,关闭445 135 137 138 139端口方法图文教程
- 学习某一门技术的步骤(韩顺平老师提供)
- 内网穿透的几种方式-免费与收费(钉钉、Frp、花生壳、nat123)
- DirectX--给视频加马赛克、字符OSD
- java播放MP3/APE音乐文件
- android中如何如何让dailog横屏显示
- java_opts 与catalina_opts区别_CATALINA_OPTS和 JAVA_OPTS区别