【Chisel硬件源源语】

2023-10-26


前言

通过之前的学习,读者应当已经掌握Chisel的基本数据类型和操作符的知识,并且了解如何构建小型电路。而在实际大型电路工程中,常用的硬件模块无需重复编写,Chisel提供了丰富的硬件原语,帮助工程师构建大型电路工程。


多路选择器

在Chisel中,有几种不同的多路选择器方式可以实现,包括MuxMuxCaseMuxLookupMux1H

  • MuxMux函数用于创建一个简单的二选一多路选择器。
   val sel = Wire(Bool())
   val in0 = Wire(UInt(4.W))
   val in1 = Wire(UInt(4.W))
   val out = Mux(sel, in0, in1)
  • MuxCaseMuxCase是一种通用的多路选择器,可以处理多个输入。它接受一个选择信号和一系列输入-输出对(键值对),根据选择信号的值选择相应的输入作为输出。如果选择信号的值没有匹配到任何键值对,则可以提供一个默认值作为最后的参数。
   val sel = Wire(UInt(2.W))
   val in0 = Wire(UInt(4.W))
   val in1 = Wire(UInt(4.W))
   val in2 = Wire(UInt(4.W))
   val out = MuxCase(0.U, Array(
     (sel === 0.U) -> in0,
     (sel === 1.U) -> in1,
     (sel === 2.U) -> in2
   ))
  • MuxLookupMuxLookup多路选择器,它接受一个选择信号、一个默认值和一个选择表。选择表是一个由选择信号的值和对应的输入构成的键值对数组。根据选择信号的值,在选择表中查找匹配的键值对,并选择对应的输入作为输出。如果选择信号的值没有匹配到任何键值对,则选择默认值作为输出。
   val sel = Wire(UInt(2.W))
   val in0 = Wire(UInt(4.W))
   val in1 = Wire(UInt(4.W))
   val in2 = Wire(UInt(4.W))
   val out = MuxLookup(sel, 0.U, Array(
     (0.U) -> in0,
     (1.U) -> in1,
     (2.U) -> in2
   ))
  • Mux1HMux1H函数是一种高级的多路选择器,它接受一个选择信号和一系列输入。选择信号可以是一个位宽为N的编码信号,其中只有一个比特位为1,其他都为0。根据选择信号的编码,Mux1H选择对应位置为1的输入作为输出。
   val sel = Wire(UInt(3.W))
   val inputs = VecInit(Seq(in0, in1, in2, in3))
   val out = Mux1H(sel,inputs)

优先编码器

优先编码器(Priority Encoder)是一种数字电路,用于将多个输入信号中的最高优先级信号编码为对应的输出信号。

在Chisel中,PriorityEncoderPriorityEncoderOH是两个预定义的模块,用于实现优先编码器和优先编码器OH。

PriorityEncoder模块将多个输入信号中的最高优先级信号编码为对应的输出信号。输出信号是一个表示最高优先级输入信号的二进制编码。

import chisel3._

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

  val encoder = PriorityEncoder(io.in)
  io.out := encoder
}

MyModule模块中使用PriorityEncoder。输入信号是一个4位无符号整数(io.in),输出信号是一个2位无符号整数(io.out)。通过调用PriorityEncoder函数并传递输入信号,可以获取优先编码器的输出。

PriorityEncoderOH模块也类似地将多个输入信号中的最高优先级信号编码为对应的输出信号。不同之处在于,输出信号是一个优先编码器OH,只有一个位置为1,其他位置为0。

import chisel3._

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

  val encoder = PriorityEncoderOH(io.in)
  io.out := encoder
}

MyModule模块中使用PriorityEncoderOH。输入信号是一个4位无符号整数(io.in),输出信号是一个4位无符号整数(io.out)。通过调用PriorityEncoderOH函数并传递输入信号,可以获取优先编码器OH的输出。


仲裁器

在Chisel中,可以使用Chisel提供的库和构建模块的能力来实现仲裁器。

仲裁器用于在多个请求者之间进行冲突解决和资源分配。常见的仲裁器类型包括轮询仲裁器、优先级仲裁器和固定优先级仲裁器。

import chisel3._
import chisel3.util._

class PriorityArbiterIO[T <: Data](gen: T, n: Int) extends Bundle {
  val in = Flipped(Vec(n, Decoupled(gen)))
  val out = Decoupled(gen)
}

class PriorityArbiter[T <: Data](gen: T, n: Int) extends Module {
  val io = IO(new PriorityArbiterIO(gen, n))

  val arbiter = Module(new Arbiter(gen, n))

  for (i <- 0 until n) {
    arbiter.io.in(i) <> io.in(i)
  }

  io.out <> arbiter.io.out
}

在上述示例中,定义了一个通用的优先级仲裁器模块(PriorityArbiter),它接受一个泛型类型的数据(gen)和请求者的数量(n)作为参数。它的输入和输出接口使用了Chisel的Decoupled接口,以支持流水线通信。

在模块内部,使用了Chisel提供的Arbiter模块来实现仲裁逻辑。我们将多个输入端口(io.in)连接到Arbiter的输入(arbiter.io.in),将Arbiter的输出(arbiter.io.out)连接到输出端口(io.out)。

通过这种方式,可以构建一个优先级仲裁器模块,将多个请求者的输入连接到仲裁器,并从仲裁器的输出获得分配的资源。


队列

Queue 模块提供了灵活的配置选项,可以用于控制队列的行为和特性,如深度、流水线寄存器、读写操作的阻塞/非阻塞等。

import chisel3._
import chisel3.util._

class MyModule extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  val queue = Module(new Queue(UInt(8.W), 4, pipe=true, flow=false))

  queue.io.enq.valid := true.B
  queue.io.enq.bits := io.in

  io.out := queue.io.deq.bits
  queue.io.deq.ready := true.B
}
  • UInt(8.W): 指定队列中数据的类型为 8 位无符号整数。
  • 4: 指定队列的深度为 4,即队列可以存储 4 个元素。
  • pipe=true: 启用流水线模式,表示在读取数据时,队列将使用流水线寄存器来提高吞吐量。
  • flow=false: 禁用流控模式,表示队列在写入数据时不会等待读取端口就绪,可以连续写入数据。

通过这些配置选项,可以灵活地定制队列的行为。根据实际需求修改这些选项,以适应设计要求。

在示例中,使用 queue.io.enq.validqueue.io.enq.bits 将输入数据放入队列中,然后使用 queue.io.deq.bits 将队列中的数据输出。通过设置 queue.io.deq.ready 为真,表明输出端口已准备好接收数据。

上述示例中的队列使用的是默认时钟域和复位,可以根据需要进行调整。


存储

ROM

ROM(Read-Only Memory)是一种只读存储器,用于存储固定的数据内容。在 Chisel 中,可以使用 VecSeqMem 等组件来实现 ROM。

import chisel3._

class MyModule extends Module {
  val io = IO(new Bundle {
    val address = Input(UInt(4.W))
    val data = Output(UInt(8.W))
  })

  val romData = VecInit(Seq(10.U, 20.U, 30.U, 40.U, 50.U, 60.U, 70.U, 80.U))

  val rom = Module(new SeqMem(16, UInt(8.W), romData))

  io.data := rom.read(io.address)
}

在上述示例中,定义了一个 MyModule 模块,其中包含一个 ROM。输入接口 io.address 是一个 4 位无符号整数,输出接口 io.data 是一个 8 位无符号整数。

使用 VecInit 将固定的数据序列传递给 romData,这个序列即存储在 ROM 中的数据内容。这里简单地使用了一个 8 个元素的向量,每个元素为 8 位无符号整数。

然后,使用 SeqMem 模块实例化了一个 ROM。SeqMem 接受三个参数:depth 表示 ROM 的深度,dataType 表示数据类型,init 表示初始化数据。

romData 作为初始化数据传递给 SeqMem,从而在 ROM 中存储了指定的数据内容。

最后,使用 rom.read 方法根据输入的地址从 ROM 中读取数据,并将结果赋值给输出接口 io.data

请注意,上述示例中的 ROM 是静态的,无法在运行时进行写入操作。


RAM

RAM(Random Access Memory)是一种随机存取存储器,用于存储和读取数据。在 Chisel 中,可以使用 Mem 组件来实现 RAM。

import chisel3._

class MyModule extends Module {
  val io = IO(new Bundle {
    val address = Input(UInt(4.W))
    val dataIn = Input(UInt(8.W))
    val dataOut = Output(UInt(8.W))
    val writeEnable = Input(Bool())
  })

  val ram = Mem(16, UInt(8.W))

  when(io.writeEnable) {
    ram.write(io.address, io.dataIn)
  }

  io.dataOut := ram.read(io.address)
}

在上述示例中,定义了一个 MyModule 模块,其中包含一个 RAM。输入接口 io.address 是一个 4 位无符号整数,io.dataIn 是一个 8 位无符号整数,io.writeEnable 是一个写使能信号。输出接口 io.dataOut 是一个 8 位无符号整数。

使用 Mem 组件实例化了一个 RAM。Mem 接受两个参数:depth 表示 RAM 的深度,dataType 表示数据类型。

根据 io.writeEnable 的值来判断是否进行写操作。当 io.writeEnable 为真时,使用 ram.write 方法将 io.dataIn 写入到 RAM 中的指定地址 io.address

使用 ram.read 方法根据输入的地址从 RAM 中读取数据,并将结果赋值给输出接口 io.dataOut

通过以上设置,构建了一个 RAM,并实现了根据地址读取和写入数据的功能。


从文件向RAM写入

在 Chisel 中,可以使用 loadMemoryFromFile 函数来从文件中加载数据到内存(包括 RAM)。

loadMemoryFromFile 是 Chisel Test API 中的一个函数,用于在测试过程中将数据加载到内存中。

import chisel3._
import chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester, loadMemoryFromFile}

class MyModule extends Module {
  val io = IO(new Bundle {
    // IO定义
  })

  val ramDepth = 16
  val ramDataWidth = 8
  val ram = Mem(ramDepth, UInt(ramDataWidth.W))

  // 在测试中加载数据到 RAM
  val testDataFile = "data.txt"
  loadMemoryFromFile(ram, testDataFile)
  // ...
}

在上述示例中,假设数据存储在名为 data.txt 的文本文件中,每行一个数据。使用 loadMemoryFromFile 函数将数据从文件加载到 RAM 中。


计数器

在 Chisel 中,可以使用 Counter 组件实现计数器功能。Counter 组件提供了一种简单且方便的方式来生成计数器信号。

import chisel3._
import chisel3.util._

class MyModule extends Module {
  val io = IO(new Bundle {
    val enable = Input(Bool())
    val count = Output(UInt(8.W))
  })

  val maxCount = 255.U
  val counter = Counter(io.enable, maxCount)

  io.count := counter.value
}

在示例中定义了一个 MyModule 模块,其中包含一个计数器。输入接口 io.enable 是一个使能信号,输出接口 io.count 是一个 8 位无符号整数,用于输出计数器的当前值。

使用 Counter 组件实例化了一个计数器。Counter 接受两个参数:cond 表示计数器的使能条件,n 表示计数器的最大值。

示例中将 io.enable 作为计数器的使能条件,并将 255(即 2^8-1)作为计数器的最大值。计数器会在每个时钟周期中根据使能信号进行递增,并在达到最大值后重新归零。

计数器的初始值默认为零,但也可以通过 Counter 组件的第三个参数来指定初始值。

例如,Counter(io.enable, maxCount, startValue)


状态机

在 Chisel 中,可以使用有限状态机(Finite State Machine,FSM)来实现基于状态的控制逻辑。状态机是一种在不同状态之间转换的模型,每个状态对应着一组特定的行为或操作。

import chisel3._
import chisel3.util._

class MyModule extends Module {
  val io = IO(new Bundle {
    val input = Input(Bool())
    val output = Output(Bool())
  })

  // 定义状态
  val idle :: stateA :: stateB :: stateC :: Nil = Enum(4)
  val currentState = RegInit(idle)

  // 状态转换逻辑
  when(currentState === idle) {
    when(io.input) {
      currentState := stateA
    }
  }.elsewhen(currentState === stateA) {
    when(io.input) {
      currentState := stateB
    }.otherwise {
      currentState := idle
    }
  }.elsewhen(currentState === stateB) {
    when(io.input) {
      currentState := stateC
    }.otherwise {
      currentState := idle
    }
  }.elsewhen(currentState === stateC) {
    currentState := idle
  }

  // 状态动作逻辑
  io.output := false.B
  switch(currentState) {
    is(stateA) {
      io.output := true.B
    }
    is(stateB) {
      io.output := io.input
    }
    // 其他状态的动作逻辑
  }
}

示例中定义了一个 MyModule 模块,其中包含一个基于状态机的逻辑。输入接口 io.input 是一个布尔值,输出接口 io.output 也是一个布尔值。

使用 Enum 函数定义了四个状态:idlestateAstateBstateC。使用 RegInitcurrentState 寄存器初始化为 idle 状态。

whenelsewhen 语句根据当前状态和输入信号 io.input 来定义状态之间的转换逻辑。例如,如果当前状态为 idle,并且输入信号为高电平,则将当前状态转换为 stateA。如果当前状态为 stateA,并且输入信号为高电平,则将当前状态转换为 stateB,否则返回 idle 状态,以此类推。

switch 语句根据当前状态来定义状态动作逻辑。在示例中,当状态为 stateA 时,将输出信号 io.output 设置为高电平;当状态为 stateB 时,将输出信号 io.output 设置为输入信号 io.input 的值。

在状态转换逻辑中需要使用时钟边沿敏感的条件(例如 risingEdgefallingEdgeio.input && io.inputChanged 等)来触发状态转换和动作。


总结

更多内容可以查看Chisel其他官方资料以扩展学习。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Chisel硬件源源语】 的相关文章

随机推荐

  • day009:HTML表单标签(2)

    接上节
  • 蓝桥杯青少组省赛Python模拟题

    请你编写程序 输入一串数字 输出这串数字的总个数 输入描述 输入一行 内容为数字 输出描述 输出一个正整数 表示输入数字的总个数 样例输入 6789 样例输出 4 代码 n input print len n 已知1小时等于60分钟 1分钟
  • 不上架App Store怎么安装到非越狱苹果手机使用

    很多开发者打包了ipa却不能安装 因为ios不像安卓打包出来就能安装 如苹果手机没越狱 需要提供特定的ios证书打包的ipa才能安装到手机使用 有两种ios证书可以实现 1 ios开发者证书 个人和公司账号申请 需要添加udid 限制100
  • (c语言)单、双精度

    单精度型 1 用符号float表示 2 分配4个字符 3 数值范围3 4E 38 3 4E 38 提供七个有效数字 4 后缀为f或F表示单精度浮点型 双精度型 1 用符号double表示 2 分配8个字符 3 数值范围1 7E 308 1
  • python安装包失败 ModuleNotFoundError: No module named 'lxml'

    最近在学习python 用到了tushare这个包 但是安装的时候 总是报 import lxml html ModuleNotFoundError No module named lxml Command python setup py
  • Matplotlib绘制象限图——支持中文、箭头、自定义轴线交点

    Matplotlib绘制象限图 支持中文 箭头 自定义轴线交点 1 效果图 2 原理 2 1 绘制象限图步骤 2 1 添加文字到图表 3 源码 参考 这篇博客将介绍如何使用matplotlib绘制象限图 写这篇博客源于博友的提问 首先pye
  • GDB first impression

    I never use GDB before though I ve heard of it long times ago Well I never use it simply because I never really debug pr
  • 利用 DAC0832 设计信号发生器

    Proteus 仿真要求 利用 DAC0832 产生一个占空比为 1 3 的矩形波信号 电路图 代码图 利用 DAC0832 产生一个占空比为 1 3 的矩形波信号 include
  • SQL进阶_3

    三值逻辑和 NULL 大多数编程语言都是基于二值逻辑的 即逻辑真值只有真和假两个 而 SQL 语言则采用一种特别的逻辑体系 三值逻辑 即逻辑真值除了真和假 还有第三个值 不确定 普通语言里的布尔型只有 true 和 false 两个值 这种
  • 二分查找BinarySearch原理分析、判定树、及其变种

    二分查找BinarySearch 1 二分查找及其要求 二分查找 又叫折半查找 是一种效率较高的查找算法 1 二分查找的要求 线性表是有序表 即表中结点按关键字有序 并且要用向量作为表的存储结构 不妨设有序表是递增有序的 存储结构 二分查找
  • CARLA--车辆激光雷达安装-显示并存储数据[超详细]--[入门4]

    系列文章目录 CARLA pygame window界面大小调节两种方法 Ubuntu18 04 收藏 CRALA模拟器全网优质学习资料整合 入门 1 CARLA蓝图库可调用的车辆和地图模型名称大全 如何在carla中加入车辆群 基于tra
  • 什么是Sentinel?它能做什么

    Sentinel 是什么 随着微服务的流行 服务和服务之间的稳定性变得越来越重要 Sentinel 是面向分布式服务架构的轻量级流量控制产品 主要以流量为切入点 从流量控制 熔断降级 系统负载保护等多个维度来帮助您保护服务的稳定性 Sent
  • Linux嵌入式所有知识点-思维导图-【一口君吐血奉献】

    一 前言 很多粉丝问我 我的Linux和嵌入式当初是如何学习的 其实彭老师在最初学习的过程中 走了相当多的弯路 有些可以不学的花了太多的时间去啃 有些作为基础必须优先学习的 却忽略了 结果工作中用到这些知识时傻眼了 有些需要后面进阶阶段学习
  • opencv 识别矩形java_使用OpenCV进行矩形检测/跟踪

    HSV空间中的H通道是Hue 它对光线变化不敏感 红色范围约为 150 180 根据提到的信息 我做了以下工作 切换到HSV空间 拆分H通道 阈值并将其标准化 应用变形操作 打开 查找轮廓 按某些属性 宽度 高度 面积 比率等 进行过滤 P
  • #VCS# 常用UCLI 命令汇总

    以下 列出了常用的 UCLI 命令 均是synps 公司提供
  • 功率时延谱 matlab,改进的互功率谱时延估计算法

    前言 基于麦克风阵列的声源定位是利用麦克风阵列拾取多路声音信号 结合声源和阵列结构之间的几何关系 得到声源的位置信息 由于麦克风阵列在时域和频域的基础上增加了空域信息 因此对声音信息的处理能力明显增强 其优势主要体现于 具有空间选择性 能够
  • 病毒分析系列2

    前言 接上篇 进行病毒分析时 在进行具体的病毒行为分析前 需要或许可疑文件的基本信息 此时可以使用pe工具进行分析和获取 基本静态信息获取 一般需要获取的信息包括但不限于 程序哈希值 导入函数表 导出函数表 是否有壳 程序的位数 字符串 具
  • 数据结构--常用比较排序

    目录 一 冒泡排序 二 选择排序 三 插入排序 四 希尔排序 五 堆排序 六 快速排序 6 1 key位置选择 6 2 一趟排序的方法 hoare版本 挖坑法 前后指针法 6 3 完整排序 七 归并排序 一 冒泡排序 通过两两顺序比较 把大
  • openwrt排错实战

    1 openwrt不断重启 原因 1 没有连接外网 看门狗机制启动 解决 将外网插到WAN口即可 2 重新刷了固件后灭有授权 解决 设备联网后 访问一下这个网站 http 192 168 1 1 cgi bin auth cgi 福利 op
  • 【Chisel硬件源源语】

    Chisel硬件源源语 前言 多路选择器 优先编码器 仲裁器 队列 存储 ROM RAM 从文件向RAM写入 计数器 状态机 总结 前言 通过之前的学习 读者应当已经掌握Chisel的基本数据类型和操作符的知识 并且了解如何构建小型电路 而