文章目录

  • 一、前言
  • 二、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:信号类型与常量:

  1. 信号类型:Bits、UInt、SInt
  2. 常量:.W(表示信号类型或常量的宽度)、.U、.S
  3. 8.U(4.W)表示4bit宽度的常量8
  4. “hff”.U、“o377”.U、“b1111_1111”.U分别为十进制常量255.U在其它进制下的表示
  5. bool类型:true.B、false.B

ch4.2:组合电路:

  1. 算数操作符:(和其它语言一样的)加减乘除、取余、取反
  2. 逻辑操作符:与非或、异或、相等、不等
  3. 操作符的优先级取决于电路的赋值顺序(不同于其它语言):所以有必要使用括号
  4. chisel提供的复用器:
val result = Mux(<条件>, <条件为真的输出选择>, <条件为假的输出选择>)

ch4.3:状态寄存器:

  1. 寄存器定义:val reg = RegInit(0.U(8.W)),定义了一个八位寄存器,在复位初始化为0
  2. 寄存器用作计数器的示例:从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来构建

  1. Bundle:组合不同类型的信号
  2. Vec:组合可索引的相同类型的信号
  3. Bundle和Vec可以相互嵌套
  4. 定义一个Bundle类型、有初始值的寄存器:先创建Bundle类型的Wire变量,再给这个变量赋值,再用这个变量去定义寄存器
val initVal = Wire(new Channel())
initVal.data := 0.U
initVal.valid := false.B
val channelReg = RegInit(initVal)

四、(ch5)搭建过程和测试

ch5.1:使用sbt搭建你的项目

  1. 库文件通过build.sbt被引用
  2. 如果build.sbt设置latest.release则表示总是用最新的chisel版本,这意味着每次搭建都要联网查看maven仓库——实际上提倡无联网情况下的搭建
  3. “import <软件包名>._”表示包里的所有类都要被引用
  4. chisel工具流:参考文档中的fig5.2图,从.scala文件到生成.vcd波形文件和.v综合电路文件

ch5.2.1:PeekPokeTester

  1. chisel模块的单元测试:sbt "runMain xxx"

ch5.2.2:使用scalaTester

  1. scala模块的单元测试:sbt "testOnly xxx"

ch5.2.3:波形

  1. 在scalaTester下使用Driver.execute()代替Driver(),即可生成.vcd波形文件,用GTKWave(或ModelSim)可以打开

ch5.2.4:printf debugging

  1. printf是来源于C语言的另一种调试形式:在函数的任何地方都可以插入printf()函数
  2. printf支持C和scala两种风格
  3. 示例:略

五、(ch6)组成部分

ch6.1:chisel的组成部分是模块

  1. 模块的嵌套示例:fig6.1

    • (重要)“硬件组件”在chisel代码里称为module,所以它们都用extends Module继承的方式来定义。并且里面一定要用IO(new Bundle())定义它的全部IO——Input和Output都在里面一起定义。

ch6.2:一个运算逻辑单元

  1. 以一个简单的运算逻辑单元ALU为作为大Module的示例,讲解其内部fetch、decode、execute三个Module的互联关系
  2. 顺便引出:switch/is语句的使用,需要引入chisel3.util包

ch6.3:整体连接

  1. Bundle的整体双向互联,可用批量连接运算符"<>":Bundle中识别为同名的信号val,会互联到一起

ch6.4:使用函数的轻量级组成部分

  1. 函数(def):模块(class … extends Module)是构造硬件描述的通用方法。但是,也有一些“样板代码”可以在对模块进行声明、实例化、连接时使用(这就是函数)
  2. 示例1:用RegNext()函数构造延时一周期的新函数:
def delay(x:UInt) = RegNext(x)
  1. 示例2:调用上述函数,来定义一个“对输入变量延时两个周期后输出的变量”
def delay(x:UInt) = RegNext(x)
val delOut = delay(delay(defIn))

六、(ch7)组合搭建模块

ch7.1:组合电路

  1. 组合电路在chisel中的表示1:逻辑运算

    • 最简单的就是定义一个变量名,其内容为布尔表达式
    • val e = (a & b) | c
    • val f= ~e
  2. 组合电路在chisel中的表示2:复用器(输出信号要定义为Wire(UInt()))
    • 用chisel的when/.elsewhen/.otherwise表示二选一复用器的串联
    • 用switch/is表示多选一复用器
  3. 说明:scala中也有if/else语句,但它不产生硬件,只是纯软件语句

ch7.2:解码器

  1. 以2/4解码器为例,演示switch/is语句在实现解码器中的用法

ch7.3:编码器

  1. 以4/2编码器为例,演示switch/is语句在实现编码器中的用法

七、(ch8)时序建造模块

“因为我们感兴趣的是同步设计,所以当我们说时序电路时,就意味着是同步时序电路”

ch8.1:寄存器

  1. 寄存器的时钟输入信号不需要定义:chisel已自动隐含添加
  2. 用输入d和输出q来定义寄存器:val q = RegNext(d)
  3. 定义带reset信号的寄存器:val valReg = RegInit(0.U(4.W))
  4. 定义带enable信号的寄存器:
val enableReg = Reg(UInt(4.W))
when(enable) { enableReg := inVal }
  1. 定义带reset和enable信号的寄存器:
val resetEnableReg = RegInit(0.U(4.W))
when(enable) { resetEnableReg := inVal }

ch8.2:计数器

  1. 最简单形式的计数器就是将寄存器的输出连接到加法器,而加法器的输出连接到寄存器的输入(D触发器的输入D)

ch8.2.1:向上和向下计数

  1. 用when条件语句,实现向上或向下计数到特定值后回到0
  2. 用复用器硬件,实现向上或向下计数到特定值后回到0

ch8.2.2:使用计数器产生时序

  1. 一个常见的实践是,在我们的电路中以f_tick频率产生单周期的tick(时钟脉冲)

ch8.2.3:nerd计数器

  1. 向下计数到-1的计数器:检测最高bit为1就表示计数到了-1

ch8.2.4:一个计时器

  1. 计时器:只计数一次的计数器

    • 示例:fig8.9和listing8.1

ch8.2.5:脉冲宽度调制

  1. 示例:看不懂。略过

ch8.3:位移寄存器

  1. 示例:串转并输出、并转串输入的实现,都是用Cat()来实现(Cat=concatenate)

ch8.3.1:使用并行输出的移位寄存器

  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:并行读取的移位寄存器

  1. 示例:fig8.13,并行的loadReg[3:0]赋值给串行的寄存器serOut
 when(load) {loadReg := d} otherwise {loadReg := Cat(0.U, loadReg(3, 1))}val serOut = loadReg(0)

ch8.4:存储器

  1. 存储器可以通过一系列的寄存器搭建。但基于寄存器的存储器硬件上非常昂贵,所以更大的存储器是通过sram搭建的
  2. 同步存储器:在输入端(读/写地址、写数据、写使能)设计了寄存器。这意味着设置地址后一个周期,读的数据就可用了。
  3. 用chisel库函数SyncReadMem构建的存储器模块只是最基本的存储器:可以指定byte数,但输入、输出data的宽度固定为1byte,另外还有一个写使能。剩下的定义需要外部重新封装。
  4. 有一个有趣的问题:当在进行写操作的同一个时钟周期,对同一个地址进行读操作,会读到什么值、我们对存储器的read-during-write行为感兴趣。
    • 有三种可能:新值、旧值或未定义的值(新值和旧值不同bit的混合)。
    • 发生在fpga上的可能性取决于fpga的类型,有时还可以指定。
  5. 示例: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)}
  1. ch8.5:练习


八、(ch9)输入处理

ch9.1:异步输入

  1. 异步输入因为没有时钟,所以直接输出到触发器,可能会违反触发器输入的建立和保持时间,导致触发器的多稳态,甚至震荡;
  2. 解决的方法是:使用“输入同步器”,即两个触发器串联(比如A和B),因为触发器是同步于时钟的,所以即使A输出可能是多稳态,但B输出可以是稳定的;
  3. 实现:
    val btnSync = RegNext(RegNext(btn))

ch9.2:防抖动

  1. 示例:在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:输入信号滤波

  1. 输入信号中有噪声,但不想用以上的两种方法来排除(输入同步器、防抖动滤波),所以这里提出第三种处理方法:使用投票电路;
  2. 实际这样的投票电路非常少用;
  3. 对信号进行相同周期间隔的三次采样,输出结果取两次相同的值(要用到计数器,产生采样周期);
  4. 示例:(略)

ch9.4:使用函数合并输入处理

  1. 第一次给出一个结合def定义函数、val定义变量的组合成的模块
  2. 这个示例实现的功能是:有滤波处理的计数器
    • 对输入信号(按键输入)进行3次投票实现滤波: def filter(v: Bool, t: Bool);
    • 投票的时间间隔(周期)由另一个函数实现: def tickGen(fac: Int);
    • 对滤波后的信号寻找上升沿,定义一个计数器对该上升沿进行+1,实现了一个对外部信号的计数器

ch9.5:练习: (略)


九、(ch10)有限状态机

ch10.1:基本有限状态机 (Moore FSM为例)

  1. FSM:Finite-States Machine, 有限状态机,在chisel中是作为module内部的一部分;
  2. 状态机的核心语句:
    • 状态定义1:用Enum枚举,状态名称自动被综合工具用二进制编码代替(当前chisel版本决定),如:val <状态1> :: <状态2> :: <状态3> :: Nil = Enum(<状态的个数,这里为3>)
    • 状态定义2:用Enum枚举,状态名称使用定义chisel常量(当前chisel版本需要显式使用才行);这不是常用编码,示例略;
    • 状态使用:“状态”定义为寄存器,如:val stateReg = RegInit(<状态1>)
    • 状态切换:用switch/is语句:实现硬件的多选一复用器;

ch10.2:使用Mealy FSM产生快速输出

  1. Moore FSM:输出由当前状态、当前输入决定,状态图的转换箭头用“<输入>”来标记;
  2. Mealy FSM:输出由当前输出、当前输入决定,状态图的转换箭头用“<输入>/<输出>”来标记;
  3. 示例:边沿检测电路
    • 不用状态机表示时,最简单的方法是一行chisel代码:val risingEdge = din & !RegNext(din)
    • 用Mealy状态机时,核心语句也是Enum、switch/is;
    • Mealy状态机代码:略;

ch10.3:Moore对比Mealy

  1. 还是以最简单的“上升沿检测电路”为例,对比两者的优缺点
  2. Moore FSM:
    • 优点:存在能切断组合路径的一个状态寄存器,所以不会发生FSM通信相关的两个问题(Mealy的缺点),这在稍微大一些的设计中尤为重要;
    • 缺点1:硬件实现所需要的逻辑比Mealy多一倍;
    • 缺点2:对输入信号的上升沿检测,最快也要同步到最近的一个时钟,不能同步于输入信号;
  3. Mealy FSM:
    • 优点1:硬件实现所需要的逻辑比Moore少;
    • 优点2:对输入信号的上升沿检测,能跟随输入信号,而不用等待、同步于时钟信号;
    • 缺点1:Mealy内部用于FSM通信的组合路径,实际的设计会比较长;
    • 缺点2:如果FSM通信构成一个圆圈,那么组合路径也会形成一个环回,这在同步设计中会是个错误;
  4. 总结1:Moore在FSM通信的组合中更好,因为它比Mealy更稳定;
  5. 总结2:除非关注在当前周期下FSM的反应,才会用Mealy(因为它的输出同步于输入信号、而不是时钟);
  6. 总结3:类似“上升沿检测电路”这种小电路,Mealy也很实用;

ch10.4:练习: (略)


十、(ch11)状态机通信

“通常问题会很复杂,以至于不能用单个fsm去描述。这种情况下,问题可以被分为两个或更多的更小、更简单的fsm。然后那些fsm使用信号去通信。一个fsm的输出是另一个fsm的输入,同时也观察其它fsm的输出。当我们分成一个大的fsm为许多简单fsm,这称为“分解fsm”。但是,fsm通信经常直接根据spec来设计,因为如果实现成单个fsm会是不可实现的大。”

ch11.1:一个灯光闪烁器的例子

  1. 示例的要求:

    • 状态机输入一个周期的start时,触发灯光闪烁器的序列,输出为light信号,有on/off两种状态
    • 一个序列闪烁三次
    • 每次闪烁表示为:light=on,6个周期;light=off,4个周期
    • 闪烁序列完成后,fsm变为light=off,等待下一次start触发开始
  2. 状态机1:
    • 实现为单个状态机
    • 计算一共会有27个状态;
  3. 状态机2:
    • 实现为分解的两个状态机:master和timer
    • master状态机:输出timerLoad信号,控制timer开始;输出timerSelect信号,选择计时时间为6或4;输入信号timerDone,表示timer状态机已完成计时
    • timer状态机:根据master输入的timerLoad、timerSelect开始计时,完成后输出timerDone
  4. 状态机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接口

  1. ready/valid接口是一个分别在发送端定义data/valid、接收端定义ready信号的简单控制流接口
  2. 为了让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)}
  1. ready/valid接口有一个问题:

    • 即:“ready和valid在全部有效以后是否可能自动清零?”
    • 这个问题可能发生在:发送端的valid或接受端的ready,在使能一段时间后就分别由于别的(意外)事件导致清零;然后数据无效,导致没有数据传输
    • 解决:上述两种行为(情况)是否被允许,并不属于ready/valid接口的内容;但是它需要在接口的具体使用上被定义
  2. 方案1:使用IrrevocableIO类
    • 使用DecoupledIO类的时候,chisel没有对ready/valid信号的交互行为做限制条件;
    • 但IrrevocableIO类会有限制条件(只是一个习惯、而不是强制规范?)——是对于接收端的:
    • “一个具体的ReadyValidIO的子类,当valid是高位,ready是低位,保证不会在bits数值改变的一个周期后改变;
      也就是说,一旦valid升高,它就不会变低,直到下一个ready也升高。”
  3. 方案2:以AXI接口为参考
    • 它对以下的4个总线操作使用了rady/valid接口:读地址、写地址、读数据、写数据;
    • AXI提出的限制是:一旦ready或valid为高,就直到发生了数据传输才能拉低

十一、(ch12)硬件生成器

ch12.1:一点scala的内容:

  1. val变量:定义一个(硬件组件)表达式,但不能被赋值;(尝试重新赋值会在编译时报错)
  2. var变量:定义一个(硬件生成器?)表达式,且能被赋值;
  3. val和var变量的类型:隐式类型,由scala编译时自动推断;显式类型,可以类似这样定义:val number:Int=42
  4. “:=”:这种赋值是chisel的操作符,而不是scala的操作符;
  5. if/else语句:在进行电路生成的scala进行时执行,并不生成硬件复用器(复用器的生成方法是when/.elsewhen/.otherwise和switch/is语句);

ch12.2.1:使用参数配置:

  1. 示例:参数化位宽的加法器
        val add8 = Module(new ParamAdder(8))val add16 = Module(new ParamAdder(16))

ch12.2.2:使用类型参数的函数:

  1. 示例1:二进一出、io类型支持自定义的复用器
        def myMux[T <: Data](sel: Bool, tPath: T, fPath: T): T = {...}
  1. 上面的def函数表示:

    • 整个函数头中T表示chisel类型系统的根类型Data
    • 第二个参数tPath和第三个参数fPath都使用T类型
    • 函数的返回值也使用T类型
  2. 示例2:二进一出、io类型支持自定义的复用器
        def myMux[T <: Data](sel: Bool, tPath: T, fPath: T): T = {val ret = Wire(fPath.cloneType)...ret}
  1. 上面的def函数新增了:

    • 用chisel内置的.cloneType来获取参数的类型,来作为返回值的类型(实际上这个用法很少用;Nutshell代码里就没有)

ch12.2.3:具有类型参数的模块

  1. 模块和函数的区别(?):

    • 模块定义:class xx(xx) extends Module {...}
    • 函数定义:def xx(xx) = {...}
  2. 示例: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))
  1. 上面的示例表示:

    • 定义一个noc芯片,数据输入、输出端口(线束bundle)的类型是参数化、可自定义的(甚至连bundle的组数也是参数化的)
    • noc芯片的输入、输出端口每一组bundle的类型,是通过先定义Bundle类,再把该类作为参数传给模块的(上例即class Payload)

ch12.2.4:参数化的捆束(Bundle)

  1. 当在Vec内部使用bundle时,需要对参数声明为私有的参数化类型?否则会一直使用到最上层调用时传参传来的类型
  2. 示例:
        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:生成组合逻辑

  1. 从外部读取文本文件来生成逻辑表?
  2. 示例:(略)

ch12.4:使用继承

  1. 示例:对基本计数器定义一个必有的输出信号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}}
  1. 顺便给出单元测试示例: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:使用函数式编程做硬件生成

  1. 将实现了硬件生成的基本函数a作为一个参数,传给另一个函数作为参数b,以被调用来生成多个、或组合的新硬件模块
  2. 示例1:将基本的二进一出加法器作为向量操作函数vec的参数,来实现多进一出的加法链(向量加法器)
        def add(a:UInt, b:UInt) = a + bval sum = vec.reduce(add)
  1. 示例2:(优化)把示例1直接写成一行语句(利用scala通配符"_")
        val sum = vec.reduce(_ + _)
  1. 示例3:(优化)把示例2的组合性延迟降低

    • 上述语句实现的一串加法链会产生多个时钟延迟;
    • 如果我们不信任综合工具会正确重新排列这个加法链,我们可以用chisel的reduceTree方法去生成一个加法器的树
      val sum = vec.reduceTree(_ + _)

十二、(ch13)示例设计

ch13.1:fifo缓冲器

  1. 示例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))... //状态机实现})
  1. 示例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. 示例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语言基础相关推荐

  1. 【从嵌入式视角学习香山处理器】六、NutShell代码结构(乱画的框图)

    文章目录 一.前言 二.简单粗暴版:最终成品的框图 三.不要太凌乱版:去掉连线后的框图 一.前言 这是从上一篇文章<[从嵌入式视角学习香山处理器]五.香山开发工作流实践1:主要子模块工程之间的关 ...

  2. pwn学习总结(四)—— 堆基础知识(持续更新)

    pwn学习总结(四)-- 堆基础知识(持续更新) 前言 chunk 使用中(分配后) 空闲中(释放后) 堆块大小 空间复用 bins fastbin unsorted bin small bin 前言 ...

  3. 【数据库学习笔记】Day03 - SQL语言基础及数据库定义功能

    [数据库学习笔记]Day03 - SQL语言基础及数据库定义功能 〇.本文所用数据库表格: 一.关系运算: 关系运算,数学名词,基本运算有两类:一类是传统的集合运算(并.差.交等),另一类是专门的关系 ...

  4. 学习大数据需要什么语言基础

    Python易学,人人都可以掌握,如果零基础入门数据开发行业的小伙伴,可以从Python语言入手. Python语言简单易懂,适合零基础入门,在编程语言排名上升最快,能完成数据挖掘.机器学习.实时计算 ...

  5. Java 学习(一)Java语言基础

    Java 语言基础(一) 前言 一.注释和标识符 1. 注释 2. 字符集 3. 标识符 4. 关键字(略) 二.数据类型 1.基本类型 2.引用类型 三. 常量和变量 1.常量 2.变量 四.操作符 ...

  6. java语言定义一个具备栈功能的类_Java学习笔记 第二章 Java语言基础

    第二章 JAVA语言基础 一.关键字 1.关键字的定义和特点 定义:被Java语言赋予了特殊含义的单词 特点:关键字中所有的字母都为小写 2.用于定义数据类型的关键字 c;ass  interface ...

  7. java入门学习笔记(二)—— Eclipse入门学习之快捷键、java语言基础知识之各类关键字及其用法简析

    一.Eclipse入门学习 1. 快捷键 对于一个编辑器,快捷键必不可少,是十分好用且有效的工具. 对于一个初学者,首先掌握了如下快捷键. (很多通用的快捷键不多说) Ctrl + / -- 注释当前 ...

  8. QML学习【一】QML语言基础

    QML语言基础 首先我们要理解QML语言是什么,我们可以了联想C++与STL的关系来理解QML与Qt Quick的关系.QML是一种说明性语言,支持ECMAScript表达式.如果说你有学习过QT那么 ...

  9. stm32滴答计时器_STM32嵌入式开发学习笔记(四):使用滴答计时器实现精准计时...

    前面我们讲过,因为在STM32上没有系统时间的接口,因此无法调用sleep函数,在本文中,笔者将利用滴答计时器实现精准延时. 查阅技术手册,滴答计时器依赖于一个SysTick_Type类型寄存器,定义 ...

  10. 【C语言进阶深度学习记录】四 C语言中的类型转换

    今天学习C语言中的类型转换,包括隐式类型转换和显示类型转换 文章目录 1 C语言中的数据类型转换 1.1 强制类型转换 1.11 强制类型转换代码分析 1.2 隐式类型转换 1.21 隐式类型转换代码 ...

最新文章

  1. PTA数据结构与算法题目集(中文)7-29
  2. Linux常用命令——hostname
  3. 67 个节省开发者时间的实用工具、库与资源(前端向)
  4. Android监视返回键
  5. [数据结构]快速排序
  6. 计算机对口升学试题英语,对口招生考试对口升学英语模拟试卷试题.docx
  7. 传智播客pscs6ppt_freeCodeCamp播客直播。 这是6集,您现在可以狂欢。
  8. Photoshop CS6将多张图片合成GIF动态图或视频,并将其保存导出
  9. python爬取json数据_Python爬取数据保存为Json格式的代码示例
  10. Qtcreator中经常使用快捷键总结
  11. 超详细的Java面试题总结(三)之Java集合篇常见问题
  12. shp数据制作3DTiles白膜
  13. MATLAB图像分割系统GUI设计
  14. ubuntu1804安装YouCompleteMe 配置vim
  15. sqlldr mysql_Oracle中的SQLLDR工具使用
  16. 连接跟踪子系统之helper
  17. mac清理软件哪个好用?五大Mac Cleaner介绍推荐
  18. laravel框架excel扩展包maatwebsite-excel升级3.1版本兼容处理
  19. 赫夫曼压缩解压(java)
  20. python识图找图_【python 图像识别】图像识别从菜鸟

热门文章

  1. 黑鲨重装计算机安装无法继续,示例黑鲨装机大师装机失败无法开机怎么办?
  2. 用云服务器搭建一个属于自己的网站(手把手教学)
  3. python--修改证件照的大小
  4. 计算机445 135 139端口,关闭445 135 137 138 139端口方法图文教程
  5. 学习某一门技术的步骤(韩顺平老师提供)
  6. 内网穿透的几种方式-免费与收费(钉钉、Frp、花生壳、nat123)
  7. DirectX--给视频加马赛克、字符OSD
  8. java播放MP3/APE音乐文件
  9. android中如何如何让dailog横屏显示
  10. java_opts 与catalina_opts区别_CATALINA_OPTS和 JAVA_OPTS区别