require(...)are pre-elaboration assertions, which are useful when your generator only works with certain parameterizations or when some parameterizations are mutually exclusive or nonsensical.
There is a separate construct for simulation-time assertions called assert(...).
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int) extendsModule{ require(in0Width >= 0) // Pre-elaboration assertions require(in1Width >= 0) require(sumWidth >= 0) val io = IO(newBundle { 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 }
classSort4(ascending: Boolean) extendsModule{ val io = IO(newBundle { 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 defcomp(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 } }
Option
1 2 3
val map = Map("a" -> 1) val a = map("a") // 1 val b = map("b") // Errors!
Map.get returns a key’s value of abstract class Option. Option has two subclasses, **Some **and None.
1 2
val a = map.get("a") // Some(1) val b = map.get("b") // None
Option.get returns a value, which errors if called on None.
Each code block that follows a => operator continues until it reaches either the ending brace of the match or the next case statement.
A match is searched in the order of the case statements, once a case statement has been matched, no other checks against other case statements are made.
case _wildcard handle any value not found. (default)
Multiple Value Matching
1 2 3 4 5 6 7 8
(val1, val2) match { case (true, true) => "wolverine" case (true, false) => "elephant" case (false, true) => println("here") "shrew" case _ => "puppy" }
Type Matching
1 2 3 4 5 6 7 8 9
val sequence = Seq("abc", 1, 0.0, 'a', 'b') sequence.foreach { x => x match { case s: String => println(s"$x is a String") case _: Int | _: Double => println(s"$x is an Int or Double") case 'a' | _: Char => println(s"$x is a Char") case _ => println(s"$x is an unknown type!") } }
Type Matching and Erasure
Type matching has some limitations. Because Scala runs on the JVM, and the JVM does not maintain polymorphic types, you cannot match on them at runtime (because they are all erased). Note that the following example always matches the first case statement, because the [String], [Int], and [Double] polymorphic types are erased, and the case statements are actually matching on just a Seq.
1 2 3 4 5 6 7 8
val sequence = Seq(Seq("a"), Seq(1), Seq(0.0)) sequence.foreach { x => x match { case s: Seq[String] => println(s"$x is a String") case s: Seq[Int] => println(s"$x is an Int") case s: Seq[Double] => println(s"$x is a Double") } }
classHalfFullAdder(val hasCarry: Boolean) extendsModule{ val io = IO(newBundle { ... val carryIn = if (hasCarry) Some(Input(UInt(1.W))) elseNone ... }) val sum = io.a +& io.b +& io.carryIn.getOrElse(0.U) ... }
Optional IO with Zero-Width Wires
Chisel types are allowed to have widths of zero. An IO with width zero is pruned from the emitted Verilog. Anything that tries to use the value of a zero-width wire gets a constant zero.
1 2 3 4 5 6 7 8 9
classHalfFullAdder(val hasCarry: Boolean) extendsModule{ val io = IO(newBundle { ... val carryIn = Input(if (hasCarry) UInt(1.W) elseUInt(0.W)) ... }) val sum = io.a +& io.b +& io.carryIn ... }
Implicit Argument
There can only be one implicit value of a given type.
When we call tooManyCats, we either omit the second implicit argument list (letting the compiler find it for us), or explicitly provide an argument (which can be different than the implicit value).
1 2 3 4 5 6 7 8 9
objectCatDog{ implicitval numberOfCats: Int = 3 // implicit val numberOfDogs: String = "5"
Implicit Conversions are used to automatically convert one Scala object into another.
Generally, implicits can make your code confusing, so we recommend you use them as a last resort. First try inheritance, traits, or method overloading.
1 2 3 4 5
classAnimal(val name: String, val species: String) classHuman(val name: String) implicitdefhuman2animal(h: Human): Animal = newAnimal(h.name, "Homo sapiens") val me = newHuman("Adam") println(me.species)
// Mealy machine has caseclassBinaryMealyParams( // 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) }
classBinaryMealy(val mp: BinaryMealyParams) extendsModule{ val io = IO(newBundle { 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 } } } }
// example from https://en.wikipedia.org/wiki/Mealy_machine val nStates = 3 val s0 = 2 defstateTransition(state: Int, in: Boolean): Int = { if (in) { 1 } else { 0 } } defoutput(state: Int, in: Boolean): Int = { if (state == 2) { return0 } if ((state == 1 && !in) || (state == 0 && in)) { return1 } else { return0 } }
val testParams = BinaryMealyParams(nStates, s0, (state: Int, in: Boolean) => if (in) 1else0, output) // anonymous function println(getVerilog(newBinaryMealy(testParams)))