0%

3.2 Generator-Parameter

3.2 Generators: Parameters

Parameter Passing

  • 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
class ParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int) extends Module {
require(in0Width >= 0) // Pre-elaboration assertions
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
}

println(getVerilog(new ParameterizedWidthAdder(1, 4, 6)))

Parameterized 4-Input Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Sort4(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
}
}

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.

Option.getOrElse provides a default value.

1
2
3
4
Some(1).get        		// 1
// None.get // Errors!
Some(1).getOrElse(2) // 1
None.getOrElse(2) // 2

Sometimes, a parameter doesn’t have a good default value. Option can be used with a default value of None in these situations.

Optional Reset by Input: Option

If resetValue = None, which is the default, the register will have no reset value and be initialized to garbage.

1
2
3
4
5
6
7
8
9
class DelayBy1(resetValue: Option[UInt] = None) extends Module {
...
val reg = if (resetValue.isDefined) { // resetValue = Some(number)
RegInit(resetValue.get)
} else { //resetValue = None
Reg(UInt())
}
...
}

Match - Case

  • 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")
}
}

Optional Reset by Match

1
2
3
4
5
6
7
8
class DelayBy1(resetValue: Option[UInt] = None) extends Module {
...
val reg = resetValue match {
case Some(r) => RegInit(r)
case None => Reg(UInt())
}
...
}

IO with Optional Fields

Optional IO with Option

1
2
3
4
5
6
7
8
9
class HalfFullAdder(val hasCarry: Boolean) extends Module {
val io = IO(new Bundle {
...
val carryIn = if (hasCarry) Some(Input(UInt(1.W))) else None
...
})
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
class HalfFullAdder(val hasCarry: Boolean) extends Module {
val io = IO(new Bundle {
...
val carryIn = Input(if (hasCarry) UInt(1.W) else UInt(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
object CatDog {
implicit val numberOfCats: Int = 3
// implicit val numberOfDogs: String = "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
}

Implicit Log

Note: there are better ways to do logging in Scala!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sealed trait Verbosity
implicit case object Silent extends Verbosity
case object Verbose extends Verbosity

class ParameterizedWidthAdder(in0Width: Int, in1Width: Int, sumWidth: Int)(implicit verbosity: Verbosity)
extends Module {
def log(msg: => String): Unit = verbosity match {
case Silent =>
case Verbose => println(msg)
}
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")
}

println(getVerilog(new ParameterizedWidthAdder(1, 4, 5)))
println(getVerilog(new ParameterizedWidthAdder(1, 4, 5)(Verbose)))

Implicit Conversions

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
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)

Mealy Machine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Mealy machine has
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
}
}
}
}

// example from https://en.wikipedia.org/wiki/Mealy_machine
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, (state: Int, in: Boolean) => if (in) 1 else 0, output) // anonymous function
println(getVerilog(new BinaryMealy(testParams)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module BinaryMealy(
input clock,
input reset,
input io_in,
output io_out
);
`ifdef RANDOMIZE_REG_INIT
reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
reg [1:0] state; // @[cmd41.sc 21:22]
wire _GEN_2 = state == 2'h0 & io_in; // @[cmd41.sc 26:26 cmd41.sc 24:10]
wire _GEN_3 = io_in ? 1'h0 : 1'h1; // @[cmd41.sc 27:20 cmd41.sc 29:16 cmd41.sc 32:16]
wire _GEN_5 = state == 2'h1 ? _GEN_3 : _GEN_2; // @[cmd41.sc 26:26]
assign io_out = state == 2'h2 ? 1'h0 : _GEN_5; // @[cmd41.sc 26:26]
always @(posedge clock) begin
if (reset) begin // @[cmd41.sc 21:22]
state <= 2'h2; // @[cmd41.sc 21:22]
end else if (state == 2'h2) begin // @[cmd41.sc 26:26]
state <= {{1'd0}, io_in};
end else if (state == 2'h1) begin // @[cmd41.sc 26:26]
state <= {{1'd0}, io_in};
end else if (state == 2'h0) begin // @[cmd41.sc 26:26]
state <= {{1'd0}, io_in};
end
end
endmodule