Chisel Language II

  本文归纳总结于Chisel的官方tutorial:https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master

3 Generators  

  3.1 Parameters

Chisel提供了一个非常强大的自定义硬件生成器的功能,这一节的主要知识便是如何去正确传递参数值来实现自定义hardware。

    3.1.1 参数传递

import chisel3._
import chisel3.util._

class ParameterizedWidthAdder (in0Width: Int, in1Width: Int, sumWidth: Int) extends Module {
  require(in0Width >= 0)
  require(in1Width >= 0)
  require(sumWidth >= 0)

  val io = IO(new Bundle {
    val in0 = Input(UInt(in0Width.W))
    val in1 = Input(UInt(in1Width.W))
    val sum = Output(UInt(sumWidth.W))
  })
  // a +& b includes the carry, a + b does not
  io.sum := io.in0 +& io.in1
}

object ParameterizedWidthAdder  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new ParameterizedWidthAdder (1,4,6))
    println(getVerilogString (new ParameterizedWidthAdder (1,4,6) ))
}


   以上代码可以生成一个自定义的位数的adder,如在生产的verilog中使用了1位的io.in0,4位的io.in1和6位的sum。其中+&表示计算结果可以进位,普通的+不包含进位。require可以在调用函数的时候提前对parameter进行检验。
   以下例子是基于2.3中的sort:


import chisel3._

import chisel3.util._

class Sort4_a (ascending: Boolean) extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(16.W))
    val in1 = Input(UInt(16.W))
    val in2 = Input(UInt(16.W))
    val in3 = Input(UInt(16.W))
    val out0 = Output(UInt(16.W))
    val out1 = Output(UInt(16.W))
    val out2 = Output(UInt(16.W))
    val out3 = Output(UInt(16.W))
  })
   
  // this comparison funtion decides < or > based on the module's parameterization
  def comp(l: UInt, r: UInt): Bool = {
      if (ascending) {
        l < r
      } else {
        l > r
    }
  }

  val row10 = Wire(UInt(16.W))
  val row11 = Wire(UInt(16.W))
  val row12 = Wire(UInt(16.W))
  val row13 = Wire(UInt(16.W))

  when(comp(io.in0, io.in1)) {
    row10 := io.in0            // preserve first two elements
    row11 := io.in1
  }.otherwise {
    row10 := io.in1            // swap first two elements
    row11 := io.in0
  }

  when(comp(io.in2, io.in3)) {
    row12 := io.in2            // preserve last two elements
    row13 := io.in3
  }.otherwise {
    row12 := io.in3            // swap last two elements
    row13 := io.in2
  }

  val row21 = Wire(UInt(16.W))
  val row22 = Wire(UInt(16.W))

  when(comp(row11, row12)) {
    row21 := row11            // preserve middle 2 elements
    row22 := row12
  }.otherwise {
    row21 := row12            // swap middle two elements
    row22 := row11
  }

  val row20 = Wire(UInt(16.W))
  val row23 = Wire(UInt(16.W))
  when(comp(row10, row13)) {
    row20 := row10            // preserve the first and the forth elements
    row23 := row13
  }.otherwise {
    row20 := row13            // swap the first and the forth elements
    row23 := row10
  }

  when(comp(row20, row21)) {
    io.out0 := row20            // preserve first two elements
    io.out1 := row21
  }.otherwise {
    io.out0 := row21            // swap first two elements
    io.out1 := row20
  }

  when(comp(row22, row23)) {
    io.out2 := row22            // preserve first two elements
    io.out3 := row23
  }.otherwise {
    io.out2 := row23            // swap first two elements
    io.out3 := row22
  }
}

object Sort4_a  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new Sort4_a(true))
    println(getVerilogString (new Sort4_a(true)))
}

和2.3的区别是,在定义module的时候提供了一个参数,这个参数会被module里的comb函数调用,来决定之后的sort是按降序还是升序进行。


 3.1.2 Option and Default Arguments

val map = Map("a" -> 1)

val a = map("a")

println(a)

val b = map("b")

println(b)



val map = Map("a" -> 1)
val a = map.get("a")
println(a)
val b = map.get("b")
println(b)


val some = Some(1)
val none = None
println(some.get)          // Returns 1
// println(none.get)       // Errors!
println(some.getOrElse(2)) // Returns 1
println(none.getOrElse(2)) // Returns 2

  以上三类代码展示了scala中的map功能和Option功能中的none和some。 Map可以定义一个键值组合,map("a")可以直接获得a的值,而.get是可以做到同样的功能。 Some和none可以给一个变量定义值或是不赋予任何值,getOrElse则是可以判定变量是否有值,若有值则get,无值则传递getOrElse(a)中的a这个值。

3.1.3 Options for Parameters with Defaults

import chisel3._

import chisel3.util._

class DelayBy1(resetValue: Option[UInt] = None) extends Module {
    val io = IO(new Bundle {
        val in  = Input( UInt(16.W))
        val out = Output(UInt(16.W))
    })
    val reg = if (resetValue.isDefined) { // resetValue = Some(number)
        RegInit(resetValue.get)
    } else { //resetValue = None
        Reg(UInt())
    }
    reg := io.in
    io.out := reg
}

object DelayBy1  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new DelayBy1(Some(3.U)))
//   (new chisel3.stage.ChiselStage).emitVerilog(new DelayBy1())
  println(getVerilogString(new DelayBy1()))
}

以上代码运用了option功能来提供一个是否初始化reg的功能,如若不提供option的值,比如some(3.U),那么因为默认的resetValue是None,那么reg就会被初始为垃圾值,避免了出现了莫名其妙的值来表示无意义。.isDefined可以检测option值中是否被赋值。此后的reg:=io.in只是正常的运用寄存器来使数值传递delay一个周期,与option功能并无关联。

3.1.4 Match/Case Statements

// y is an integer variable defined somewhere else in the code

val y = 7

/// ...

val x = y match {

  case 0 => "zero" // One common syntax, preferred if fits in one line

  case 1 =>        // Another common syntax, preferred if does not fit in one line.

      "one"        // Note the code block continues until the next case

  case 2 => {      // Another syntax, but curly braces are not required

      "two"

  }

  case _ => "many" // _ is a wildcard that matches all values

}

println("y is " + x)


scala中match case是一种类似于C语言中的switch case, 上面语句的逻辑是: x会等于一个值,这个值是由y和哪一个case的情况match来决定的,macth case会在case成功后停止match并返回=>后的值给x。case _ 是万能case,不论什么情况都会和match成功,用来当作最后一个case来提示macth失败比较合适。

def animalType(biggerThanBreadBox: Boolean, meanAsCanBe: Boolean): String = {
  (biggerThanBreadBox, meanAsCanBe) match {
    case (true, true) => "wolverine"
    case (true, false) => "elephant"
    case (false, true) => "shrew"
    case (false, false) => "puppy"
  }
}
println(animalType(true, true))

以上代码便运用了match case制作了判定动物类型的函数。

val sequence = Seq("a", 1, 0.0)
sequence.foreach { x =>
  x match {
    case _: Int | _: Double => println(s"$x is a number!")
    case _ => println(s"$x is an unknown type!")
  }
}

 也可通过 ':'+type来match数据类型,如上代码所示。| 表示并集。

import chisel3._

import chisel3.util._

class DelayBy1_1(resetValue: Option[UInt] = None) extends Module {
  val io = IO(new Bundle {
    val in  = Input( UInt(16.W))
    val out = Output(UInt(16.W))
  })
  val reg = resetValue match {
    case Some(r) => RegInit(r)
    case None    => Reg(UInt())
  }
  reg := io.in
  io.out := reg
}

object DelayBy1_1  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new DelayBy1_1())
//   (new chisel3.stage.ChiselStage).emitVerilog(new DelayBy1_1())
  println(getVerilogString(new DelayBy1_1(Some(5.U))))
}


以上chisel代码用match功能来确定是否给register一个初始化的值。



3.1.5 IOs with Optional Fields

import chisel3._

import chisel3.util._

class HalfFullAdder(val hasCarry: Boolean) extends Module {
  val io = IO(new Bundle {
    val a = Input(UInt(1.W))
    val b = Input(UInt(1.W))
    val carryIn = if (hasCarry) Some(Input(UInt(1.W)))
else None
    val s = Output(UInt(1.W))
    val carryOut = Output(UInt(1.W))
  })
  val sum = io.a +& io.b +& io.carryIn.getOrElse(0.U)
  io.s := sum(0)
  io.carryOut := sum(1)
}

object HalfFullAdder  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new HalfFullAdder(false))
//   (new chisel3.stage.ChiselStage).emitVerilog(new HalfFullAdder())
  println(getVerilogString(new HalfFullAdder(true)))
}

   以上代码使用了Option函数的功能,并联合if else实现了一个可以选择有无carryin的fulladder。 因为是全加器,所以实现加法时要计算进位,所以使用了+&,然后通过使用getOrElse来实现增加carryin,而不影响hascarry为false时的情况。sum(0)表示sum的第0位,sum(1)表示第1位。
import chisel3._

import chisel3.util._

class HalfFullAdder_1(val hasCarry: Boolean) extends Module {
  val io = IO(new Bundle {
    val a = Input(UInt(1.W))
    val b = Input(UInt(1.W))
    val carryIn = Input(if (hasCarry) UInt(1.W) else UInt(0.W))
    val s = Output(UInt(1.W))
    val carryOut = Output(UInt(1.W))
  })
  val sum = io.a +& io.b +& io.carryIn
  io.s := sum(0)
  io.carryOut := sum(1)
}

object HalfFullAdder_1  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new HalfFullAdder_1(false))
//   (new chisel3.stage.ChiselStage).emitVerilog(new HalfFullAdder_1())
  println(getVerilogString(new HalfFullAdder_1(true)))
}


也可像以上代码中的UInt(0.W)来实现判定是否带carryin的功能,0位宽的值会被自动归为0。

3.1.5 Implicits

object CatDog {

  implicit val numberOfCats: Int = 3

  //implicit val numberOfDogs: Int = 5


  def tooManyCats(nDogs: Int)(implicit nCats: Int): Boolean = nCats > nDogs

    

  val imp = tooManyCats(2)    // Argument passed implicitly!

  val exp = tooManyCats(2)(1) // Argument passed explicitly!

}

CatDog.imp   // true

CatDog.exp    // false



如以上代码所示, 红色语句用隐式的方法定义了一个变量,在scala中,在一个给定的scope内,一个type只能有一个隐式变量,所以第二个隐式变量的值自动等于第一个隐式变量的值。

实际调用含有隐式变量的函数时,可以不必给隐式变量赋值,也可再赋值。

object MyImplicits {
sealed trait Verbosity
implicit case object Silent extends Verbosity
case object Verbose extends Verbosity
}



import chisel3._

import chisel3.util._
import  MyImplicits._

class ParameterizedWidthAdder_1(in0Width: Int, in1Width: Int, sumWidth: Int)(implicit verbosity: Verbosity)
extends Module {
  def log(ms: => String): Unit = verbosity match {
    case Silent =>
    case Verbose => println(ms)
  }
  require(in0Width >= 0)
  log(s"in0Width of $in0Width OK")
  require(in1Width >= 0)
  log(s"in1Width of $in1Width OK")
  require(sumWidth >= 0)
  log(s"sumWidth of $sumWidth OK")
  val io = IO(new Bundle {
    val in0 = Input(UInt(in0Width.W))
    val in1 = Input(UInt(in1Width.W))
    val sum = Output(UInt(sumWidth.W))
  })
  log("Made IO")
  io.sum := io.in0 + io.in1
  log("Assigned output")
}


object ParameterizedWidthAdder_1 extends App {
(new chisel3.stage.ChiselStage).emitVerilog(new ParameterizedWidthAdder_1 (1, 4, 5)(Verbose))
 println(getVerilogString(new ParameterizedWidthAdder_1 (1, 4, 5)))
}

以上代码运用implicit功能为代码增加了可选的log功能。log函数会根据自己定义的verbosity型变量的值是silent还是verbose来决定是否输出log。由于silent为隐式变量,并不需要输入,也可输入Silent。

class Animal(val name: String, val species: String)
class Human(val name: String)
implicit def human2animal(h: Human): Animal = new Animal(h.name, "Homo sapiens")
val me = new Human("Adam")
println(me.species)  // Homo spaiens

以上代码为implicit的另一功能展示。可以使用隐式的定义,来拓展显示的定义,而并不影响显示定义。

3.1.6 Generator Examples

// Mealy machine has

import chisel3._

import chisel3.util._

case class BinaryMealyParams(
  // number of states
  nStates: Int,
  // initial state
  s0: Int,
  // function describing state transition
  stateTransition: (Int, Boolean) => Int,
  // function describing output
  output: (Int, Boolean) => Int
){
  require(nStates >= 0)
  require(s0 < nStates && s0 >= 0)
}


class BinaryMealy(val mp: BinaryMealyParams) extends Module {
  val io = IO(new Bundle {
    val in = Input(Bool())
    val out = Output(UInt())
  })

  val state = RegInit(UInt(), mp.s0.U)

  // output zero if no states
  io.out := 0.U
  for (i <- 0 until mp.nStates) {
    when (state === i.U) {
      when (io.in) {
        state  := mp.stateTransition(i, true).U
        io.out := mp.output(i, true).U
      }.otherwise {
        state  := mp.stateTransition(i, false).U
        io.out := mp.output(i, false).U
      }
    }
  }
}
object BinaryMealy  extends App {
val nStates = 3
val s0 = 2
def stateTransition(state: Int, in: Boolean): Int = {
  if (in) {
    1
  } else {
    0
  }
}
def output(state: Int, in: Boolean): Int = {
  if (state == 2) {
    return 0
  }
  if ((state == 1 && !in) || (state == 0 && in)) {
    return 1
  } else {
    return 0
  }
}
val testParams = BinaryMealyParams(nStates, s0, stateTransition, output)

  (new chisel3.stage.ChiselStage).emitVerilog(new BinaryMealy(testParams))
    println(getVerilogString (new BinaryMealy(testParams)))
}

 

用以上代码可以描述如图的mealy状态机:




  代码中的函数等定义基本取决于图片中的输入和当前状态与输出之间的关系。如s0转向s1时,则应输入1(红色)并且output的值也是1(蓝色)。
  代码中出现的s0就是当前状态,当s0等于2时,也就是初始state是2,这个时候代表si,当输入是1时,s1被激活,当输入是0时,则s0被激活。
  tester如下所示,但是使用tester时要先为testparams赋值。

test(new BinaryMealy(testParams)) { c =>
  c.io.in.poke(false.B)
  c.io.out.expect(0.U)
  c.clock.step(1)
  c.io.in.poke(false.B)
  c.io.out.expect(0.U)
  c.clock.step(1)
  c.io.in.poke(false.B)
  c.io.out.expect(0.U)
  c.clock.step(1)
  c.io.in.poke(true.B)
  c.io.out.expect(1.U)
  c.clock.step(1)
  c.io.in.poke(true.B)
  c.io.out.expect(0.U)
  c.clock.step(1)
  c.io.in.poke(false.B)
  c.io.out.expect(1.U)
  c.clock.step(1)
  c.io.in.poke(true.B)
  c.io.out.expect(1.U)
  c.clock.step(1)
  c.io.in.poke(false.B)
  c.io.out.expect(1.U)
  c.clock.step(1)
  c.io.in.poke(true.B)
  c.io.out.expect(1.U)
}

3.2 Generators: Collections

Generator经常需要处理可变数量的对象,无论它们是 IO、模块还是test vector。Collection是处理此类情况的重要组成部分。本模块将介绍 Scala Collection以及如何将它们与 Chisel Generator一起使用。

import scala.collection._


本节需要新增import以上内容。

3.2.1 Generators and Collections

以下代码展示了如图所示的Fir filter:

import chisel3._
import chisel3.util._
import scala.collection._

class My4ElementFir(b0: Int, b1: Int, b2: Int, b3: Int) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  val x_n1 = RegNext(io.in, 0.U)
  val x_n2 = RegNext(x_n1, 0.U)
  val x_n3 = RegNext(x_n2, 0.U)
  io.out := io.in * b0.U(8.W) + x_n1 * b1.U(8.W) +
    x_n2 * b2.U(8.W) + x_n3 * b3.U(8.W)
}

object My4ElementFir  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new My4ElementFir(1,2,3,4))
//   (new chisel3.stage.ChiselStage).emitVerilog(new My4ElementFir())
  println(getVerilogString(new My4ElementFir(1,2,3,4)))
}

    该电路是一个简单的Generator,因为它可以生成具有不同系数的滤波器。但是如果我们希望电路有更多层滤波呢?我们将分几个步骤执行此操作。

  • 构建可配置 FIR的model
  • 重构我们的 My4ElementFir 以允许可配置的滤波层数。
   以上代码中,RegNext代表下一时刻的Reg的值,括号内部右边的数代表着复位后的值,也就是,说如果reset信号为1,那么就是Reg在下一时刻被初始化为0,否则为括号左边的值。‘=’ 在chisel中代表软件上的赋值,非硬件连接,也就是x_n1代表着一个register,而不是x_n1和register相连。':='在chisel中才代表硬件连接,所以io.out是由各个寄存器的组合输出。

   import chisel3._
import chisel3.util._
// import chisel3.tester._
// import chisel3.tester.RawTester.test
import scala.collection._

class MyManyElementFir(consts: Seq[Int], bitWidth: Int) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(bitWidth.W))
    val out = Output(UInt(bitWidth.W))
  })

  val regs = mutable.ArrayBuffer[UInt]()
  for(i <- 0 until consts.length) {
      if(i == 0) regs += io.in
      else       regs += RegNext(regs(i - 1), 0.U)
  }
 
  val muls = mutable.ArrayBuffer[UInt]()
  for(i <- 0 until consts.length) {
      muls += regs(i) * consts(i).U
  }

  val scan = mutable.ArrayBuffer[UInt]()
  for(i <- 0 until consts.length) {
      if(i == 0) scan += muls(i)
      else scan += muls(i) + scan(i - 1)
  }

  io.out := scan.last
}

object MyManyElementFir  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new MyManyElementFir(Seq(1,2,3,4),4))
//   (new chisel3.stage.ChiselStage).emitVerilog(new MyManyElementFir())
  println(getVerilogString(new MyManyElementFir(Seq(1,2,3,4),4)))
}

  上方代码重构了一个新的Fir滤波器,通过使用mutable(可变的).ArrayBuffer来初始化空数组,第一次调用时与RegNext联合使用构建了来实现寄存器的连接结构,第二次调用时则是让每个对应的muls数组的单元存储被加权后的值。最后一次调用则是用它来计算最终要输出的值。

3.2.2 Hardware Collections

import chisel3._
import chisel3.stage.ChiselStage
import chisel3.testers.{BasicTester, TesterDriver}
import chisel3.util._

import scala.annotation.tailrec

class MyManyDynamicElementVecFir(length: Int) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out = Output(UInt(8.W))
    val consts = Input(Vec(length, UInt(8.W)))
  })

  // Reference solution
 val regs = RegInit(VecInit(Seq.fill(length - 1){0.U(8.W)}))
  for(i <- 0 until length - 1) {
      if(i == 0) regs(i) := io.in
      else       regs(i) := regs(i - 1)
  }
 
  val muls = Wire(Vec(length, UInt(8.W)))
  for(i <- 0 until length) {
      if(i == 0) muls(i) := io.in * io.consts(i)
      else       muls(i) := regs(i - 1) * io.consts(i)
  }

  val scan = Wire(Vec(length, UInt(8.W)))
  for(i <- 0 until length) {
      if(i == 0) scan(i) := muls(i)
      else scan(i) := muls(i) + scan(i - 1)
  }

  io.out := scan(length - 1)
}

object MyManyDynamicElementVecFir  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new MyManyDynamicElementVecFir(6))
//   (new chisel3.stage.ChiselStage).emitVerilog(new MyManyDynamicElementVecFir())
  println(getVerilogString(new MyManyDynamicElementVecFir(6)))
}

以上代码使用了一个新结构,Vec。Vec是Chiel独有的集合形式。Vec只能被用在scala自带的集合不能用多个时候比如:

1. IO Bundle中
2. 硬件的集合,比如RegFILE。

以上代码便通过Vec定义出了一个RegArrary。

3.2.3 RegFile

import chisel3._
import chisel3.stage.ChiselStage
import chisel3.testers.{BasicTester, TesterDriver}
import chisel3.util._

import scala.annotation.tailrec  
   
   
   class RegisterFile(readPorts: Int) extends Module {
    require(readPorts >= 0)
    val io = IO(new Bundle {
        val wen   = Input(Bool())
        val waddr = Input(UInt(5.W))
        val wdata = Input(UInt(32.W))
        val raddr = Input(Vec(readPorts, UInt(5.W)))
        val rdata = Output(Vec(readPorts, UInt(32.W)))
    })
   
    // A Register of a vector of UInts
    val reg = RegInit(VecInit(Seq.fill(32)(0.U(32.W)))) //定义Reg初始化为0
   
   when (io.wen) {
        reg(io.waddr) := io.wdata //io.wen==true, 写入数据
    }
    for (i <- 0 until readPorts) {
        when (io.raddr(i) === 0.U) {
            io.rdata(i) := 0.U // 地址为0的寄存器永远输出为0
        } .otherwise {
            io.rdata(i) := reg(io.raddr(i)) // 由所输入的信息决定端口,端口内输入的信息
// 为寄存器的地址,一个端口只能一次输出一个
// 数据。每个read端口都要能向外部传递信息。
        }
    }

   
}
   
   
object RegisterFile  extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new RegisterFile(6))
//   (new chisel3.stage.ChiselStage).emitVerilog(new RegisterFile())
  println(getVerilogString(new RegisterFile(6)))
}
以上代码定义了一个多端口(6)的RegFile。通过Vec来定义地址的端口,比如raddr(0)==31, 就意味着在0号端口读取地址为31的寄存器。代码的具体解释可见上方注释。
val raddr = Input(Vec(readPorts, UInt(5.W)))

这行代码是定义了一个数组,数组的内容是各个端口中收到的数据,数据是要读的寄存器的地址。
val rdata = Output(Vec(readPorts, UInt(32.W)))
这行代码是定义了上述寄存器中的值并输出的一个结构。
io.rdata(i) := reg(io.raddr(i))

io.raddr(i)中的i是第i个端口的意思,整体的意思是输入进来的寄存器的地址。
reg(io.raddr(i))就是读取对应的值,然后将值输出给io.rdata(i)。

Comments

Popular posts from this blog

托福 TPO词汇题汇总

浮点数

缓存