Functions in Scala are first-class objects. That means we can assign a function to a val and pass it to classes, objects, or other functions as an argument.
1 2 3 4 5 6
// These are normal functions. defplus1funct(x: Int): Int = x + 1 // These are functions as vals. // The first one explicitly specifies the return type. val plus1val: Int => Int = x => x + 1// val plus1val: (Int => Int) = { x => x + 1 } val times2val = (x: Int) => x * 2
val vs. def
(?) Why would you want to create a val instead of a def? With a val, you can now pass the function around to other functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// create our function defplus2(x: Int): Int = x + 1 val plus1 = (x: Int) => x + 1 val times2 = (x: Int) => x * 2
// pass it to map, a list function val myList = List(1, 2, 5, 9) val myListPlus = myList.map(plus1) val myListTimes = myList.map(times2)
// create a custom function, which performs an operation on X N times using recursion defopN(x: Int, n: Int, op: Int => Int): Int = { if (n <= 0) { x } else { opN(op(x), n-1, op) } }
// our FIR has parameterized window length, IO bitwidth, and windowing function classMyFir(length: Int, bitwidth: Int, window: (Int, Int) =>Seq[Int]) extendsModule{ val io = IO(newBundle { val in = Input(UInt(bitwidth.W)) val out = Output(UInt((bitwidth*2+length-1).W)) // expect bit growth, conservative but lazy })
// calculate the coefficients using the provided window function, mapping to UInts val coeffs = window(length, bitwidth).map(_.U)
// create an array holding the output of the delays // note: we avoid using a Vec here since we don't need dynamic indexing val delays = Seq.fill(length)(Wire(UInt(bitwidth.W))).scan(io.in)( (prev: UInt, next: UInt) => { next := RegNext(prev) next })
// multiply, putting result in "mults" val mults = delays.zip(coeffs).map{ case(delay: UInt, coeff: UInt) => delay * coeff }
// add up multiplier outputs with bit growth val result = mults.reduce(_+&_)
Abstract Classes define unimplemented values that subclasses must implement.
Abstract class can’t be instantiated.
A class can only directly inherit from one parent abstract class.
1
classMyClassextendsHasTrait1
1 2 3 4 5 6 7 8 9 10
abstractclassMyAbstractClass{ defmyFunction(i: Int): Int val myValue: String } classConcreteClassextendsMyAbstractClass{ defmyFunction(i: Int): Int = i + 1 val myValue = "Hello World!" } val abstractClass = newMyAbstractClass() // Illegal! Cannot instantiate an abstract class val concreteClass = newConcreteClass() // Legal!
traitHasFunction{ defmyFunction(i: Int): Int } traitHasValue{ val myValue: String val myOtherValue = 100 } classMyClassextendsHasFunctionwithHasValue{ overridedefmyFunction(i: Int): Int = i + 1 val myValue = "Hello World!" } val myTraitFunction = newHasFunction() // Illegal! Cannot instantiate a trait val myClass = newMyClass() // Legal!
Object
You can simply directly reference it
Object can’t be instantiated**. **(no need to call new)
Companion Object
Companion object and class share the same name and defined in the same file.
When you use new before the class/object name, it will instantiate the class.
If you don’t use new, it will reference the object.
1 2 3 4 5 6 7 8
objectLion{ defroar(): Unit = println("I'M AN OBJECT!") } classLion{ defroar(): Unit = println("I'M A CLASS!") } newLion().roar() // instantiate class Lion.roar() // companion ojbect
Companion objects are usually used for the following reasons:
to contain constants related to the class
to execute code before/after the class constructor
to create multiple constructors for a class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
objectAnimal{ val defaultName = "Bigfoot" privatevar numberOfAnimals = 0 defapply(name: String): Animal = { numberOfAnimals += 1 newAnimal(name, numberOfAnimals) } defapply(): Animal = apply(defaultName) } classAnimal(name: String, order: Int) { definfo: String = s"Hi my name is $name, and I'm $order in line!" }
val bunny = Animal.apply("Hopper") val yeti = Animal()
Chisel uses many companion objects, like Module. When you write the following you are calling the Module companion object.
1
val myModule = Module(newMyModule)
Case Class
Allows external access to the class parameters.
Eliminates the need to use new when instantiating the class.
This is because the Scala compiler automatically creates a companion object for every case class in your code, which contains an apply method for the case class.
Automatically creates an unapply method that supplies access to all of the class Parameters.
Cannot be subclassed from.
1 2 3 4 5 6 7 8 9 10 11
classNail(length: Int) // Regular class val nail = newNail(10) // Requires the `new` keyword // println(nail.length) // Illegal! Class constructor parameters are not by default externally visible
classScrew(val threadSpace: Int) // By using the `val` keyword, threadSpace is now externally visible val screw = newScrew(2) // Requires the `new` keyword println(screw.threadSpace)
caseclassStaple(isClosed: Boolean) // Case class constructor parameters are, by default, externally visible val staple = Staple(false) // No `new` keyword required println(staple.isClosed)
Inheritance with Chisel
Every Chisel module you make is a class extending the base type Module.
Every Chisel IO you make is a class extending the base type Bundle (or, in some special cases, Bundle‘s supertype Record).
Chisel hardware types like UInt or Bundle all have Data as a supertype.
val a = Wire(UInt(4.W)) a := 0.U// legal: chisel UInt a := 0// illegal: scala Int
Boolean vs. Bool
1 2 3 4 5 6 7 8
val bool = Wire(Bool()) val boolean: Boolean = false // legal when (bool) { ... } if (boolean) { ... } // illegal if (bool) { ... } when (boolean) { ... }
Type Coercion
Scala
x.asInstanceOf[T] casts the object x to the type T. It throws an exception if the given object cannot be cast to type T.
1 2 3 4 5 6 7 8 9
val x: UInt = 3.U // We can't cast UInt to Int try { println(x.asInstanceOf[Int]) } catch { case e: java.lang.ClassCastException => println("As expected, we can't cast UInt to Int") } // But we can cast UInt to Data since UInt inherits from Data. println(x.asInstanceOf[Data])
Chisel
Chisel has a set of type casting functions. The most general is asTypeOf(). Some chisel objects also define asUInt() and asSInt() as well as some others.
1 2 3 4 5 6 7
classTypeConvertDemoextendsModule{ val io = IO(newBundle { val in = Input(UInt(4.W)) val out = Output(SInt(4.W)) }) io.out := io.in.asTypeOf(io.out) }
Match
Type Match
1 2 3 4 5 6 7 8 9 10
classConstantSum(in1: Data, in2: Data) extendsModule{ val io = IO(newBundle { val out = Output(chiselTypeOf(in1)) // in case in1 is literal then just get its type }) (in1, in2) match { case (x: UInt, y: UInt) => io.out := x + y case (x: SInt, y: SInt) => io.out := x + y case _ => thrownewException("I give up!") } }
Value Match
It is good to remember that Chisel types generally should not be value matched. Scala’s match executes during circuit elaboration, but what you probably want is a post-elaboration comparison. The following gives a syntax error:
1 2 3 4 5 6 7 8 9 10 11
classInputIsZeroextendsModule{ val io = IO(newBundle { val in = Input(UInt(16.W)) val out = Output(Bool()) }) io.out := (io.in match { // note that case 0.U is an error case (0.U) => true.B case _ => false.B }) }
Unapply
value matching with case classes
1 2 3 4 5 6
caseclassSomething(a: String, b: Int) val a = Something("A", 3) a match { caseSomething("A", value) => value caseSomething(str, 3) => 0 }
Scala unapply methods are another form of syntactic sugar that give match statements the ability to both match on types and extract values from those types during the matching
defdelay(p: SomeGeneratorParameters): Int = p match { case sg @ SomeGeneratorParameters(_, _, true) => sg.totalWidth * 3 caseSomeGeneratorParameters(_, sw, false) => sw * 2 case sg @ SomeGeneratorParameters(_, _, true) if sg.pipelineMe => sg.totalWidth * 3 }
All these syntaxes are enabled by a Scala unapply method contained in a class’s companion object. If you want to unapply a class but do not want to make it a case class, you can manually implement the unapply method. The following example demonstrates how one can manually implement a class’s apply and unapply methods:
defgetSmallBoats(seq: Seq[Boat]): Seq[Boat] = seq.filter { b => b match { caseBoat(_, length) if length < 60 => true caseBoat(_, _) => false } }
val boats = Seq(Boat("Santa Maria", 62), Boat("Pinta", 56), Boat("Nina", 50)) println(getSmallBoats(boats).map(_.name).mkString(" and ") + " are small boats!")