  def OR  = 7.U    // Or
  def XOR = 8.U    // Xor
  def DIV = 9.U    // Div (NOT IMPLEMENTED)
  def MIN = 10.U   // Minimum (NOT IMPLEMENTED)
  def MAX = 11.U   // Maximum (NOT IMPLEMENTED)
}

import operations._

class Alu
  (
    dataWidth: Int,
    opWidth: Int
  )
  extends Module {
  val io = IO(new Bundle {
    val din1 = Input(SInt(dataWidth.W))
    val din2 = Input(SInt(dataWidth.W))
    val dout = Output(SInt(dataWidth.W))
    val opConfig = Input(UInt(opWidth.W))
  })
  // Result
  val outAux = Wire(SInt((dataWidth).W))

  when (io.opConfig === SUM) {                               // SUM
    outAux := io.din1 + io.din2
  }
  .elsewhen (io.opConfig === MUL) {                          // MUL
    outAux := io.din1 * io.din2
  }
  .elsewhen (io.opConfig === SUB) {                          // SUB
    outAux := io.din1 - io.din2
  }
  .elsewhen (io.opConfig === SLL) {                          // SLL
    outAux := io.din1 << (io.din2(18, 0)).asUInt
  }
  .elsewhen (io.opConfig === SRA) {                          // SRA
    outAux := io.din1 >> io.din2.asUInt
  }
  .elsewhen (io.opConfig === SRL) {                          // SRL
    outAux := (io.din1.asUInt >> io.din2.asUInt).asSInt
  }
  .elsewhen (io.opConfig === AND) {                          // AND
    outAux := io.din1 & io.din2
  }
  .elsewhen (io.opConfig === OR) {                           // OR
    outAux := io.din1 | io.din2
  }
  .elsewhen (io.opConfig === XOR) {                          // XOR
    outAux := io.din1 ^ io.din2
  }
  .otherwise {
    outAux := 0.S                                            // Default
  }

  io.dout := outAux
}

// Generate the Verilog code
object AluMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new Alu(32, 5), Array("--target-dir", "generated"))
}


  eb2.io.doutReady := joinDin2Ready

  val joinInst = Module (new Join(dataWidth))
  joinInst.io.din1 := joinDin1
  joinInst.io.din2 := joindin2
  joinInst.io.doutReady := joinDoutReady
  joinInst.io.din1Valid := joinDin1Valid
  joinInst.io.din2Valid := joinDin2Valid

  joinDoutValid := joinInst.io.doutValid
  joinDin1Ready := joinInst.io.din1Ready
  joinDin2Ready := joinInst.io.din2Ready
  joinDout1 := joinInst.io.dout1
  joinDout2 := joinInst.io.dout2

  val fuInst = Module (new Fu(dataWidth, 5))
  fuInst.io.din1 := joinDout1
  fuInst.io.din2 := joinDout2
  fuInst.io.dinValid := joinDoutValid
  joinDoutReady := fuInst.io.dinReady
  fuInst.io.loopSource := loadInitialValue
  fuInst.io.iterationsReset := iterationsResetLoad
  fuInst.io.opConfig := opConfig
  fuDout := fuInst.io.dout
  fuDoutValid := fuInst.io.doutValid

  fuInst.io.doutReady := fuDoutReady

  val ebOut = Module (new DEb(dataWidth))
  ebOut.io.din := fuDout
  ebOut.io.dinValid := fuDoutValid
  ebOut.io.din := fuInst.io.dout
  ebOut.io.dinValid := fuInst.io.doutValid

  fuDoutReady := ebOut.io.dinReady
  io.dout := ebOut.io.dout
  io.doutValid := ebOut.io.doutValid
  ebOut.io.doutReady := forkedDoutReady

  val fs = Module (new Fs(5))
  val readyOutFs = Cat("b1".U, io.northDoutReady, io.eastDoutReady, io.southDoutReady, io.westDoutReady)
  fs.io.readyOut := readyOutFs
  forkedDoutReady := fs.io.readyIn
  fs.io.forkMask := forkSenderMask
}

// Generate the Verilog code
object CellProcessingMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new CellProcessing(32), Array("--target-dir", "generated"))
}


object ConfMuxMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new ConfMux(2, 1), Array("--target-dir", "generated"))
}


    io.doutValid := regDinValid1
  }.otherwise {
    io.dout := regDin2
    io.doutValid := regDinValid2
  }
}

// Generate the Verilog code
object DEbMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new DEb(32), Array("--target-dir", "generated"))
}


  (
    dataWidth: Int,
    fifoDepth: Int
  )
  extends Module {
  val io = IO(new Bundle {
    val din = Input(SInt(dataWidth.W))
    val dinValid = Input(Bool())
    val doutReady = Input(Bool())
    val dinReady = Output(Bool())
    val dout = Output(SInt(dataWidth.W))
    val doutValid = Output(Bool())
  })

  val fifo = Module(new DFifoImp(dataWidth, fifoDepth))
  fifo.io.clock := clock
  fifo.io.reset := reset.asBool
  fifo.io.din := io.din
  fifo.io.dinValid := io.dinValid
  fifo.io.doutReady := io.doutReady
  io.dinReady := fifo.io.dinReady
  io.dout := fifo.io.dout
  io.doutValid := fifo.io.doutValid
}

// Generate the Verilog code
object DFifoMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new DFifo(32, 32), Array("--target-dir", "generated"))
}


    val reset = Input(Bool())
    val din = Input(SInt(dataWidth.W))
    val dinValid = Input(Bool())
    val doutReady = Input(Bool())
    val dinReady = Output(Bool())
    val dout = Output(SInt(dataWidth.W))
    val doutValid = Output(Bool())
  })

  addResource("vsrc/DFifoImp.v")
}

  extends BlackBox(Map("dataWidth" -> dataWidth, "fifoDepth" -> fifoDepth)) with HasBlackBoxResource{
  val io = IO(new Bundle {


}

// Generate the Verilog code
object DRegMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new DReg(8), Array("--target-dir", "generated"))
}

  when (io.doutReady === true.B) {
    data := io.din
    valid := io.dinValid
  }


  (
      numberOfValids: Int,
      numberOfReadys: Int
  )
  extends Module {
  val io = IO(new Bundle {
    val validIn = Input(UInt(numberOfValids.W))
    val readyOut = Input(UInt(numberOfReadys.W))
    val validMuxSel = Input(UInt((log2Ceil(numberOfValids).W)))
    val forkMask = Input(UInt(numberOfReadys.W))
    val validOut = Output(Bool())
  })
  // All wires
  val aux = Wire(Vec(numberOfReadys+1, Bool()))
  val temp = Wire(Vec(numberOfReadys+1, Bool()))
  val vaux = Wire(SInt(1.W))

  for (i <- 0 until numberOfReadys) {
    aux(i) := ((~io.forkMask(i)) | io.readyOut(i)).asBool
  }
  val confMux  = Module (new ConfMux(numberOfValids, 1))
  confMux.io.selector := io.validMuxSel
  confMux.io.muxInput := (io.validIn).asSInt
  vaux := confMux.io.muxOutput

  aux(numberOfReadys) := vaux(0).asBool
  temp(0) := aux(0)

  for (i <- 1 until numberOfReadys+1) {
    temp(i) := temp(i-1) & aux(i)
  }
  io.validOut := temp(numberOfReadys)
}

// Generate the Verilog code
object FrMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new Fr(5, 5), Array("--target-dir", "generated"))
}


object FsMain extends App {
    println("Generating the hardware")
    (new chisel3.stage.ChiselStage).emitVerilog(new Fs(5), Array("--target-dir", "generated"))
}


  def STATE_1 = 1.U(2.W)    // 01
  def STATE_2 = 2.U(2.W)    // 10
  def STATE_3 = 3.U(2.W)    // 11
}

import states._

class Fu
  (
    dataWidth: Int,
    opWidth: Int
  )
  extends Module {
  val io = IO(new Bundle {
    val din1 = Input(SInt(dataWidth.W))
    val din2 = Input(SInt(dataWidth.W))
    val dinValid = Input(Bool())
    val doutReady = Input(Bool())
    val loopSource = Input(UInt(2.W))
    val iterationsReset = Input(UInt(16.W))
    val opConfig = Input(UInt(opWidth.W))
    val dinReady = Output(Bool())
    val dout = Output(SInt(dataWidth.W))
    val doutValid = Output(Bool())
  })

  val aluDin1 = Wire(SInt(dataWidth.W))
  val aluDin2 = Wire(SInt(dataWidth.W))

  val aluDout = Wire(SInt(dataWidth.W))

  val doutReg = RegInit(0.S(dataWidth.W))
  val count = RegInit(0.U(16.W))
  val loaded = RegInit(0.U(1.W))

  //val valid = RegInit(0.U(1.W))
  val valid = Wire(Bool())

  val alu = Module (new Alu(dataWidth, opWidth))
  alu.io.din1 := aluDin1
  alu.io.din2 := aluDin2
  aluDout := alu.io.dout
  alu.io.opConfig := io.opConfig

  when (io.loopSource === STATE_0) {
    aluDin1 := io.din1
    aluDin2 := io.din2
  }
  .elsewhen (io.loopSource === STATE_1) {
    when (loaded === 0.U) {
      aluDin1 := io.din1
      aluDin2 := io.din2
    }
    .otherwise {
      aluDin1 := doutReg
      aluDin2 := io.din2
    }
  }
  .elsewhen (io.loopSource === STATE_2) {
    when (loaded === 0.U) {
      aluDin1 := io.din1
      aluDin2 := io.din2
    }
    .otherwise {
      aluDin1 := io.din1
      aluDin2 := doutReg
    }
  }
  .otherwise {
    aluDin1 := (dataWidth - 1).S
    aluDin2 := (dataWidth - 1).S
  }

  when (io.doutReady === 1.U) {
    valid := 0.U
  }.otherwise{
    valid := 1.U
  }

  when (io.dinValid === 1.U && io.doutReady === 1.U &&
    (io.loopSource === STATE_1 || io.loopSource === STATE_2)) {
    loaded := 1.U
    count := count + 1.U
  }

  when (count === io.iterationsReset &&
    (io.loopSource === STATE_1 || io.loopSource === STATE_2) &&
    io.doutReady === 1.U){
    count := 0.U
    loaded := 0.U
    valid := 1.U
    doutReg := aluDout
  }
  .elsewhen ((io.loopSource === STATE_1 || io.loopSource === STATE_2) &&
      io.dinValid === 1.U && io.doutReady === 1.U){
    doutReg := aluDout
  }

  io.dinReady := io.doutReady

  when (io.loopSource === STATE_0){
    io.dout := aluDout
    io.doutValid := io.dinValid
  }
  .otherwise{
    io.dout := doutReg
    io.doutValid := valid
  }
}

// Generate the Verilog code
object FuMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new Fu(32, 5), Array("--target-dir", "generated"))
}


object JoinMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new Join(32), Array("--target-dir", "generated"))
}


    northDin(i) := (io.dataIn(dataWidth*(i+1) - 1, dataWidth*i)).asSInt
    northDinValid(i) := io.dataInValid(i)
    vecDataInReady(i) := northDinReady(i)

  }

  io.dataInReady := vecDataInReady.asUInt
  val vecDataOut = Wire(Vec(outputNodes, SInt(dataWidth.W)))

  val I = RegInit(0.U(log2Ceil(inputNodes).W))
  val J = RegInit(0.U(log2Ceil(outputNodes).W))

  for (I <- 0 until inputNodes)
  {
    for (J <- 0 until outputNodes)
    {
      // WEST_OV
      if (I == 0)
      {
        if (J == 0)
        {
          val northWestOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          northWestOv.io.northDin := northDin(I)
          northWestOv.io.northDinValid := northDinValid(I)
          northDinReady(I) := northWestOv.io.northDinReady.asBool

          // ********* East
          northWestOv.io.eastDin := intercDataWE(I)(J)
          northWestOv.io.eastDinValid := intercValidWE(I)(J)
          intercReadyWE(I)(J) := northWestOv.io.eastDinReady

          // ********* South
          northWestOv.io.southDin := intercDataNS(I)(J)
          northWestOv.io.southDinValid := intercValidNS(I)(J)
          intercReadyNS(I)(J) := northWestOv.io.southDinReady

          // ********* West
          northWestOv.io.westDin := 0.S
          northWestOv.io.westDinValid := 0.U
          // westDinReady = open

          // ********* North
          // northDout = open
          // northDoutValid = open
          northWestOv.io.northDoutReady := 0.U

          // ********* East
          intercDataEW(I)(J) := northWestOv.io.eastDout
          intercValidEW(I)(J) := northWestOv.io.eastDoutValid
          northWestOv.io.eastDoutReady := intercReadyEW(I)(J)

          // ********* South
          intercDataSN(I)(J) := northWestOv.io.southDout
          intercValidSN(I)(J) := northWestOv.io.southDoutValid
          northWestOv.io.southDoutReady := intercReadySN(I)(J)

          // ********* West
          // westDout = open
          // westDoutValid = open
          northWestOv.io.westDoutReady := 0.U

          // ********* Config
          northWestOv.io.configBits := configBits
          northWestOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }

        if (J != 0 & J != outputNodes - 1)
        {
          val midWestOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          midWestOv.io.northDin := intercDataSN(I)(J-1)
          midWestOv.io.northDinValid := intercValidSN(I)(J-1)
          intercReadySN(I)(J-1) := midWestOv.io.northDinReady

          // ********* East
          midWestOv.io.eastDin := intercDataWE(I)(J)
          midWestOv.io.eastDinValid := intercValidWE(I)(J)
          intercReadyWE(I)(J) := midWestOv.io.eastDinReady

          // ********* South
          midWestOv.io.southDin := intercDataNS(I)(J)
          midWestOv.io.southDinValid := intercValidNS(I)(J)
          intercReadyNS(I)(J) := midWestOv.io.southDinReady

          // ********* West
          midWestOv.io.westDin := 0.S
          midWestOv.io.westDinValid := 0.U
          //westDinReady = open

          // ********* North
          intercDataNS(I)(J-1) := midWestOv.io.northDout
          intercValidNS(I)(J-1) := midWestOv.io.northDoutValid
          midWestOv.io.northDoutReady := intercReadyNS(I)(J-1)

          // ********* East
          intercDataEW(I)(J) := midWestOv.io.eastDout
          intercValidEW(I)(J) := midWestOv.io.eastDoutValid
          midWestOv.io.eastDoutReady := intercReadyEW(I)(J)

          // ********* South
          intercDataSN(I)(J) := midWestOv.io.southDout
          intercValidSN(I)(J) := midWestOv.io.southDoutValid
          midWestOv.io.southDoutReady := intercReadySN(I)(J)

          // ********* West
          //westDout = open
          //westDoutValid = open
          midWestOv.io.westDoutReady := 0.U

          // ********* Config
          midWestOv.io.configBits := configBits
          midWestOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }

        if (J == outputNodes - 1)
        {
          val southWestOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          southWestOv.io.northDin := intercDataSN(I)(J-1)
          southWestOv.io.northDinValid := intercValidSN(I)(J-1)
          intercReadySN(I)(J-1) := southWestOv.io.northDinReady

          // ********* East
          southWestOv.io.eastDin := intercDataWE(I)(J)
          southWestOv.io.eastDinValid := intercValidWE(I)(J)
          intercReadyWE(I)(J) := southWestOv.io.eastDinReady

          // ********* South
          southWestOv.io.southDin := 0.S
          southWestOv.io.southDinValid := 0.U
          // southDinReady = open

          // ********* West
          southWestOv.io.westDin := 0.S
          southWestOv.io.westDinValid := 0.U
          // westDinReady = open

          // ********* North
          intercDataNS(I)(J-1) := southWestOv.io.northDout
          intercValidNS(I)(J-1) := southWestOv.io.northDoutValid
          southWestOv.io.northDoutReady := intercReadyNS(I)(J-1)

          // ********* East
          intercDataEW(I)(J) := southWestOv.io.eastDout
          intercValidEW(I)(J) := southWestOv.io.eastDoutValid
          southWestOv.io.eastDoutReady := intercReadyEW(I)(J)

          // ********* South
          // southDout = open
          // southDoutValid = open
          southWestOv.io.southDoutReady := 0.U

          // ********* West
          // westDout = open
          // westDoutValid = open
          southWestOv.io.westDoutReady := 0.U

          // ********* Config
          southWestOv.io.configBits := configBits
          southWestOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }
      }
      ////////////////////////////////////////////////////
      // MIDDLE_OV
      if (I != 0 & I != inputNodes - 1)
      {
        if (J == 0)
        {
          val middleNorthOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          middleNorthOv.io.northDin := northDin(I)
          middleNorthOv.io.northDinValid := northDinValid(I)
          northDinReady(I) := middleNorthOv.io.northDinReady

          // ********* East
          middleNorthOv.io.eastDin:= intercDataWE(I)(J)
          middleNorthOv.io.eastDinValid := intercValidWE(I)(J)
          intercReadyWE(I)(J) := middleNorthOv.io.eastDinReady

          // ********* South
          middleNorthOv.io.southDin := intercDataNS(I)(J)
          middleNorthOv.io.southDinValid := intercValidNS(I)(J)
          intercReadyNS(I)(J) := middleNorthOv.io.southDinReady

          // ********* West
          middleNorthOv.io.westDin:= intercDataEW(I-1)(J)
          middleNorthOv.io.westDinValid := intercValidEW(I-1)(J)
          intercReadyEW(I-1)(J) := middleNorthOv.io.westDinReady

          // ********* North
          // northDout = open
          // northDoutValid = open
          middleNorthOv.io.northDoutReady := 0.U

          // ********* East
          intercDataEW(I)(J) := middleNorthOv.io.eastDout
          intercValidEW(I)(J) := middleNorthOv.io.eastDoutValid
          middleNorthOv.io.eastDoutReady := intercReadyEW(I)(J)

          // ********* South
          intercDataSN(I)(J) := middleNorthOv.io.southDout
          intercValidSN(I)(J) := middleNorthOv.io.southDoutValid
          middleNorthOv.io.southDoutReady := intercReadySN(I)(J)

          // ********* West
          intercDataWE(I-1)(J) := middleNorthOv.io.westDout
          intercValidWE(I-1)(J) := middleNorthOv.io.westDoutValid
          middleNorthOv.io.westDoutReady := intercReadyWE(I-1)(J)

          // ********* Config
          middleNorthOv.io.configBits := configBits
          middleNorthOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }

        if(J != 0 & J != outputNodes - 1)
        {
          val middleMiddleOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          middleMiddleOv.io.northDin := intercDataSN(I)(J-1)
          middleMiddleOv.io.northDinValid := intercValidSN(I)(J-1)
          intercReadySN(I)(J-1) := middleMiddleOv.io.northDinReady

          // ********* East
          middleMiddleOv.io.eastDin := intercDataWE(I)(J)
          middleMiddleOv.io.eastDinValid := intercValidWE(I)(J)
          intercReadyWE(I)(J) := middleMiddleOv.io.eastDinReady

          // ********* South
          middleMiddleOv.io.southDin := intercDataNS(I)(J)
          middleMiddleOv.io.southDinValid := intercValidNS(I)(J)
          intercReadyNS(I)(J) := middleMiddleOv.io.southDinReady

          // ********* West
          middleMiddleOv.io.westDin := intercDataEW(I-1)(J)
          middleMiddleOv.io.westDinValid := intercValidEW(I-1)(J)
          intercReadyEW(I-1)(J) := middleMiddleOv.io.westDinReady

          // ********* North
          intercDataNS(I)(J-1) := middleMiddleOv.io.northDout
          intercValidNS(I)(J-1) := middleMiddleOv.io.northDoutValid
          middleMiddleOv.io.northDoutReady := intercReadyNS(I)(J-1)

          // ********* East
          intercDataEW(I)(J) := middleMiddleOv.io.eastDout
          intercValidEW(I)(J) := middleMiddleOv.io.eastDoutValid
          middleMiddleOv.io.eastDoutReady := intercReadyEW(I)(J)

          // ********* South
          intercDataSN(I)(J) := middleMiddleOv.io.southDout
          intercValidSN(I)(J) := middleMiddleOv.io.southDoutValid
          middleMiddleOv.io.southDoutReady := intercReadySN(I)(J)

          // ********* West
          intercDataWE(I-1)(J) := middleMiddleOv.io.westDout
          intercValidWE(I-1)(J) := middleMiddleOv.io.westDoutValid
          middleMiddleOv.io.westDoutReady := intercReadyWE(I-1)(J)

          // ********* Config
          middleMiddleOv.io.configBits := configBits
          middleMiddleOv.io.catchConfig:= catchConfig(I+inputNodes*J)
        }

        if (J == outputNodes - 1)
        {
          val middleSouthOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          middleSouthOv.io.northDin := intercDataSN(I)(J-1)
          middleSouthOv.io.northDinValid := intercValidSN(I)(J-1)
          intercReadySN(I)(J-1) := middleSouthOv.io.northDinReady

          // ********* East
          middleSouthOv.io.eastDin := intercDataWE(I)(J)
          middleSouthOv.io.eastDinValid := intercValidWE(I)(J)
          intercReadyWE(I)(J) := middleSouthOv.io.eastDinReady

          // ********* South
          middleSouthOv.io.southDin := 0.S
          middleSouthOv.io.southDinValid := 0.U
          // southDinReady = open

          // ********* West
          middleSouthOv.io.westDin := intercDataEW(I-1)(J)
          middleSouthOv.io.westDinValid := intercValidEW(I-1)(J)
          intercReadyEW(I-1)(J) := middleSouthOv.io.westDinReady

          // ********* North
          intercDataNS(I)(J-1) := middleSouthOv.io.northDout
          intercValidNS(I)(J-1) := middleSouthOv.io.northDoutValid
          middleSouthOv.io.northDoutReady := intercReadyNS(I)(J-1)

          // ********* East
          intercDataEW(I)(J) := middleSouthOv.io.eastDout
          intercValidEW(I)(J) := middleSouthOv.io.eastDoutValid
          middleSouthOv.io.eastDoutReady := intercReadyEW(I)(J)

          // ********* South
          // southDout = open
          // southDoutValid = open
          middleSouthOv.io.southDoutReady := 0.U

          // ********* West
          intercDataWE(I-1)(J) := middleSouthOv.io.westDout
          intercValidWE(I-1)(J) := middleSouthOv.io.westDoutValid
          middleSouthOv.io.westDoutReady := intercReadyWE(I-1)(J)

          // ********* Config
          middleSouthOv.io.configBits := configBits
          middleSouthOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }
      }

      // EAST_OV
      if (I == inputNodes - 1)
      {
        if (J == 0)
        {
          val northEastOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          northEastOv.io.northDin := northDin(I)
          northEastOv.io.northDinValid := northDinValid(I)
          northDinReady(I) := northEastOv.io.northDinReady

          // ********* East
          northEastOv.io.eastDin := 0.S
          northEastOv.io.eastDinValid := 0.U
          // eastDinReady = open

          // ********* South
          northEastOv.io.southDin := intercDataNS(I)(J)
          northEastOv.io.southDinValid := intercValidNS(I)(J)
          intercReadyNS(I)(J) := northEastOv.io.southDinReady

          // ********* West
          northEastOv.io.westDin := intercDataEW(I-1)(J)
          northEastOv.io.westDinValid := intercValidEW(I-1)(J)
          intercReadyEW(I-1)(J) := northEastOv.io.westDinReady

          // ********* North
          // northDout = open
          // northDoutValid = open
          northEastOv.io.northDoutReady := 0.U

          // ********* East
          eastDout(J) := northEastOv.io.eastDout
          eastDoutValid(J) := northEastOv.io.eastDoutValid
          northEastOv.io.eastDoutReady := eastDoutReady(J)

          // ********* South
          intercDataSN(I)(J) := northEastOv.io.southDout
          intercValidSN(I)(J) := northEastOv.io.southDoutValid
          northEastOv.io.southDoutReady := intercReadySN(I)(J)

          // ********* West
          intercDataWE(I-1)(J) := northEastOv.io.westDout
          intercValidWE(I-1)(J) := northEastOv.io.westDoutValid
          northEastOv.io.westDoutReady := intercReadyWE(I-1)(J)

          // ********* Config
          northEastOv.io.configBits := configBits
          northEastOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }

        if (J != 0 &  J != outputNodes - 1)
        {
          val middleEastOv = Module(new ProcessingElement(dataWidth,fifoDepth))

          // ********* North
          middleEastOv.io.northDin := intercDataSN(I)(J-1)
          middleEastOv.io.northDinValid := intercValidSN(I)(J-1)
          intercReadySN(I)(J-1) := middleEastOv.io.northDinReady

          // ********* East
          middleEastOv.io.eastDin := 0.S
          middleEastOv.io.eastDinValid := 0.U
          // io.eastDinReady = open

          // ********* South
          middleEastOv.io.southDin := intercDataNS(I)(J)
          middleEastOv.io.southDinValid:= intercValidNS(I)(J)
          intercReadyNS(I)(J) := middleEastOv.io.southDinReady

          // ********* West
          middleEastOv.io.westDin := intercDataEW(I-1)(J)
          middleEastOv.io.westDinValid := intercValidEW(I-1)(J)
          intercReadyEW(I-1)(J) := middleEastOv.io.westDinReady

          // ********* North
          intercDataNS(I)(J-1) := middleEastOv.io.northDout
          intercValidNS(I)(J-1) := middleEastOv.io.northDoutValid
          middleEastOv.io.northDoutReady := intercReadyNS(I)(J-1)

          // ********* East
          eastDout(J) := middleEastOv.io.eastDout
          eastDoutValid(J) := middleEastOv.io.eastDoutValid
          middleEastOv.io.eastDoutReady := eastDoutReady(J)

          // ********* South
          intercDataSN(I)(J) := middleEastOv.io.southDout
          intercValidSN(I)(J) := middleEastOv.io.southDoutValid
          middleEastOv.io.southDoutReady := intercReadySN(I)(J)

          // ********* West
          intercDataWE(I-1)(J) := middleEastOv.io.westDout
          intercValidWE(I-1)(J) := middleEastOv.io.westDoutValid
          middleEastOv.io.westDoutReady := intercReadyWE(I-1)(J)

          // ********* Config
          middleEastOv.io.configBits := configBits
          middleEastOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }

        if (J == outputNodes - 1)
        {
          val middleSouthOv = Module(new ProcessingElement(dataWidth, fifoDepth))

          // ********* North
          middleSouthOv.io.northDin := intercDataSN(I)(J-1)
          middleSouthOv.io.northDinValid := intercValidSN(I)(J-1)
          intercReadySN(I)(J-1) := middleSouthOv.io.northDinReady

          // ********* East
          middleSouthOv.io.eastDin := 0.S
          middleSouthOv.io.eastDinValid := 0.U
          //io.eastDinReady = open

          // ********* South
          middleSouthOv.io.southDin := 0.S
          middleSouthOv.io.southDinValid := 0.U
          // io.southDinReady = open

          // ********* West
          middleSouthOv.io.westDin := intercDataEW(I-1)(J)
          middleSouthOv.io.westDinValid := intercValidEW(I-1)(J)
          intercReadyEW(I-1)(J) := middleSouthOv.io.westDinReady

          // ********* North
          intercDataNS(I)(J-1) := middleSouthOv.io.northDout
          intercValidNS(I)(J-1) := middleSouthOv.io.northDoutValid
          middleSouthOv.io.northDoutReady := intercReadyNS(I)(J-1)

          // ********* East
          eastDout(J) := middleSouthOv.io.eastDout
          eastDoutValid(J) := middleSouthOv.io.eastDoutValid
          middleSouthOv.io.eastDoutReady := eastDoutReady(J)

          // ********* South
          // southDout = open
          // southDoutValid = open
          middleSouthOv.io.southDoutReady := 0.U

          // ********* West
          intercDataWE(I-1)(J) := middleSouthOv.io.westDout
          intercValidWE(I-1)(J) := middleSouthOv.io.westDoutValid
          middleSouthOv.io.westDoutReady := intercReadyWE(I-1)(J)

          // ********* Config
          middleSouthOv.io.configBits := configBits
          middleSouthOv.io.catchConfig := catchConfig(I+inputNodes*J)
        }
      }
    }
  }
  for( i <- 0 to outputNodes - 1){
    vecDataOut(i) := eastDout(i)
    vecDataOutValid (i) := eastDoutValid(i)
    eastDoutReady(i) := io.dataOutReady(i)
  }
  io.dataOut := vecDataOut.asUInt
  io.dataOutValid := vecDataOutValid.asUInt
}

// Generate the Verilog code
object OverlayRoccMain extends App {
  println("Generating the hardware")
  (new chisel3.stage.ChiselStage).emitVerilog(new OverlayRocc(32, 6, 6, 32), Array("--target-dir", "generated"))
}

  when (io.cellConfig(191) === 1.U){
    catchConfig(unsignedCellConfig) := 1.B
  }


  (
    dataWidth: Int,
    fifoDepth: Int
  )
  extends Module {
  val io = IO(new Bundle {
    //  Data in
    val northDin = Input(SInt(dataWidth.W))
    val northDinValid = Input(Bool())
    val northDinReady = Output(Bool())

    val eastDin = Input(SInt(dataWidth.W))
    val eastDinValid = Input(Bool())
    val eastDinReady = Output(Bool())

    val southDin = Input(SInt(dataWidth.W))
    val southDinValid = Input(Bool())
    val southDinReady = Output(Bool())

    val westDin = Input(SInt(dataWidth.W))
    val westDinValid = Input(Bool())
    val westDinReady = Output(Bool())

    //  Data out
    val northDout = Output(SInt(dataWidth.W))
    val northDoutValid = Output(Bool())
    val northDoutReady = Input(Bool())

    val eastDout = Output(SInt(dataWidth.W))
    val eastDoutValid = Output(Bool())
    val eastDoutReady = Input(Bool())

    val southDout = Output(SInt(dataWidth.W))
    val southDoutValid = Output(Bool())
    val southDoutReady = Input(Bool())

    val westDout = Output(SInt(dataWidth.W))
    val westDoutValid = Output(Bool())
    val westDoutReady = Input(Bool())

    //  Config
    val configBits = Input(UInt(182.W))
    val catchConfig = Input(Bool())
  })

  // Config signals
  val muxNsel = Wire(UInt(2.W))
  val muxEsel = Wire(UInt(2.W))
  val muxSsel = Wire(UInt(2.W))
  val muxWsel = Wire(UInt(2.W))
  val acceptMaskFrN = Wire(UInt(5.W))
  val acceptMaskFrE = Wire(UInt(5.W))
  val acceptMaskFrS = Wire(UInt(5.W))
  val acceptMaskFrW = Wire(UInt(5.W))
  val acceptMaskFsiN = Wire(UInt(5.W))
  val acceptMaskFsiE = Wire(UInt(5.W))
  val acceptMaskFsiS = Wire(UInt(5.W))
  val acceptMaskFsiW = Wire(UInt(5.W))

  val configBitsReg = RegInit(0.U(182.W))

  //  Interconnect signals
  val northBuffer = Wire(SInt(dataWidth.W))
  val eastBuffer = Wire(SInt(dataWidth.W))
  val southBuffer = Wire(SInt(dataWidth.W))
  val westBuffer = Wire(SInt(dataWidth.W))

  val northBufferValid = Wire(UInt(1.W))
  val eastBufferValid = Wire(UInt(1.W))
  val southBufferValid = Wire(UInt(1.W))
  val westBufferValid = Wire(UInt(1.W))

  val northBufferReady = Wire(UInt(1.W))
  val eastBufferReady = Wire(UInt(1.W))
  val southBufferReady = Wire(UInt(1.W))
  val westBufferReady = Wire(UInt(1.W))

  val northRegDin = Wire(SInt(dataWidth.W))
  val eastRegDin = Wire(SInt(dataWidth.W))
  val southRegDin = Wire(SInt(dataWidth.W))
  val westRegDin = Wire(SInt(dataWidth.W))

  val northRegDinValid = Wire(UInt(1.W))
  val eastRegDinValid = Wire(UInt(1.W))
  val southRegDinValid = Wire(UInt(1.W))
  val westRegDinValid = Wire(UInt(1.W))

  val northRegDinReady = Wire(UInt(1.W))
  val eastRegDinReady = Wire(UInt(1.W))
  val southRegDinReady = Wire(UInt(1.W))
  val westRegDinReady = Wire(UInt(1.W))
  //  cell processing signals
  val fuDin1Ready = Wire(UInt(1.W))
  val fuDin2Ready = Wire(UInt(1.W))
  val fuDout = Wire(SInt(dataWidth.W))
  val fuDoutValid = Wire(UInt(1.W))

  when (io.catchConfig) {
      configBitsReg := io.configBits
  }
  // Configuration bits arrangement
  // val muxNsel = configBitsReg(7, 6)
  muxNsel := configBitsReg(7, 6)
  muxEsel := configBitsReg(9, 8)
  muxSsel := configBitsReg(11, 10)
  muxWsel := configBitsReg(13, 12)
  // cell_interior conf
  acceptMaskFsiN := configBitsReg(28, 24)
  acceptMaskFsiE := configBitsReg(33, 29)
  acceptMaskFsiS := configBitsReg(38, 34)
  acceptMaskFsiW := configBitsReg(43, 39)
  // cell_interior conf
  acceptMaskFrN := configBitsReg(61, 57)
  acceptMaskFrE := configBitsReg(66, 62)
  acceptMaskFrS := configBitsReg(71, 67)
  acceptMaskFrW := configBitsReg(76, 72)

  // ------------------------------- NORTH NODE -------------------------------

  val fifoNin = Module (new DFifo(dataWidth, fifoDepth))
  fifoNin.io.din := io.northDin
  fifoNin.io.dinValid := io.northDinValid
  fifoNin.io.doutReady := northBufferReady
  io.northDinReady := fifoNin.io.dinReady
  northBuffer := fifoNin.io.dout
  northBufferValid := fifoNin.io.doutValid

  val fsNin = Module (new Fs(5))
  val ready_out_fsNin = Cat(fuDin1Ready, fuDin2Ready, eastRegDinReady, southRegDinReady, westRegDinReady)
  fsNin.io.readyOut := ready_out_fsNin
  northBufferReady := fsNin.io.readyIn
  fsNin.io.forkMask := acceptMaskFsiN

  val muxNout  = Module (new ConfMux(4, dataWidth))
  muxNout.io.selector := muxNsel
  muxNout.io.muxInput := (Cat(westBuffer, southBuffer, eastBuffer, fuDout)).asSInt
  northRegDin := muxNout.io.muxOutput

  val frNout = Module (new Fr(4, 5))
  val ready_frNout = Cat(fuDin1Ready, fuDin2Ready, eastRegDinReady, southRegDinReady, westRegDinReady)
  val validIn_frNout = Cat(westBufferValid, southBufferValid, eastBufferValid, fuDoutValid)
  frNout.io.readyOut := ready_frNout
  frNout.io.validIn := validIn_frNout
  frNout.io.validMuxSel := muxNsel
  frNout.io.forkMask := acceptMaskFrN
  northRegDinValid := frNout.io.validOut

  val regNout = Module (new DReg(dataWidth))

  regNout.io.din := northRegDin
  regNout.io.dinValid := northRegDinValid
  regNout.io.doutReady := io.northDoutReady

  northRegDinReady := regNout.io.dinReady
  io.northDout := regNout.io.dout
  io.northDoutValid := regNout.io.doutValid

  //------------------------------- NORTH NODE -------------------------------

  // ------------------------------- EAST  NODE -------------------------------

  val fifoEin = Module (new DFifo(dataWidth, fifoDepth))
  fifoEin.io.din := io.eastDin
  fifoEin.io.dinValid := io.eastDinValid
  fifoEin.io.doutReady := eastBufferReady
  io.eastDinReady := fifoEin.io.dinReady
  eastBuffer := fifoEin.io.dout
  eastBufferValid := fifoEin.io.doutValid

  val fsEin = Module (new Fs(5))
  val ready_out_fsEin = Cat(fuDin1Ready, fuDin2Ready, northRegDinReady, southRegDinReady, westRegDinReady)
  fsEin.io.readyOut := ready_out_fsEin
  eastBufferReady := fsEin.io.readyIn
  fsEin.io.forkMask := acceptMaskFsiE

  val muxEout  = Module (new ConfMux(4, dataWidth))
  muxEout.io.selector := muxEsel
  muxEout.io.muxInput := (Cat(westBuffer, southBuffer, northBuffer, fuDout)).asSInt
  eastRegDin := muxEout.io.muxOutput

  val frEout = Module (new Fr(4, 5))
  val ready_frEout = Cat(fuDin1Ready, fuDin2Ready, northRegDinReady, southRegDinReady, westRegDinReady)
  val validIn_frEout = Cat(westBufferValid, southBufferValid, northBufferValid, fuDoutValid)
  frEout.io.readyOut := ready_frEout
  frEout.io.validIn := validIn_frEout
  frEout.io.validMuxSel := muxEsel
  frEout.io.forkMask := acceptMaskFrE
  eastRegDinValid := frEout.io.validOut

  val regEout = Module (new DReg(dataWidth))

  regEout.io.din := eastRegDin
  regEout.io.dinValid := eastRegDinValid
  regEout.io.doutReady := io.eastDoutReady

  eastRegDinReady := regEout.io.dinReady
  io.eastDout := regEout.io.dout
  io.eastDoutValid := regEout.io.doutValid

  // ------------------------------- EAST  NODE -------------------------------

  // ------------------------------- SOUTH NODE -------------------------------

  val fifoSin = Module (new DFifo(dataWidth, fifoDepth))
  fifoSin.io.din := io.southDin
  fifoSin.io.dinValid := io.southDinValid
  fifoSin.io.doutReady := southBufferReady
  io.southDinReady := fifoSin.io.dinReady
  southBuffer := fifoSin.io.dout
  southBufferValid := fifoSin.io.doutValid

  val fsSin = Module (new Fs(5))
  val ready_out_fsSin = Cat(fuDin1Ready, fuDin2Ready, northRegDinReady, eastRegDinReady, westRegDinReady)
  fsSin.io.readyOut := ready_out_fsSin
  southBufferReady := fsSin.io.readyIn
  fsSin.io.forkMask := acceptMaskFsiS

  val muxSout  = Module (new ConfMux(4, dataWidth))
  muxSout.io.selector := muxSsel
  muxSout.io.muxInput := (Cat(westBuffer, eastBuffer, northBuffer, fuDout)).asSInt
  southRegDin := muxSout.io.muxOutput

  val frSout = Module (new Fr(4, 5))
  val ready_frSout = Cat(fuDin1Ready, fuDin2Ready, northRegDinReady, eastRegDinReady, westRegDinReady)
  val validIn_frSout = Cat(westBufferValid, eastBufferValid, northBufferValid, fuDoutValid)
  frSout.io.readyOut := ready_frSout
  frSout.io.validIn := validIn_frSout
  frSout.io.validMuxSel := muxSsel
  frSout.io.forkMask := acceptMaskFrS
  southRegDinValid := frSout.io.validOut

  val regSout = Module (new DReg(dataWidth))

  regSout.io.din := southRegDin
  regSout.io.dinValid := southRegDinValid
  regSout.io.doutReady := io.southDoutReady

  southRegDinReady := regSout.io.dinReady
  io.southDout := regSout.io.dout
  io.southDoutValid := regSout.io.doutValid
  // ------------------------------- SOUTH NODE -------------------------------

  // ------------------------------- WEST  NODE -------------------------------

  val fifoWin = Module (new DFifo(dataWidth, fifoDepth))
  fifoWin.io.din := io.westDin
  fifoWin.io.dinValid := io.westDinValid
  fifoWin.io.doutReady := westBufferReady
  io.westDinReady := fifoWin.io.dinReady
  westBuffer := fifoWin.io.dout
  westBufferValid := fifoWin.io.doutValid

  val fsWin = Module (new Fs(5))
  val ready_out_fsWin = Cat(fuDin1Ready, fuDin2Ready, northRegDinReady, eastRegDinReady, southRegDinReady)
  fsWin.io.readyOut := ready_out_fsWin
  fsWin.io.forkMask :=  acceptMaskFsiW
  westBufferReady := fsWin.io.readyIn

  val muxWout  = Module (new ConfMux(4, dataWidth))
  muxWout.io.selector := muxWsel
  muxWout.io.muxInput := (Cat(southBuffer, eastBuffer, northBuffer, fuDout)).asSInt
  westRegDin := muxWout.io.muxOutput

  val frWout = Module (new Fr(4, 5))
  val ready_frWout = Cat(fuDin1Ready, fuDin2Ready, northRegDinReady, eastRegDinReady, southRegDinReady)
  val validIn_frWout = Cat(southBufferValid, eastBufferValid, northBufferValid, fuDoutValid)
  frWout.io.readyOut := ready_frWout
  frWout.io.validIn := validIn_frWout
  frWout.io.validMuxSel := muxWsel
  frWout.io.forkMask := acceptMaskFrW
  westRegDinValid := frWout.io.validOut

  val regWout = Module (new DReg(dataWidth))

  regWout.io.din := westRegDin
  regWout.io.dinValid := westRegDinValid
  regWout.io.doutReady := io.westDoutReady

  westRegDinReady := regWout.io.dinReady
  io.westDout := regWout.io.dout
  io.westDoutValid := regWout.io.doutValid
  // ------------------------------- WEST  NODE -------------------------------

  // cell processing
  val cell = Module (new CellProcessing(dataWidth))
  cell.io.northDin := northBuffer
  cell.io.northDinValid := northBufferValid
  cell.io.eastDin := eastBuffer
  cell.io.eastDinValid := eastBufferValid
  cell.io.southDin := southBuffer
  cell.io.southDinValid := southBufferValid
  cell.io.westDin := westBuffer
  cell.io.westDinValid := westBufferValid
  cell.io.northDoutReady := northRegDinReady
  cell.io.eastDoutReady := eastRegDinReady
  cell.io.southDoutReady := southRegDinReady
  cell.io.westDoutReady := westRegDinReady
  cell.io.configBits := configBitsReg
  fuDin1Ready := cell.io.fuDin1Ready
  fuDin2Ready := cell.io.fuDin2Ready
  fuDout := cell.io.dout
  fuDoutValid := cell.io.doutValid
}

// Generate the Verilog code
object ProcessingElementMain extends App {
    println("Generating the hardware")
    (new chisel3.stage.ChiselStage).emitVerilog(new ProcessingElement(32, 32), Array("--target-dir", "generated"))
}


        for (i <- 1 until numOfTests) {
          dut.io.din1.poke((-(i*2)).S)
          dut.io.din2.poke(i.S)
          dut.clock.step(1)
        }


      dut.clock.step(1)
      dut.io.northDin.poke(northDin)
      dut.io.northDinValid.poke(northDinValid)
      dut.io.westDin.poke(westDin)
      dut.io.westDinValid.poke(westDinValid)
      dut.clock.step(1)

      dut.io.northDin.poke(3.S)
      dut.io.westDin.poke(4.S)

      dut.clock.step(1)

      dut.io.northDin.poke(5.S)
      dut.io.westDin.poke(6.S)

      dut.clock.step(1)

      dut.io.northDin.poke(7.S)
      dut.io.westDin.poke(8.S)

      dut.clock.step(1)

      dut.io.northDin.poke(0.S)
      dut.io.northDinValid.poke(false.B)

      dut.io.westDin.poke(0.S)
      dut.io.westDinValid.poke(false.B)

      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.clock.step(1)

      // Outputs
      println("Outputs")
      println("fuDin1Ready: " + dut.io.fuDin1Ready.peek().toString)
      println("fuDin2Ready: " + dut.io.fuDin2Ready.peek().toString)
      println("dout: " + dut.io.dout.peek().toString)
      println("doutValid: " + dut.io.doutValid.peek().toString)
      println("*************************************")
      println("*************************************")
    }
  }
}

      dut.io.eastDinValid.poke(eastDinValid)

      dut.io.southDin.poke(southDin)
      dut.io.southDinValid.poke(southDinValid)


      dut.clock.step(1)
      println("Mux Input: " + dut.io.muxInput.peek().toString)
      println("Mux Selector: " + dut.io.selector.peek().toString)
      println("Mux Output: " + dut.io.muxOutput.peek().toString)
      println("*************************************")
      dut.clock.step(1)
    }
  }
}

      dut.clock.step(1)
      selector = 2
      muxInput = 53


          println("An: " + dut.io.doutReady.peek().toString)
          dut.clock.step(1)
          dut.clock.step(1)
          dut.io.dinValid.poke(false.B)
          dut.clock.step(1)


                dut.io.dinValid.poke(false.B)
                dut.clock.step(10)
            }

            dut.clock.step(10)
        }
    }
}

                dut.io.din.poke(i.S)
                dut.io.dinValid.poke(true.B)


            var doutReady = false.B
            var dinValid = false.B


            dut.io.forkMask.poke(forkMask)
            println("*************************************")
            println("*************************************")
            println("Valid In: " + dut.io.validIn.peek().toString)
            println("Ready Out: " + dut.io.readyOut.peek().toString)
            println("Valid Mux Sel: " + dut.io.validMuxSel.peek().toString)
            println("Fork Mask: " + dut.io.forkMask.peek().toString)
            dut.clock.step(1)
            println("--------------------------------------")
            println("Valid Out: " + dut.io.validOut.peek().toString)
            validMuxSel = "b1".U
            dut.io.validMuxSel.poke(validMuxSel)
            println("*************************************")
            println("*************************************")
            println("Valid In: " + dut.io.validIn.peek().toString)
            println("Ready Out: " + dut.io.readyOut.peek().toString)
            println("Valid Mux Sel: " + dut.io.validMuxSel.peek().toString)
            println("Fork Mask: " + dut.io.forkMask.peek().toString)
            dut.clock.step(1)
            println("--------------------------------------")
            println("Valid Out: " + dut.io.validOut.peek().toString)
            println("*************************************")
            println("*************************************")
        }
    }
}

            var validIn = "b11011".U
            var readyOut = "b11011".U
            var validMuxSel = "b10".U
            var forkMask = "b11011".U


            dut.io.readyOut.poke("b00000".U)
            dut.io.forkMask.poke("b00000".U)


            dut.io.doutReady.poke(doutReady)
            dut.io.opConfig.poke(opConfig)
            dut.io.loopSource.poke(loopSource)
            dut.io.iterationsReset.poke(iterationsReset)
            dut.clock.step(1)
            ////////////////////////////////////////////////////////////////
            // Test 8
            ////////////////////////////////////////////////////////////////
            println("*************************************")
            println("Multiple inputs (din1 and din2)")
            println("*************************************")
            // dinValid: true
            // doutReady: true
            // Loop source: 0
            // Iterations Reset: 0
            din1 = 1.S
            din2 = 2.S
            dinValid = true.B
            doutReady = true.B
            opConfig = 0.U
            loopSource = 0.U
            iterationsReset = 0.U
            dut.io.din1.poke(din1)
            dut.io.din2.poke(din2)
            dut.io.dinValid.poke(dinValid)
            dut.io.doutReady.poke(doutReady)
            dut.io.opConfig.poke(opConfig)
            dut.io.loopSource.poke(loopSource)
            dut.io.iterationsReset.poke(iterationsReset)
            println("Din1: " + dut.io.din1.peek().toString)
            println("Din2: " + dut.io.din2.peek().toString)
            dut.clock.step(1)
            println("*************************************")
            println("Summation: " + dut.io.dout.peek().toString)
            din1 = 2.S
            din2 = 3.S
            dut.io.din1.poke(din1)
            dut.io.din2.poke(din2)
            println("Din1: " + dut.io.din1.peek().toString)
            println("Din2: " + dut.io.din2.peek().toString)
            dut.clock.step(1)
            println("*************************************")
            println("Summation: " + dut.io.dout.peek().toString)
            din1 = 3.S
            din2 = 4.S
            dut.io.din1.poke(din1)
            dut.io.din2.poke(din2)
            println("Din1: " + dut.io.din1.peek().toString)
            println("Din2: " + dut.io.din2.peek().toString)
            dut.clock.step(1)
            println("*************************************")
            println("Summation: " + dut.io.dout.peek().toString)
            din1 = 4.S
            din2 = 5.S
            dut.io.din1.poke(din1)
            dut.io.din2.poke(din2)
            println("Din1: " + dut.io.din1.peek().toString)
            println("Din2: " + dut.io.din2.peek().toString)
            dut.clock.step(1)
            println("*************************************")
            println("Summation: " + dut.io.dout.peek().toString)
            din1 = 5.S
            din2 = 6.S
            dut.io.din1.poke(din1)
            dut.io.din2.poke(din2)
            println("Din1: " + dut.io.din1.peek().toString)
            println("Din2: " + dut.io.din2.peek().toString)
            dut.clock.step(1)
            println("*************************************")
            println("Summation: " + dut.io.dout.peek().toString)
            din1 = 6.S
            din2 = 7.S
            dut.io.din1.poke(din1)
            dut.io.din2.poke(din2)
            println("Din1: " + dut.io.din1.peek().toString)
            println("Din2: " + dut.io.din2.peek().toString)
            dut.clock.step(1)
            println("*************************************")
            println("Summation: " + dut.io.dout.peek().toString)
        }
    }
}


      dut.io.dataOutReady.poke(dataOutReady)
      dut.clock.step(1)

      var cellConfig: UInt = 0.U


          dut.io.dataIn.poke(din)
          dut.io.dataInValid.poke("b111111".U)
          dut.clock.step(1)
          dut.io.dataInValid.poke("b000000".U)
          dut.clock.step(10)
      }

      // Finish
      for( i <- 0 to 10){
          dut.clock.step(1)
      }
      println("End of the simulation")
    }
  }
}


      for( i <- 0 to 10){
        dut.clock.step(1)
      }


  def INSTRUCTION_CHANGE_DELAY = 5
}

import ProcessingElementParams._

class ProcessingElementTest extends AnyFlatSpec with ChiselScalatestTester {
  "ProcessingElementTest test" should "pass" in {
    // Test code for summation with positive numbers

    test(new ProcessingElement(DATA_WIDTH, FIFO_DEPTH)).withAnnotations(Seq(VerilatorBackendAnnotation, WriteVcdAnnotation)) { dut =>

      var numOfTests = 150

      var northDin = 0.S
      var northDinValid = false.B
      val northDoutReady = true.B

      var eastDin = 0.S
      var eastDinValid = false.B
      val eastDoutReady = true.B

      var southDin = 0.S
      var southDinValid = false.B
      val southDoutReady = true.B

      var westDin = 0.S
      var westDinValid = false.B
      val westDoutReady = true.B

      // Initialization
      dut.io.northDin.poke(northDin)
      dut.io.northDinValid.poke(northDinValid)
      dut.io.northDoutReady.poke(northDoutReady)

      dut.io.eastDin.poke(eastDin)
      dut.io.eastDinValid.poke(eastDinValid)
      dut.io.eastDoutReady.poke(eastDoutReady)

      dut.io.southDin.poke(southDin)
      dut.io.southDinValid.poke(southDinValid)
      dut.io.southDoutReady.poke(southDoutReady)

      dut.io.westDin.poke(westDin)
      dut.io.westDinValid.poke(westDinValid)
      dut.io.westDoutReady.poke(westDoutReady)

      ////////////////////////////////////////////////////////////////////////
      ////////
      ////////
      ////////
      //////// Feedback Loop
      ////////
      ////////
      ////////
      ////////
      ////////////////////////////////////////////////////////////////////////

      // **** Test case: SUM without feedback loop
      var configBits = "b0000000000011110110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001000000000000000_1000_0000_0000_0000_0000_0000_0011".U
      var catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      //dut.io.northDin.poke(1.S)
      //dut.io.westDin.poke(1.S)
      for (i <- 1 until 16) {
        dut.io.northDin.poke(i.S)
        dut.io.westDin.poke((i).S)
        dut.io.northDinValid.poke(true.B)
        dut.io.westDinValid.poke(true.B)
        dut.clock.step(1)
        dut.io.northDinValid.poke(false.B)
        dut.io.westDinValid.poke(false.B)
        dut.clock.step(10)
      }
      dut.io.northDin.poke(0.S)
      dut.io.westDin.poke(0.S)
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }

      // **** Test case: Mul
      configBits = "b01000000000001101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000110000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      dut.io.northDin.poke(2.S)
      dut.io.westDin.poke(1.S)

      for (i <- 1 until 27) {
        //dut.io.northDin.poke(i.S)
        //dut.io.westDin.poke((i+1).S)
        dut.clock.step(1)
      }
      dut.io.northDin.poke(0.S)
      dut.io.westDin.poke(0.S)
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }

      // **** Test case: Sub
      configBits = "b01000000000111101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000001010000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      dut.io.northDin.poke(1.S)
      dut.io.westDin.poke(124.S)

      for (i <- 1 until 123) {
        dut.clock.step(1)
      }
      dut.io.northDin.poke(0.S)
      dut.io.westDin.poke(0.S)
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }

      // **** Test case: Shift left (logic)
      //////////// North: din_2, West: din_1
      configBits = "b01000000000001111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000001110000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      dut.io.westDin.poke(1.S)
      dut.io.northDin.poke(1.S)
      for (i <- 1 until 31) {
          dut.clock.step(1)
      }
      dut.io.westDin.poke(0.S)
      dut.io.northDin.poke(0.S)
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
          dut.clock.step(1)
      }

      // **** Test case: Shift left (arithmetic) ????? or Right ?

      configBits = "b01000000000111101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010010000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      for (i <- 1 until numOfTests) {
        dut.io.westDin.poke((-(i*4)).S)
        dut.io.northDin.poke((i-1).S)
        dut.clock.step(1)
      }
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }

      // **** Test case: Shift right (logic)

      configBits = "b01000000000111101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010110000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      dut.io.northDin.poke(1.S)
      for (i <- 1 until numOfTests) {
        dut.io.westDin.poke((-(i*2)).S)
        dut.io.northDin.poke((i-1).S)
        dut.clock.step(1)
      }

      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }

      // **** Test case: AND
      configBits = "b01000000000111101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000011010000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      for (i <- 1 until numOfTests) {
        dut.io.northDin.poke(i.S)
        if (i % 2 == 0) {
            dut.io.westDin.poke((i+1).S)
        } else {
            dut.io.westDin.poke((i+2).S)
        }
        dut.clock.step(1)
      }
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }

      // **** Test case: OR
      configBits = "b01000000000111101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000011110000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      for (i <- 1 until numOfTests) {
        dut.io.northDin.poke(i.S)
        if (i % 2 == 0) {
            dut.io.westDin.poke((i+1).S)
        } else {
            dut.io.westDin.poke((i+2).S)
        }
        dut.clock.step(1)
      }
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }
      // **** Test case: XOR
      configBits = "b01000000000111101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000100010000000000000001000000000000000000000000011".U
      catchConfig = true.B
      // Configuration
      dut.io.catchConfig.poke(catchConfig)
      dut.io.catchConfig.poke(true.B)
      dut.io.configBits.poke(configBits)
      dut.clock.step(1)
      dut.clock.step(1)
      dut.io.catchConfig.poke(false.B)
      configBits = 0.U
      dut.io.configBits.poke(configBits)

      // Inserting Data
      dut.io.northDinValid.poke(true.B)
      dut.io.westDinValid.poke(true.B)

      for (i <- 1 until numOfTests) {
        dut.io.northDin.poke(i.S)
        if (i % 2 == 0) {
            dut.io.westDin.poke((i+1).S)
        } else {
            dut.io.westDin.poke((i+2).S)
        }
        dut.clock.step(1)
      }
      dut.io.northDinValid.poke(false.B)
      dut.io.westDinValid.poke(false.B)
      for (i <- 0 until INSTRUCTION_CHANGE_DELAY) {
        dut.clock.step(1)
      }
    }
  }
}


//> using scala "2.13.14"
//> using dep "com.github.rameloni::tywaves-chisel-api:0.4.2-SNAPSHOT"
//> using dep "org.chipsalliance::chisel:6.4.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.4.0"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Xfatal-warnings", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
//> using dep "org.scalatest::scalatest:3.2.19"

import chisel3._
import circt.stage._
import chisel3.util._
import tywaves.simulator._
import tywaves.simulator.simulatorSettings._

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

class DetectTwoOnes extends Module {
  val io = IO(new Bundle {
    val in      = Input(Bool())
    val out     = Output(Bool())
  })

  object State extends ChiselEnum { val sNone, sOne1, sTwo1s = Value }
  val state = RegInit(State.sNone)

  // Tmp signal 1
  val isOne = Wire(Bool())
  isOne := io.in
  // Tmp signal 2
  val willBeTwo1s = io.in && (state === State.sOne1 || state === State.sTwo1s)

  io.out := (state === State.sTwo1s)

  switch(state) {
    is(State.sNone) { when(isOne) { state := State.sOne1 } }
    is(State.sOne1) {

    }
    is(State.sTwo1s) { when(!isOne) { state := State.sNone } }
  }
}

class DetectTwoOnesTest extends AnyFunSpec with Matchers {

    import TywavesSimulator._
    def runTest(fsm: DetectTwoOnes) = {
        // Inputs and expected results
        val inputs   = Seq(0, 0, 1, 0, 1, 1, 0, 1, 1, 1)
        val expected = Seq(0, 0, 0, 0, 0, 1, 0, 0, 1, 1)

        // Reset
        fsm.io.in.poke(0)
        fsm.clock.step(1)

        for (i <- inputs.indices) {
            fsm.io.in.poke(inputs(i))
            fsm.clock.step(1)
            //	c.clock.getStepCount
            fsm.io.out.expect(expected(i))
            //	System.out.println(s"In: ${inputs(i)}, out: ${expected(i)}")
        }
    }

  describe("TywavesSimulator") {
    it("runs DetectTwoOnes correctly") {
      val chiselStage = new ChiselStage(true)

      chiselStage.execute(
        args = Array("--target", "chirrtl"),
        annotations = Seq(
          chisel3.stage.ChiselGeneratorAnnotation(() => new DetectTwoOnes()),
          FirtoolOption("-g"),
          FirtoolOption("--emit-hgldd"),
        ),
      )

      simulate(new DetectTwoOnes(), Seq(VcdTrace, WithTywavesWaveformsGo(true)), simName = "runs_detect2ones") {
        fsm =>
            runTest(fsm)
      }

    }
  }

}

when(isOne) { state := State.sTwo1s }.otherwise { state := State.sNone }


//> using scala "2.13.14"
//> using dep "com.github.rameloni::tywaves-chisel-api:0.4.2-SNAPSHOT"
//> using dep "org.chipsalliance::chisel:6.4.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.4.0"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Xfatal-warnings", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
//> using dep "org.scalatest::scalatest:3.2.18"

// DO NOT EDIT THE ORTHER OF THESE IMPORTS (it will be solved in future versions)
import chisel3._
import tywaves.simulator._
import tywaves.simulator.ParametricSimulator._
import tywaves.simulator.simulatorSettings._
import circt.stage.ChiselStage
// _root_ disambiguates from package chisel3.util.circt if user imports chisel3.util._
//import _root_.circt.stage.ChiselStage
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

/** A simple module useful for testing Chisel generation and testing */
class GCD extends Module {
  val io = IO(new Bundle {
    val a             = Input(UInt(32.W))
    val b             = Input(UInt(32.W))
    val loadValues    = Input(Bool())
    val result        = Output(UInt(32.W))
    val resultIsValid = Output(Bool())
  })

  val x = Reg(UInt(32.W))
  val y = Reg(UInt(32.W))

  val tmp = x + y
  when(x > y) {
    val myTmpVal: UInt = x -% y
    x := myTmpVal
  }.otherwise {
    val myTmpVal = y -% x
    val myTmpVal3 = Wire(UInt(32.W))
    myTmpVal3 := myTmpVal
    y         := y -% x
  }

  io.result        := x
  io.resultIsValid := y === 0.U
}

class GCDTest extends AnyFunSpec with Matchers {
  describe("ParametricSimulator") {
    it("runs GCD correctly") {

      val chiselStage = new ChiselStage(true)
      chiselStage.execute(
        args = Array("--target", "chirrtl"),
        annotations = Seq(
          chisel3.stage.ChiselGeneratorAnnotation(() => new GCD()),
          circt.stage.FirtoolOption("-g"),
          circt.stage.FirtoolOption("--emit-hgldd"),
        ),
      )
      println("Hello, world!")
      simulate(new GCD(), Seq(VcdTrace, SaveWorkdirFile("gcdWorkdir"))) { gcd =>
        gcd.io.a.poke(24.U)
        gcd.io.b.poke(36.U)
        gcd.io.loadValues.poke(1.B)
        gcd.clock.step()
        gcd.io.loadValues.poke(0.B)
        gcd.clock.stepUntil(sentinelPort = gcd.io.resultIsValid, sentinelValue = 1, maxCycles = 10)
        gcd.io.resultIsValid.expect(true.B)
        gcd.io.result.expect(12)
      }
    }
  }

  describe("TywavesSimulator") {
    it("runs GCD correctly") {
      import TywavesSimulator._

      simulate(new GCD(), Seq(VcdTrace, WithTywavesWaveforms(true)), simName = "runs_GCD_correctly_launch_tywaves") {
        gcd =>
          gcd.io.a.poke(24.U)
          gcd.io.b.poke(36.U)
          gcd.io.loadValues.poke(1.B)
          gcd.clock.step()
          gcd.io.loadValues.poke(0.B)
          gcd.clock.stepUntil(sentinelPort = gcd.io.resultIsValid, sentinelValue = 1, maxCycles = 10)
          gcd.io.resultIsValid.expect(true.B)
          gcd.io.result.expect(12)
          gcd.io.a.poke(24.U)
          gcd.io.b.poke(72.U)
          gcd.reset.poke(true.B)
          gcd.io.loadValues.poke(1.B)
          gcd.clock.step()
          gcd.io.loadValues.poke(0.B)
          gcd.reset.poke(false.B)
          gcd.clock.stepUntil(sentinelPort = gcd.io.resultIsValid, sentinelValue = 1, maxCycles = 10)
          gcd.io.resultIsValid.expect(true.B)
          gcd.io.result.expect(24)
      }
    }
  }

}

when(io.loadValues) { x := io.a; y := io.b }


//> using scala "2.13.14"
//> using dep "com.github.rameloni::tywaves-chisel-api:0.4.2-SNAPSHOT"
//> using dep "org.chipsalliance::chisel:6.4.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.4.0"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Xfatal-warnings", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
//> using dep "org.scalatest::scalatest:3.2.18"

// DO NOT EDIT THE ORTHER OF THESE IMPORTS (it will be solved in future versions)
import tywaves.simulator._
import tywaves.simulator.simulatorSettings._
import chisel3._
// _root_ disambiguates from package chisel3.util.circt if user imports chisel3.util._
//import _root_.circt.stage.ChiselStage
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import circt.stage.ChiselStage

// Enum of possible states
object MyFSMStates extends ChiselEnum {
  val IDLE, StateA, StateB, END = Value
}

class FSM extends Bundle {

  val inputState = IO(Input(MyFSMStates()))
  val state      = RegInit(MyFSMStates.IDLE)
  val stateNxt   = WireInit(MyFSMStates.IDLE)

  val endConst = WireInit(MyFSMStates.END)

  when(state === MyFSMStates.IDLE) {
    stateNxt := MyFSMStates.StateA
  }.elsewhen(state === MyFSMStates.StateA) {
    stateNxt := MyFSMStates.StateB
  }.elsewhen(state === MyFSMStates.StateB) {
    stateNxt := MyFSMStates.END
  }.otherwise {
    stateNxt := MyFSMStates.IDLE
  }

  when(inputState === MyFSMStates.END) {
    state := MyFSMStates.IDLE
  }.otherwise {
    state := stateNxt
  }
}

class MyFSM extends Module {
  val fsm = new FSM

  val io = IO(new Bundle {
    val inputState = Input(MyFSMStates())
  })

  val aConstBundle = Wire(new Bundle {
    val bit = Bool()
    val bv = UInt(32.W)
    val subbundle = new Bundle {
      val x = SInt(3.W)
    }
  })
  aConstBundle.bit := 1.B
  aConstBundle.bv := 34.U
  aConstBundle.subbundle.x := 2.S
}

class MyFSMTest extends AnyFunSpec with Matchers {

  describe("TywavesSimulator") {
    it("runs MyFSM correctly") {
      import TywavesSimulator._
      val chiselStage = new ChiselStage(true)

      chiselStage.execute(
        args = Array("--target", "chirrtl"),
        annotations = Seq(
          chisel3.stage.ChiselGeneratorAnnotation(() => new MyFSM()),
          circt.stage.FirtoolOption("-g"),
          circt.stage.FirtoolOption("--emit-hgldd"),
        ),
      )
      simulate(new MyFSM(), Seq(VcdTrace, WithTywavesWaveformsGo(true)), simName = "runs_MYFSM_correctly_launch_tywaves_and_go") {
        fsm =>
          fsm.clock.step(10)
          fsm.clock.step(10)
      }
      simulate(new MyFSM(), Seq(VcdTrace, WithTywavesWaveforms(true)), simName = "runs_MYFSM_correctly_launch_tywaves") {
        fsm =>

          fsm.clock.step(10)
          fsm.io.inputState.poke(MyFSMStates.StateA)
          fsm.clock.step(10)


//> using scala "2.13.14"
//> using dep "com.github.rameloni::tywaves-chisel-api:0.4.2-SNAPSHOT"
//> using dep "nl.tudelft::tydi-chisel::0.1.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.4.0"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Xfatal-warnings", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
//> using dep "org.scalatest::scalatest:3.2.18"

// DO NOT EDIT THE ORTHER OF THESE IMPORTS (it will be solved in future versions)
import tywaves.simulator._
import tywaves.simulator.ParametricSimulator._
import tywaves.simulator.simulatorSettings._
import chisel3._

// _root_ disambiguates from package chisel3.util.circt if user imports chisel3.util._
//import _root_.circt.stage.ChiselStage
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

import nl.tudelft.tydi_chisel._
import chisel3.util.Counter

object MyTypes {

  /** Bit(64) type, defined in pipelineSimple_types */
  def UInt_64_t = UInt(64.W)

  assert(this.UInt_64_t.getWidth == 64)

  /** Bit(64) type, defined in pipelineSimple_types */
  def generated_0_7_d3p7qsW3_29 = SInt(64.W)

  assert(this.generated_0_7_d3p7qsW3_29.getWidth == 64)
}

/**
 * Group element, defined in pipelineSimple_types. A composite type (like a
 * struct) that contains a value associated with a timestamp.
 */
class NumberGroup extends Group {
  val time  = MyTypes.UInt_64_t
  val value = MyTypes.generated_0_7_d3p7qsW3_29
}

/**
 * Group element, defined in pipelineSimple_types. A composite type (like a
 * struct) that represents the stats of the implemented algorithm.
 */
class Stats extends Group {
  val average = (new UInt_64_t)
  val max     = MyTypes.UInt_64_t
  val min     = MyTypes.UInt_64_t
  val sum     = MyTypes.UInt_64_t
}

/** Stream, defined in pipelineSimple_types. */
class Stats_stream(private val e: TydiEl = new Stats, n: Int = 1, d: Int = 1, c: Int = 1, r: Boolean = false, u: TydiEl = Null())
    extends PhysicalStreamDetailed(e = new Stats, n = n, d = d, c = c, r = r, u = u)

object Stats_stream {
  def apply(): Stats_stream = Wire(new Stats_stream())
}

/** Stream, defined in pipelineSimple_types. */
class Generated_0_36_xeHH4woS_24
    extends PhysicalStreamDetailed(e = new NumberGroup, n = 1, d = 2, c = 1, r = false, u = Null())

object Generated_0_36_xeHH4woS_24 {
  def apply(): Generated_0_36_xeHH4woS_24 = Wire(new Generated_0_36_xeHH4woS_24())
}

/** Stream, defined in pipelineSimple_types. */
class UInt_64_t_stream extends PhysicalStreamDetailed(e=new UInt_64_t, n=1, d=1, c=1, r=false, u=Null())

object UInt_64_t_stream {
  def apply(): UInt_64_t_stream = Wire(new UInt_64_t_stream())
}

/** Bit(64), defined in pipelineSimple_types. */
class UInt_64_t extends BitsEl(64.W)

/** Bit(64), defined in pipelineSimple_types. */

/**
 * Streamlet, defined in pipelineSimple. Top level interface.
 */
class PipelineSimple_interface extends TydiModule {

  /** Stream of [[in]] with input direction. */
  val inStream = Generated_0_36_xeHH4woS_24().flip

  /** IO of [[inStream]] with input direction. */
  val in = inStream.toPhysical

  /** Stream of [[out]] with output direction. */
  val outStream = Stats_stream()

  /** IO of [[outStream]] with output direction. */
  val out = outStream.toPhysical

  val in2Stream = UInt_64_t_stream().flip
  /** IO of [[in2Stream]] with input direction. */
  val in2: PhysicalStream = in2Stream.toPhysical
}

/**
 * Streamlet, defined in pipelineSimple. Interface for the agg function.
 */
class Reducer_interface extends TydiModule {

  /** Stream of [[in]] with input direction. */
  val inStream = Generated_0_36_xeHH4woS_24().flip

  /** IO of [[inStream]] with input direction. */
  val in = inStream.toPhysical

  /** Stream of [[out]] with output direction. */
  val outStream = Stats_stream()

  /** IO of [[outStream]] with output direction. */
  val out = outStream.toPhysical
}

/**
 * Streamlet, defined in pipelineSimple. Interface for the non negative filter:
 * df.filter(col("value") >= 0).
 */
class Instance_NonNegativeFilter_interface_RefToVargenerated_0_36_KLOZ7sts_25_1 extends TydiModule {

  /** Stream of [[in]] with input direction. */
  val inStream = Generated_0_36_xeHH4woS_24().flip

  /** IO of [[inStream]] with input direction. */
  val in = inStream.toPhysical

  /** Stream of [[out]] with output direction. */
  val outStream = Generated_0_36_xeHH4woS_24()

  /** IO of [[outStream]] with output direction. */
  val out = outStream.toPhysical
}

/**
 * Implementation, defined in pipelineSimple. Implementation of
 * df.filter(col("value") >= 0).
 */
class NonNegativeFilter extends Instance_NonNegativeFilter_interface_RefToVargenerated_0_36_KLOZ7sts_25_1 {
  // Filtered only if the value is non-negative
  // inStream.valid tells us if the input is valid
  private val canPass: Bool = inStream.el.value >= 0.S && inStream.valid

  // Connect inStream to outStream
  // This is equivalent of connecting al the fields of the two streams
  // Every future assignment will overwrite the previous one
  outStream := inStream

  // Always ready to accept input
  inStream.ready := true.B

  // if (canPass) then { it can go out } else { it is not forwarded }
  outStream.valid := canPass && outStream.ready
  outStream.strb  := inStream.strb(0) && canPass

  // All the other signals are forwarded
  //  outStream.stai := inStream.stai
  //  outStream.endi := inStream.endi
  //  outStream.last := inStream.last
  //  outStream.el := inStream.el
}

/**
 * Implementation, defined in pipelineSimple. Top level implementation. It
 * instantiates the subcomponents and connects them together.
 */
class PipelineSimple extends PipelineSimple_interface {
  inStream  := DontCare
  outStream := DontCare // Probably not used
  in2Stream := DontCare

  // Modules
  private val filter  = Module(new NonNegativeFilter)
  private val reducer = Module(new Reducer)

  // Connections
  filter.in  := in          // This will overload the inStream := in
  reducer.in := filter.out
  out        := reducer.out // This will overload the out := outStream -> that's why outStream is not used
}

/**
 * Implementation, defined in pipelineSimple. Implementation of the agg
 * function.
 */
class Reducer extends Reducer_interface {
  // Set the data width to 64 bits, such as the [[MyTypes]] types
  private val dataWidth = 64.W

  // Computing min and max
  private val maxVal: BigInt = BigInt(Long.MaxValue)        // Must work with BigInt or we get an overflow
  private val cMin:   UInt   = RegInit(maxVal.U(dataWidth)) // REG for the min value
  private val cMax:   UInt   = RegInit(0.U(dataWidth))      // REG for the max value
  // Computing the sum
  private val cSum: UInt = RegInit(0.U(dataWidth)) // REG for the sum
  // Computing the avg
  private val nValidSamples: Counter = Counter(Int.MaxValue) // The number of samples received (valid && strb(0))
  private val nSamples:      Counter = Counter(Int.MaxValue) // The number of samples received (valid)

  // Set the streams IN ready and OUT valid signals
  // inReady = if (maxVal > 0) { true.B } else { false.B}
  inStream.ready := (if (maxVal > 0) true.B else false.B)
  // inStream.ready := true.B

  // The output is valid only if we have received at least one sample
  outStream.valid := nSamples.value > 0.U

  // When a value is received
  when(inStream.valid) {
    // Get the value from th input stream
    val value = inStream.el.value.asUInt
    nSamples.inc()

    // Check the strb line and perform the updates
    when(inStream.strb(0)) {
      cMin := cMin min value
      cMax := cMax max value
      cSum := cSum + value
      nValidSamples.inc()
    }
  }
  // Set the output stream
  outStream.el.sum     := cSum
  outStream.el.min     := cMin
  outStream.el.max     := cMax
  outStream.el.average.value := Mux(nValidSamples.value > 0.U, cSum / nValidSamples.value, 0.U)

  // Set the output stream control signals:
  // they are fixed since the sum, min, max and average are updated every cycle
  // and they hold the same value if no change is performed
  outStream.strb    := 1.U
  outStream.stai    := 0.U
  outStream.endi    := 1.U
  outStream.last(0) := inStream.last(0)
}

class PipelineSimpleTest extends AnyFunSpec with Matchers {
  describe("ParametricSimulator") {
    it("runs Pipeline Simple correctly") {
      simulate(new PipelineSimple, Seq(VcdTrace, SaveWorkdirFile("aaa"))) {
        dut => dut.clock.step()
      }
    }
  }

  describe("TywavesSimulator") {
    it("runs Pipeline Simple correctly") {
      import TywavesSimulator._

      simulate(
        new PipelineSimple(),
        Seq(VcdTrace, WithTywavesWaveforms(true)),
        simName = "runs_GCD_correctly_launch_tywaves",
      ) {
        dut => dut.clock.step()

      }
    }
  }

}

class Generated_0_7_d3p7qsW3_29 extends BitsEl(64.W)


//> using scala "2.13.14"
//> using dep "com.github.rameloni::tywaves-chisel-api:0.4.2-SNAPSHOT"
//> using dep "nl.tudelft::tydi-chisel::0.1.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.4.0"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Xfatal-warnings", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
//> using dep "org.scalatest::scalatest:3.2.18"

// DO NOT EDIT THE ORTHER OF THESE IMPORTS (it will be solved in future versions)
import tywaves.simulator._
import tywaves.simulator.ParametricSimulator._
import tywaves.simulator.simulatorSettings._
import chisel3._

// _root_ disambiguates from package chisel3.util.circt if user imports chisel3.util._
//import _root_.circt.stage.ChiselStage
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

import nl.tudelft.tydi_chisel._
import chisel3.util.Counter

object MyTypes {

  /** Bit(64) type, defined in pipelineSimple_types */

  assert(this.generated_0_7_Tc5SbYQz_27.getWidth == 64)

  /** Bit(64) type, defined in pipelineSimple_types */
  def generated_0_7_d3p7qsW3_29 = SInt(64.W)

  assert(this.generated_0_7_d3p7qsW3_29.getWidth == 64)
}

/**
 * Group element, defined in pipelineSimple_types. A composite type (like a
 * struct) that contains a value associated with a timestamp.
 */
class NumberGroup extends Group {
  val time  = MyTypes.generated_0_7_Tc5SbYQz_27
  val value = MyTypes.generated_0_7_d3p7qsW3_29
}

/**
 * Group element, defined in pipelineSimple_types. A composite type (like a
 * struct) that represents the stats of the implemented algorithm.
 */
class Stats extends Group {
  val average = MyTypes.generated_0_7_Tc5SbYQz_27
  val max     = MyTypes.generated_0_7_Tc5SbYQz_27
  val min     = MyTypes.generated_0_7_Tc5SbYQz_27
  val sum     = MyTypes.generated_0_7_Tc5SbYQz_27
}

/** Stream, defined in pipelineSimple_types. */
class Generated_0_30_soHLkh1p_30
    extends PhysicalStreamDetailed(e = new Stats, n = 1, d = 1, c = 1, r = false, u = Null())

object Generated_0_30_soHLkh1p_30 {
  def apply(): Generated_0_30_soHLkh1p_30 = Wire(new Generated_0_30_soHLkh1p_30())
}

/** Stream, defined in pipelineSimple_types. */
class Generated_0_36_xeHH4woS_24
    extends PhysicalStreamDetailed(e = new NumberGroup, n = 1, d = 2, c = 1, r = false, u = Null())

object Generated_0_36_xeHH4woS_24 {
  def apply(): Generated_0_36_xeHH4woS_24 = Wire(new Generated_0_36_xeHH4woS_24())
}

/** Bit(64), defined in pipelineSimple_types. */
class Generated_0_7_Tc5SbYQz_27 extends BitsEl(64.W)

/** Bit(64), defined in pipelineSimple_types. */
class Generated_0_7_d3p7qsW3_29 extends BitsEl(64.W)

/**
 * Streamlet, defined in pipelineSimple. Top level interface.
 */
class PipelineSimple_interface extends TydiModule {

  /** Stream of [[in]] with input direction. */
  val inStream = Generated_0_36_xeHH4woS_24().flip

  /** IO of [[inStream]] with input direction. */
  val in = inStream.toPhysical

  /** Stream of [[out]] with output direction. */
  val outStream = Generated_0_30_soHLkh1p_30()

  /** IO of [[outStream]] with output direction. */
  val out = outStream.toPhysical
}

/**
 * Streamlet, defined in pipelineSimple. Interface for the agg function.
 */
class Reducer_interface extends TydiModule {

  /** Stream of [[in]] with input direction. */
  val inStream = Generated_0_36_xeHH4woS_24().flip

  /** IO of [[inStream]] with input direction. */
  val in = inStream.toPhysical

  /** Stream of [[out]] with output direction. */
  val outStream = Generated_0_30_soHLkh1p_30()

  /** IO of [[outStream]] with output direction. */
  val out = outStream.toPhysical
}

/**
 * Streamlet, defined in pipelineSimple. Interface for the non negative filter:
 * df.filter(col("value") >= 0).
 */
class Instance_NonNegativeFilter_interface_RefToVargenerated_0_36_KLOZ7sts_25_1 extends TydiModule {

  /** Stream of [[in]] with input direction. */
  val inStream = Generated_0_36_xeHH4woS_24().flip

  /** IO of [[inStream]] with input direction. */
  val in = inStream.toPhysical

  /** Stream of [[out]] with output direction. */
  val outStream = Generated_0_36_xeHH4woS_24()

  /** IO of [[outStream]] with output direction. */
  val out = outStream.toPhysical
}

/**
 * Implementation, defined in pipelineSimple. Implementation of
 * df.filter(col("value") >= 0).
 */
class NonNegativeFilter extends Instance_NonNegativeFilter_interface_RefToVargenerated_0_36_KLOZ7sts_25_1 {
  // Filtered only if the value is non-negative
  // inStream.valid tells us if the input is valid
  private val canPass: Bool = inStream.el.value >= 0.S && inStream.valid

  // Connect inStream to outStream
  // This is equivalent of connecting al the fields of the two streams
  // Every future assignment will overwrite the previous one
  outStream := inStream

  // Always ready to accept input
  inStream.ready := true.B

  // if (canPass) then { it can go out } else { it is not forwarded }
  outStream.valid := canPass && outStream.ready
  outStream.strb  := inStream.strb(0) && canPass

  // All the other signals are forwarded
  //  outStream.stai := inStream.stai
  //  outStream.endi := inStream.endi
  //  outStream.last := inStream.last
  //  outStream.el := inStream.el
}

/**
 * Implementation, defined in pipelineSimple. Top level implementation. It
 * instantiates the subcomponents and connects them together.
 */
class PipelineSimple extends PipelineSimple_interface {
  inStream  := DontCare
  outStream := DontCare // Probably not used

  // Modules
  private val filter  = Module(new NonNegativeFilter)
  private val reducer = Module(new Reducer)

  // Connections
  filter.in  := in          // This will overload the inStream := in
  reducer.in := filter.out
  out        := reducer.out // This will overload the out := outStream -> that's why outStream is not used
}

/**
 * Implementation, defined in pipelineSimple. Implementation of the agg
 * function.
 */
class Reducer extends Reducer_interface {
  // Set the data width to 64 bits, such as the [[MyTypes]] types
  private val dataWidth = 64.W

  // Computing min and max
  private val maxVal: BigInt = BigInt(Long.MaxValue)        // Must work with BigInt or we get an overflow
  private val cMin:   UInt   = RegInit(maxVal.U(dataWidth)) // REG for the min value
  private val cMax:   UInt   = RegInit(0.U(dataWidth))      // REG for the max value
  // Computing the sum
  private val cSum: UInt = RegInit(0.U(dataWidth)) // REG for the sum
  // Computing the avg
  private val nValidSamples: Counter = Counter(Int.MaxValue) // The number of samples received (valid && strb(0))
  private val nSamples:      Counter = Counter(Int.MaxValue) // The number of samples received (valid)

  // Set the streams IN ready and OUT valid signals
  // inReady = if (maxVal > 0) { true.B } else { false.B}
  inStream.ready := (if (maxVal > 0) true.B else false.B)
  // inStream.ready := true.B

  // The output is valid only if we have received at least one sample
  outStream.valid := nSamples.value > 0.U

  // When a value is received
  when(inStream.valid) {
    // Get the value from th input stream
    val value = inStream.el.value.asUInt
    nSamples.inc()

    // Check the strb line and perform the updates
    when(inStream.strb(0)) {
      cMin := cMin min value
      cMax := cMax max value
      cSum := cSum + value
      nValidSamples.inc()
    }
  }
  // Set the output stream
  outStream.el.sum     := cSum
  outStream.el.min     := cMin
  outStream.el.max     := cMax
  outStream.el.average := Mux(nValidSamples.value > 0.U, cSum / nValidSamples.value, 0.U)

  // Set the output stream control signals:
  // they are fixed since the sum, min, max and average are updated every cycle
  // and they hold the same value if no change is performed
  outStream.strb    := 1.U
  outStream.stai    := 0.U
  outStream.endi    := 1.U
  outStream.last(0) := inStream.last(0)
}

class PipelineSimpleTest extends AnyFunSpec with Matchers {
  describe("ParametricSimulator") {
    it("runs Pipeline Simple correctly") {
      simulate(new PipelineSimple, Seq(VcdTrace, SaveWorkdirFile("pipelineSimpleWorkdir"))) {
        dut => dut.clock.step()
      }
    }
  }

  describe("TywavesSimulator") {
    it("runs Pipeline Simple correctly") {
      import TywavesSimulator._

      simulate(
        new PipelineSimple(),
        Seq(VcdTrace, WithTywavesWaveforms(true)),
        simName = "runs_GCD_correctly_launch_tywaves",
      ) {
        dut => dut.clock.step()

      }
    }
  }

}

def generated_0_7_Tc5SbYQz_27 = UInt(64.W)


package tywaves.circuitmapper

import chisel3.RawModule
import chisel3.stage.ChiselGeneratorAnnotation
import tywaves.BuildInfo.firtoolBinaryPath

/** This case class is used to store the top module name */
private[tywaves] case class TopModuleName(private[circuitmapper] var name: Option[String])

/** This object contains methods to create debug information from firtool */
private[tywaves] object TypedConverter {
  private lazy val chiselStage = new circt.stage.ChiselStage(withDebug = true)

  private val chiselStageBaseArgs =
    Array("--target", "systemverilog", "--split-verilog", "--firtool-binary-path", firtoolBinaryPath)

  val firtoolBaseArgs: Seq[String] =
    Seq("-O=debug", "-g", "--emit-hgldd", "--split-verilog") // Run in debug mode compiled with optimizations
  // Directories where the debug information is stored
  private var hglddDebugDir   = "hgldd/debug"
  private var hglddWithOptDir = "hgldd/opt" // TODO: remove
  private var workingDir: Option[String] = None
  private val topModuleName = TopModuleName(None)

  // Default firtool options encoded as annotations for ChiselStage
  private val defaultFirtoolOptAnno = createFirtoolOptions(firtoolBaseArgs)
  //      "-disable-annotation-unknown",
  //      "--hgldd-output-prefix=<path>",
  /*,"--output-final-mlir=WORK.mlir"*/

  // Map any sequence of string into FirtoolOptions
  private def createFirtoolOptions(args: Seq[String]) = args.map(circt.stage.FirtoolOption)

  /**
   * Create debug information from firtool by using the
   * [[circt.stage.ChiselStage]]
   */
  def createDebugInfoHgldd[T <: RawModule](
      generateModule:        () => T,
      workingDir:            String = "workingDir",
      additionalFirtoolArgs: Seq[String],
  ): Unit = {
    this.workingDir = Some(workingDir)
    hglddWithOptDir = workingDir + "/" + hglddWithOptDir
    hglddDebugDir = workingDir + "/" + hglddDebugDir
    //    hglddDebugDir = workingDir + "/" + "support-artifacts"

    // Annotations for ChiselStage
    val annotations = Seq(ChiselGeneratorAnnotation(generateModule)) ++ defaultFirtoolOptAnno

    // Run without debug mode: TODO check it is actually not needed
    //    chiselStage.execute(
    //      chiselStageBaseArgs ++ Array("--target-dir", hglddWithOptDir),
    //      annotations,
    //    ) // execute returns the passThrough annotations in CIRCT transform stage

    val finalAnno = chiselStage.execute(
      chiselStageBaseArgs ++ Array("--target-dir", hglddDebugDir),
      createFirtoolOptions(additionalFirtoolArgs) ++ annotations,
    ) // execute returns the passThrough annotations in CIRCT transform stage

    // Get the module name
    topModuleName.name = finalAnno.collectFirst {
      case chisel3.stage.ChiselCircuitAnnotation(circuit) => circuit.name
    }
  }

  /** Get the directory where the debug information is stored */
  def getDebugInfoDir(gOpt: Boolean): String =
    if (gOpt) hglddDebugDir
    else hglddWithOptDir

  /**
   * Return the working directory used in the last call of
   * [[createDebugInfoHgldd]]
   */
  def getWorkingDir: Option[String] = workingDir

  /**
   * Return the top module name of the last circuit used in the last call of
   * [[createDebugInfoHgldd]]
   */

def getTopModuleName: Option[String] = topModuleName.name


package tywaves.simulator

import chisel3.RawModule
import chisel3.simulator.SingleBackendSimulator
import chisel3.simulator.PeekPokeAPI
import svsim._

import java.nio.file.Files
import java.time.{LocalDateTime, format}

/**
 * The interface object to the simulation.
 *
 * It uses [[svsim]] and [[PeekPokeAPI]] to run a simulation.
 */
object ParametricSimulator extends PeekPokeAPI {

  /** Use this method to run a simulations */
  def simulate[T <: RawModule](
      module:   => T,
      settings: Seq[SimulatorSettings] = Seq(),
      simName:  String = "defaultSimulation",
  )(body: T => Unit): Unit =
    (new ParametricSimulator).simulate(module, settings, simName)(body)
}

/**
 * A simulator that can be customized by passing some parameters through a list
 * of [[SimulatorSettings]]. It offers the possibility to:
 *   - output a trace vcd file
 *   - store the files generated during the simulation
 */
class ParametricSimulator {

  // Settings variable of the simulator
  private var _backendCompileSettings = verilator.Backend.CompilationSettings()
  private val _testRunDir             = "test_run_dir"
  private var _moduleDutName          = ""
  private val _simulatorName          = getClass.getSimpleName.stripSuffix("$")
  private var _simName                = "defaultSimulationName"

  // Working directory and workspace
  private var _workdirFinalFile: Option[String] = None
  private var _workdir:          Option[String] = None // Initialized when makeSimulator is called
  protected def wantedWorkspacePath: String =
    if (_workdirFinalFile.isDefined)
      Seq(_testRunDir, _moduleDutName, _simulatorName, _simName, _workdirFinalFile.get).mkString("/")
    else
      Seq(_testRunDir, _moduleDutName, _simulatorName, _simName).mkString("/")

  // Traces
  private var _traceExtension:  Option[String] = None
  private var _traceWantedName: String         = "trace"
  private var _finalTracePath:  Option[String] = None

  def finalTracePath: Option[String] = _finalTracePath
  private def _emittedTraceRelPath: Option[String] =
    if (_workdir.isDefined && _traceExtension.isDefined)
      Some(Seq(_workdir.get, s"trace.${_traceExtension.get}").mkString("/"))
    else None

  private var _firtoolArgs: Seq[String] = Seq()

  /** Launch and execute a simulation given a list of [[SimulatorSettings]]. */
  def simulate[T <: RawModule](
      module:   => T,
      settings: Seq[SimulatorSettings] = Seq(),
      simName:  String,
  )(body: T => Unit): Unit = {

    // Set the initial settings before the simulation: i.e. backend compile settings
    setInitialSettings(settings)

    // Create a new simulator
    val simulator = makeSimulator
    simulator
      .simulate(module) { simulatedModule =>
        // Set the controller settings: i.e. enable trace
        setControllerSettings(simulatedModule.controller, settings)

        // Update the wanted workspace path
        _moduleDutName = simulatedModule.wrapped.name
        _simName = simName

        // Launch the actual simulation and return the result
        body(simulatedModule.wrapped)
      }
      .result

    // Cleanup the simulation after the execution
    simulator.cleanup()
  }

  /**
   * Set settings before the simulation starts. It sets the initial settings
   * such as the backend compile settings, trace style, etc.
   */
  private def setInitialSettings(settings: Seq[SimulatorSettings]): Unit = {
    settings.foreach {
      case t: TraceSetting =>
        _backendCompileSettings = verilator.Backend.CompilationSettings(
          traceStyle = Some(t.traceStyle),
          outputSplit = None,
          outputSplitCFuncs = None,
          disabledWarnings = Seq(),
          disableFatalExitOnWarnings = false,
        )
        // Set also the extension
        _traceExtension = Some(t.extension)

      case TraceName(name) =>
        if (
          !settings.exists {
            case _: TraceSetting => true
            case _ => false
          }
        ) throw new Exception("TraceName must be used with TraceSetting")
        if (settings.count(_.isInstanceOf[TraceName]) > 1)
          throw new Exception("TraceName must be used only once")
        _traceWantedName = name

      case SaveWorkspace(name) =>
        if (settings.count(_.isInstanceOf[SaveWorkspace]) > 1)
          throw new Exception("SaveWorkspace must be used only once")
        if (name == "") // If no name is specified -> Current date time as default (gg-mm-ddThh:mm) no millis
          _workdirFinalFile =
            Some(LocalDateTime.now().format(format.DateTimeFormatter.ofPattern("dd_MM_yyyy:HH-mm-ss")))
        else
          _workdirFinalFile = Some(name)

      case FirtoolArgs(args) => _firtoolArgs = args
      case _                 =>
    }

    // If it contains underscore, add it to the trace name
    if (settings.contains(simulatorSettings.VcdTraceWithUnderscore))
      _traceWantedName = s"${_traceWantedName}_underscore"

  }

  /**
   * Set the controller settings. It sets settings such as the trace output
   * enable.
   */
  private def setControllerSettings(controller: Simulation.Controller, settings: Seq[SimulatorSettings]): Unit =
    settings.foreach {
      case _: TraceSetting => controller.setTraceEnabled(true)
      case _ => // Do nothing
    }

  /** Default ParametricSimulator */
  protected class DefaultParametricSimulator(val workspacePath: String, val tag: String)
      extends SingleBackendSimulator[verilator.Backend] {

    val backend:              verilator.Backend = verilator.Backend.initializeFromProcessEnvironment()
    override val firtoolArgs: Seq[String]       = _firtoolArgs

    val backendSpecificCompilationSettings: verilator.Backend.CompilationSettings = _backendCompileSettings
    val commonCompilationSettings: CommonCompilationSettings =
      CommonCompilationSettings(
        optimizationStyle = CommonCompilationSettings.OptimizationStyle.OptimizeForCompilationSpeed,
        availableParallelism = CommonCompilationSettings.AvailableParallelism.UpTo(4),
        defaultTimescale = Some(CommonCompilationSettings.Timescale.FromString("1ms/1ms")),
      )

    /**
     * Cleanup the simulation and move the simulation workspace to the wanted
     * workspace path.
     */
    def cleanup(): Unit = {

      val workDirOld = os.Path(workspacePath)
      val workDir    = os.pwd / os.RelPath(wantedWorkspacePath)

      // Check if the workspace must be saved or not
      if (_workdirFinalFile.isDefined)
        os.copy(workDirOld, workDir, replaceExisting = true, createFolders = true, mergeFolders = true)

      // Rename the wanted trace
      if (_traceExtension.isDefined) {
        val tracePath = workDirOld / os.RelPath(_emittedTraceRelPath.get)
        val wantedTracePath =
          os.pwd / os.RelPath(Seq(wantedWorkspacePath, s"${_traceWantedName}.${_traceExtension.get}").mkString("/"))
        os.copy(tracePath, wantedTracePath, replaceExisting = true, createFolders = true)
        _finalTracePath = Some(wantedTracePath.toString)
      }

    }
  }

  /** Create a new simulator */
  protected def makeSimulator: DefaultParametricSimulator = { // def allows to create a new simulator each tim
    val className = _simulatorName
    val id        = java.lang.management.ManagementFactory.getRuntimeMXBean.getName

    val tmpWorkspacePath = Files.createTempDirectory(s"${className}_${id}_").toString
    val tag              = "default"
    val defaultSimulator = new DefaultParametricSimulator(tmpWorkspacePath, tag = tag)
    _workdir = Some(s"${defaultSimulator.workingDirectoryPrefix}-$tag")
    defaultSimulator
  }
}

def getFirtoolArgs:       Seq[String] = _firtoolArgs


package tywaves.simulator

import chisel3.RawModule

trait ParametricSimulatorInterface {
  private var simulator = new ParametricSimulator

  /** If true, the simulator will be reset before running each simulation */
  private var _resetSimulationBeforeRun = false

  /** Use this method to run a simulations */
  def simulate[T <: RawModule](
      module:   => T,

   * Use this method to manually reset the simulator and run multiple
   * independent simulations
   */
  def reset(): Unit =
    simulator = new ParametricSimulator

  def resetBeforeEachRun(): Unit =
    _resetSimulationBeforeRun = true
}

      settings: Seq[SimulatorSettings] = Seq(),
      simName:  String = "defaultSimulation",
  )(body: T => Unit): Unit = {
    if (_resetSimulationBeforeRun)
      reset()


package tywaves.simulator

import svsim.verilator
import verilator.Backend.CompilationSettings.TraceStyle

/** Trait to represent the simulator settings */
trait SimulatorSettings

protected sealed trait TraceSetting extends SimulatorSettings {
  val traceStyle: TraceStyle
  val extension:  String
}

/**
 * [[TraceVcd]] is an abstracted representation of the
 * [[verilator.Backend.CompilationSettings.TraceStyle.Vcd]] that can be used by
 * the user of the simulator.
 */
private[simulator] case class TraceVcd(traceUnderscore: Boolean) extends TraceSetting with SimulatorSettings {
  val traceStyle: TraceStyle = TraceStyle.Vcd(traceUnderscore)
  val extension:  String     = "vcd"
}

/**
 * [[TraceName]] stores the name of the final trace file.
 */
private[simulator] case class TraceName(name: String) extends SimulatorSettings

/**
 * [[SaveWorkspace]] tells to save the workspace of the simulation and stores
 * the final name.
 */
private[simulator] case class SaveWorkspace(name: String) extends SimulatorSettings

/**
 * [[FirtoolArgs]] stores the arguments to pass to the firtool command.
 */

/**
 * Package object to expose the simulator settings to the user. The following
 * settings can be used by a simulator to allow users to configure the
 * simulation.
 */
package object simulatorSettings {
  // Interface to the simulation
  val VcdTrace:               TraceVcd = TraceVcd(false)
  val VcdTraceWithUnderscore: TraceVcd = TraceVcd(true)

  val SaveWorkdirFile: String => SaveWorkspace = (name: String) => SaveWorkspace(name)
  val SaveWorkdir:     SaveWorkspace           = SaveWorkspace("")

  val NameTrace: String => TraceName = (name: String) => TraceName(name)

  val WithFirtoolArgs: Seq[String] => FirtoolArgs = (args: Seq[String]) => FirtoolArgs(args)

}

private[simulator] case class FirtoolArgs(args: Seq[String]) extends SimulatorSettings


package tywaves.simulator

import scala.annotation.unused

private[tywaves] object TywavesInterface {

  private val program = tywaves.BuildInfo.surferTywavesBinaryPath

  def run(
      vcdPath:       String,
      hglddDirPath:  Option[String],
      extraScopes:   Seq[String],
      topModuleName: Option[String],
      waitFor:       Boolean = true,
  ): Unit = {
    {
      import scala.sys.process._

      // Check if tywaves is installed
      val exitCode = s"which $program".!

      case Some(_) => Some("--hgldd-dir")
      case None    => None
    }

    val extraScopesCmd = (extraScopes, topModuleName) match {
      case (Nil, _) | (_, None) => Nil
      case _ => Seq("--extra-scopes") ++ extraScopes ++
          Seq("--top-module") ++ topModuleName
    }

    val cmd = Seq(program, vcdPath) ++ hglddDirCmd ++ hglddDirPath ++ extraScopesCmd

    // Execute and return to the caller
    @unused val process = new ProcessBuilder(cmd: _*).inheritIO().start()
    if (waitFor)
      process.waitFor()
    // No wait for the process to finish
  }
}

      if (exitCode != 0)
        throw new Exception(s"$program not found on the PATH! Please install it running: make all\n")
    }