package Ex0

import chisel3._
import chisel3.iotesters.PeekPokeTester
import org.scalatest.{Matchers, FlatSpec}
import TestUtils._

class DotProdSpec extends FlatSpec with Matchers {
  import DotProdTests._

  val elements = scala.util.Random.nextInt(5) + 2

  behavior of "DotProd"

  it should "Only signal valid output at end of calculation" in {
    wrapTester(
      chisel3.iotesters.Driver(() => new DotProd(elements)) { c =>
        new SignalsWhenDone(c)
      } should be(true)
    )
  }


  it should "Calculate the correct output" in {
    wrapTester(
      chisel3.iotesters.Driver(() => new DotProd(elements)) { c =>
        new CalculatesCorrectResult(c)
      } should be(true)
    )
  }


  it should "Calculate the correct output and signal when appropriate" in {
    wrapTester(
      chisel3.iotesters.Driver(() => new DotProd(elements)) { c =>
        new CalculatesCorrectResultAndSignals(c)
      } should be(true)
    )
  }
}

object DotProdTests {

  class SignalsWhenDone(c: DotProd) extends PeekPokeTester(c) {

    for(ii <- 0 until c.elements - 1){
      expect(c.io.outputValid, false)
      step(1)
    }
    expect(c.io.outputValid, true)
    step(1)

    for(ii <- 0 until c.elements - 1){
      expect(c.io.outputValid, false)
      step(1)
    }
    expect(c.io.outputValid, true)
    step(1)
  }


  class CalculatesCorrectResult(c: DotProd) extends PeekPokeTester(c) {

    val inputsA = List.fill(c.elements)(scala.util.Random.nextInt(10))
    val inputsB = List.fill(c.elements)(scala.util.Random.nextInt(10))
    val expectedOutput = (for ((a, b) <- inputsA zip inputsB) yield a * b) sum

    for(ii <- 0 until c.elements){
      poke(c.io.dataInA, inputsA(ii))
      poke(c.io.dataInB, inputsB(ii))
      if(ii == c.elements - 1)
        expect(c.io.dataOut, expectedOutput)
      step(1)
    }
  }


  class CalculatesCorrectResultAndSignals(c: DotProd) extends PeekPokeTester(c) {

    val inputsA = List.fill(c.elements)(scala.util.Random.nextInt(10))
    val inputsB = List.fill(c.elements)(scala.util.Random.nextInt(10))
    val expectedOutput = (for ((a, b) <- inputsA zip inputsB) yield a * b) sum

    for(ii <- 0 until c.elements){
      poke(c.io.dataInA, inputsA(ii))
      poke(c.io.dataInB, inputsB(ii))
      if(ii == c.elements - 1){
        expect(c.io.dataOut, expectedOutput)
        expect(c.io.outputValid, true)
      }
      else
        expect(c.io.outputValid, false)
      step(1)
    }


    for(ii <- 0 until c.elements){
      poke(c.io.dataInA, inputsA(ii))
      poke(c.io.dataInB, inputsB(ii))
      if(ii == c.elements - 1){
        expect(c.io.dataOut, expectedOutput)
        expect(c.io.outputValid, true)
      }
      else
        expect(c.io.outputValid, false)
      step(1)
    }
  }
}
