【Chisel硬件源源语】


前言

通过之前的学习,读者应当已经掌握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其他官方资料以扩展学习。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Chisel 是一种基于 Scala 语言硬件描述语言,它支持硬件描述与数字系统设计。与传统的硬件描述语言相比,Chisel 使用了更加现代化的法结构,使得数字系统的设计更加简洁、灵活。Chisel 与数字系统设计 pdf 的关系在于,它可以帮助工程师们在数字系统设计过程中更加高效地进行开发,提高设计的灵活性和可重用性。 Chisel 语言的特点之一是支持硬件生成,这意味着它能够生成 Verilog 或者 VHDL 代码,从而可以与现有的数字系统设计工具兼容,同时也可以很好地与其他硬件描述语言一起协同工作。此外,Chisel 还提供了更加强大的抽象能力,支持参数化的模块化设计,从而可以更加高效地进行硬件设计和验证。 数字系统设计 pdf 是一本介绍数字系统设计原理和实践的教材,它包含了数字系统设计的基本概念、原理和方法。Chisel 与数字系统设计 pdf 的关系在于,它可以作为一种工具,帮助读者更好地理解和应用数字系统设计的知识。通过使用 Chisel 进行硬件描述和设计,读者可以在实践中加深对数字系统设计 pdf 中所学内容的理解,并将其应用到实际的硬件开发项目中去。 总的来说,Chisel 语言与数字系统设计 pdf 有着密切的关系,它们可以相互促进,帮助工程师和学习者更加高效地进行数字系统设计和开发。通过掌握 Chisel 语言并结合数字系统设计 pdf 的知识,可以使数字系统设计的学习和实践变得更加顺畅和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kiri1001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值