// ---
// jupyter:
//   jupytext:
//     text_representation:
//       extension: .scala
//       format_name: light
//       format_version: '1.5'
//       jupytext_version: 1.14.4
//   kernelspec:
//     display_name: Scala
//     language: scala
//     name: scala
// ---

// # Test Chisel notebook
// > An example that defines a module over multiple notebook cells.

// We must first import our Chisel dependencies:
// ```Scala
// import $file.^.source.load_ivy
// ```

//export
//default_exp test
//package Examples
import $file.^.source.load_ivy

//export
import chisel3.{Input => Input}
import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
import chisel3.tester.RawTester.{test => Test}

// ### define the IO
// > If we define our IO as a standalone bundle we are able to reuse it for other modules

//export
class ALUIO(width: Int) extends Bundle {
    val a = Input(SInt(width.W))
    val b = Input(SInt(width.W))
    val out = Output(SInt())

    override def cloneType = new ALUIO(width).asInstanceOf[this.type]
}

// ### A skeleton module will be parent class of all of our methods
// > This module currently has only IO defined. We will use Scala implicits to inject functionality into it.

//export 
class ALUSkeleton(width: Int) extends Module {
    val io = IO(new ALUIO(width))
}

// ### The add method from Add class will be injected into the ALUSkeleton class
// > This syntax will be used to inject the ```add``` method into our skeleton module:
// ```Scala
// class Add(m: ALUSkeleton) {
//     def add(a: SInt, b: SInt): SInt = a +& b
// }
// ```

//export 
class Add(m: ALUSkeleton) {
    def add(a: SInt, b: SInt): SInt = a +& b
}

// Similarly we can define a `sub` method that will be added

//export 
class Sub(m: ALUSkeleton) {
    def sub(a: SInt, b: SInt): SInt = a -& b
}
println("changed auto-gen file")

// ### Here multiple methods will be injected
// > Both `mul` and `div` will be injected.

//export 
class MulDiv(m: ALUSkeleton) {
    println("changed auto-gen file")
    def mul(a: SInt, b: SInt): SInt = a * b
    def div(a: SInt, b: SInt): SInt = a / b
}

// ### Adding the methods to our skeleton module:
// > implicit def call will add the methods of the RHS classes to the implicit def parameter: ALUSkeleton class.
// Note the name ```includeAdd``` does not matter as it won't be called again, but is good practice to give it a clear name of what it is doing (i.e includAdd <=> include the Add class' methods)

//export 
implicit def includeAdd(m: ALUSkeleton) = new Add(m)
implicit def includeSub(m: ALUSkeleton) = new Sub(m)
implicit def includeMulDiv(m: ALUSkeleton) = new MulDiv(m)

// ### Use our implicitly defined functions
// We extend our ALUSkeleton giving us access to all of our implicitly defined methods. We can match based on parameterized operator (at compile time) and then only instantiate hardware for that given operation. 

//export
/** This Operator module performs 1 type of operation depending on 'op' parameter */
class Operator(op: String, width: Int) extends ALUSkeleton(width) {
    op match {
        // Call on the implicit function
        case "+" => io.out := this.add(io.a, io.b)
        case "-" => io.out := this.sub(io.a, io.b)
        case "*" => io.out := this.mul(io.a, io.b)
        case "/" => io.out := this.div(io.a, io.b)
        case _ => io.out := 0.S
    }
}

// ### Summary
// > While this includes a few more steps than simply implementing everything directly into an Operator module, it can actually save some headaches when building much larger moudules. The best case for developing this way is if you're defining a specific arithmetic implmentation, which this example was meant to outline. For example a multiplier may require many sub methods to implement, and these implementation details can be hidden from the module using it. Also working like this puts you in the mindset of pulling out independent pieces of logic to write functions for that can later be reused in other modules. 

// +
//hide
val width = 10
Test(new Operator(op="+", width)) { c =>
    c.io.a.poke(5.S); c.io.b.poke(3.S)
    c.io.out.expect(8.S)
}


Test(new Operator(op="-", width)) { c =>
    c.io.a.poke(5.S); c.io.b.poke(3.S)
    c.io.out.expect(2.S)
}


Test(new Operator(op="*", width)) { c =>
    c.io.a.poke(5.S); c.io.b.poke(3.S)
    c.io.out.expect(15.S)
}


Test(new Operator(op="/", width)) { c =>
    c.io.a.poke(5.S); c.io.b.poke(3.S)
    c.io.out.expect(1.S)
}


Test(new Operator(op="%", width)) { c =>
    c.io.a.poke(5.S); c.io.b.poke(3.S)
    c.io.out.expect(0.S)
}
// -

//export
class OperatorTester(c: Operator, op: String, width: Int) extends PeekPokeTester(c) {
  // 100 random tests
  val cycles = 10
  import scala.util.Random
  import scala.math.min
  for (i <- 0 until cycles) {
    val in_a = Random.nextInt(1 << (width - 1))
    val in_b = Random.nextInt(1 << (width - 1))
    poke(c.io.a, in_a)
    poke(c.io.b, in_b)
    val exp = op match {
        case "+" => in_a + in_b
        case "-" => in_a - in_b
        case "*" => in_a * in_b
        case "/" => in_a / in_b
    }
//     println(s"$in_a $op $in_b = $exp")
    expect(c.io.out, exp)
    }
  }

//hide
for (op <- Seq("+", "-", "*", "/")) {
    assert(Driver(() => new Operator(op, width)) {c => new OperatorTester(c, op, width)})
}
println("SUCCESS!!")

// ### Some test funcs/classes/objects to be exported from this notebook:

//export NewScript
def FuncToNewScript(): Unit = {print("hello world")}

//export NewScript2
def FuncToNewScript2(): Unit = {print("hello squirrel")}

//export
object TestObj {}

//export
abstract class LotsOfParams(a: Int, b: List[(Int, Int)], c: BigInt)

//export
def testTypeParams[T](a: T): List[T] = {
    List.fill(5)(a)
}

//hide
assert(testTypeParams(5) == List(5, 5, 5, 5, 5))

class LotsOfApply(a: Int) {}
object LotsOfApply {
    def apply: LotsOfApply = new LotsOfApply(1)
    def apply(a: Int): LotsOfApply = new LotsOfApply(a)
}

StarcoderdataJupyterScriptsDedupFiltered