// See README.md for license details.
package core
import chisel3.iotesters.PeekPokeTester

import scala.io.{BufferedSource, Source}
import java.util.Calendar

import java.io.{BufferedWriter, FileWriter ,PrintWriter}

case class CpuBusTester(c: CpuBus, hexname: String, logname: String) extends PeekPokeTester(c) {
  // read from binary file
  val s: BufferedSource = Source.fromFile(hexname)
  var buffs: Array[String] = _
  try {
    buffs = s.getLines.toArray
  } finally {
    s.close()
  }
  step(1)
  poke(c.io.sw.halt, value = 1)
  poke(c.io.sw.w_waitrequest_sig, value = 0)
  poke(c.io.sw.w_datawaitreq_sig, value = 0)
  step(1)

  var pw = new PrintWriter(new BufferedWriter(new FileWriter(logname)), true)

  for (addr <- 0 until buffs.length * 4 by 4) {
    val mem_val = buffs(addr / 4).replace(" ", "")
    val mem = Integer.parseUnsignedInt(mem_val, 16)

    poke(signal = c.io.sw.w_add, value = addr)
    step(1)
    poke(signal = c.io.sw.w_dat, value = mem)
    pw.println(f"write: addr = 0x$addr%04X,\tdata = 0x$mem%08X")
    step(1)
  }

  step(1)
  pw.println(f"---------------------------------------------------------")

  poke(signal = c.io.sw.w_pc, value = 0) // restart pc address
  step(1) // fetch pc
  poke(c.io.sw.w_waitrequest_sig, value = 1) // after reset, waitrequest = 1
  poke(signal = c.io.sw.halt, value = 0)
  step(2)
  pw.println(f"count\tINST\t\t| EX STAGE:rs1 ,\t\t\trs2 ,\t\timm\t\t\t| MEM:ALU out\t| WB:ALU out, rd\t\t\t\tstall")

  // external interrupt signal(true = rising edge, 0 = off)
  poke(signal = c.io.sw.w_interrupt_sig, value = 0)

  // about 1000 cycle, we can finish 'riscv-tests'.
  // change parameters on your another projects.
  for (lp <- 0 until 1000 by 1) {
    val a           = peek(signal = c.io.sw.r_pc)   // pc count
    val d           = peek(signal = c.io.sw.r_dat)  // instruction
    val exraddr1    = peek(c.io.sw.r_ex_raddr1)     // rs1 address
    val exraddr2    = peek(c.io.sw.r_ex_raddr2)     // rs2 address
    val exrs1       = peek(c.io.sw.r_ex_rs1)        // rs1 data
    val exrs2       = peek(c.io.sw.r_ex_rs2)        // rs2 data
    val eximm       = peek(c.io.sw.r_ex_imm)        // imm
    val memaluo     = peek(c.io.sw.r_mem_alu_out)   // alu(MEM stage)
    val wbaluo      = peek(c.io.sw.r_wb_alu_out)    // alu(WB stage)
    val wbaddr      = peek(c.io.sw.r_wb_rf_waddr)   // write-back rd address
    val wbdata      = peek(c.io.sw.r_wb_rf_wdata)   // write-back rd data
    val stallsig    = peek(c.io.sw.r_stall_sig)     // stall signal
    // if you need fire external interrupt signal uncomment below
    //    if(lp == 96){
    //      poke(signal = c.io.sw.w_interrupt_sig, value = 1)
    //    }
    //    else{
    //      poke(signal = c.io.sw.w_interrupt_sig, value = 0)
    //    }
    if(lp > 3) {
      poke(c.io.sw.w_waitrequest_sig, value = 0)
    }

    pw.println(f"0x$a%04X,\t0x$d%08X\t| x($exraddr1)=>0x$exrs1%08X, x($exraddr2)=>0x$exrs2%08X,\t0x$eximm%08X\t| 0x$memaluo%08X\t| 0x$wbaluo%08X, x($wbaddr%d)\t<= 0x$wbdata%08X, $stallsig%x") //peek(c.io.sw.data)

  }
  step(1)
  pw.println("---------------------------------------------------------")

  poke(signal = c.io.sw.halt, value = 1)
  step(2)

  for (lp <- 0 to 31 by 1) {

    poke(signal = c.io.sw.g_add, value = lp)
    step(1)
    val d = {
      peek(signal = c.io.sw.g_dat)
    }

    step(1)
    pw.println(f"read : x$lp%2d = 0x$d%08X ") //peek(c.io.sw.data)
  }

  poke(signal = c.io.sw.g_add, value = 3)                 // select x3 register (gp)
  step(1)

  val d: BigInt = peek(signal = c.io.sw.g_dat)
  step(1)
  if (d == 1) {                                           // if(x3 == 1) then PASS else FAIL
    pw.println(f"$hexname%s PASS: simulation finished.")
  } else {
    pw.println(f"$hexname%s FAIL: simulation finished.")
  }
  pw.println(f"simulation finished at " + Calendar.getInstance().getTime())
  pw.close()
  expect(c.io.sw.g_dat, expected = 1)

}

/*
    if( (lp >= 20 && lp<=25) || lp == 65 || lp == 90){
      poke(signal = c.io.sw.w_waitrequest_sig, value = 1)
    }
    else{


// See README.md for license details.
package core
import chisel3.iotesters
import chisel3.iotesters.ChiselFlatSpec
//noinspection ScalaStyle
class TestCoreAll extends ChiselFlatSpec {
	"rv32ui-p-add.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-add.hex", "src/sw/rv32ui-p-add_tester.log")
		}) should be (true)
	}
	"rv32ui-p-addi.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-addi.hex", "src/sw/rv32ui-p-addi_tester.log")
		}) should be (true)
	}
	"rv32ui-p-and.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-and.hex", "src/sw/rv32ui-p-and_tester.log")
		}) should be (true)
	}
	"rv32ui-p-andi.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-andi.hex", "src/sw/rv32ui-p-andi_tester.log")
		}) should be (true)
	}
	"rv32ui-p-auipc.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-auipc.hex", "src/sw/rv32ui-p-auipc_tester.log")
		}) should be (true)
	}
	"rv32ui-p-beq.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-beq.hex", "src/sw/rv32ui-p-beq_tester.log")
		}) should be (true)
	}
	"rv32ui-p-bge.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-bge.hex", "src/sw/rv32ui-p-bge_tester.log")
		}) should be (true)
	}
	"rv32ui-p-bgeu.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-bgeu.hex", "src/sw/rv32ui-p-bgeu_tester.log")
		}) should be (true)
	}
	"rv32ui-p-blt.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-blt.hex", "src/sw/rv32ui-p-blt_tester.log")
		}) should be (true)
	}
	"rv32ui-p-bltu.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-bltu.hex", "src/sw/rv32ui-p-bltu_tester.log")
		}) should be (true)
	}
	"rv32ui-p-bne.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-bne.hex", "src/sw/rv32ui-p-bne_tester.log")
		}) should be (true)
	}
	"rv32ui-p-jal.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-jal.hex", "src/sw/rv32ui-p-jal_tester.log")
		}) should be (true)
	}
	"rv32ui-p-jalr.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-jalr.hex", "src/sw/rv32ui-p-jalr_tester.log")
		}) should be (true)
	}
	"rv32ui-p-lb.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-lb.hex", "src/sw/rv32ui-p-lb_tester.log")
		}) should be (true)
	}
	"rv32ui-p-lbu.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-lbu.hex", "src/sw/rv32ui-p-lbu_tester.log")
		}) should be (true)
	}
	"rv32ui-p-lh.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-lh.hex", "src/sw/rv32ui-p-lh_tester.log")
		}) should be (true)
	}
	"rv32ui-p-lhu.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-lhu.hex", "src/sw/rv32ui-p-lhu_tester.log")
		}) should be (true)
	}
	"rv32ui-p-lui.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-lui.hex", "src/sw/rv32ui-p-lui_tester.log")
		}) should be (true)
	}
	"rv32ui-p-lw.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-lw.hex", "src/sw/rv32ui-p-lw_tester.log")
		}) should be (true)
	}
	"rv32ui-p-or.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-or.hex", "src/sw/rv32ui-p-or_tester.log")
		}) should be (true)
	}
	"rv32ui-p-ori.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-ori.hex", "src/sw/rv32ui-p-ori_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sb.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sb.hex", "src/sw/rv32ui-p-sb_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sh.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sh.hex", "src/sw/rv32ui-p-sh_tester.log")
		}) should be (true)
	}
	"rv32ui-p-simple.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-simple.hex", "src/sw/rv32ui-p-simple_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sll.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sll.hex", "src/sw/rv32ui-p-sll_tester.log")
		}) should be (true)
	}
	"rv32ui-p-slli.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-slli.hex", "src/sw/rv32ui-p-slli_tester.log")
		}) should be (true)
	}
	"rv32ui-p-slt.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-slt.hex", "src/sw/rv32ui-p-slt_tester.log")
		}) should be (true)
	}
	"rv32ui-p-slti.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-slti.hex", "src/sw/rv32ui-p-slti_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sltiu.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sltiu.hex", "src/sw/rv32ui-p-sltiu_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sltu.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sltu.hex", "src/sw/rv32ui-p-sltu_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sra.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sra.hex", "src/sw/rv32ui-p-sra_tester.log")
		}) should be (true)
	}
	"rv32ui-p-srai.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-srai.hex", "src/sw/rv32ui-p-srai_tester.log")
		}) should be (true)
	}
	"rv32ui-p-srl.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-srl.hex", "src/sw/rv32ui-p-srl_tester.log")
		}) should be (true)
	}
	"rv32ui-p-srli.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-srli.hex", "src/sw/rv32ui-p-srli_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sub.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sub.hex", "src/sw/rv32ui-p-sub_tester.log")
		}) should be (true)
	}
	"rv32ui-p-sw.hex tester using iotesters" should "be pass test." in {

		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-xor.hex", "src/sw/rv32ui-p-xor_tester.log")
		}) should be (true)
	}
	"rv32ui-p-xori.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-xori.hex", "src/sw/rv32ui-p-xori_tester.log")
		}) should be (true)
	}
	"rv32mi-p-breakpoint.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-breakpoint.hex", "src/sw/rv32mi-p-breakpoint_tester.log")
		}) should be (true)
	}
	"rv32mi-p-csr.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-csr.hex", "src/sw/rv32mi-p-csr_tester.log")
		}) should be (true)
	}
	"rv32mi-p-illegal.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-illegal.hex", "src/sw/rv32mi-p-illegal_tester.log")
		}) should be (true)
	}
	"rv32mi-p-ma_addr.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-ma_addr.hex", "src/sw/rv32mi-p-ma_addr_tester.log")
		}) should be (true)
	}
	"rv32mi-p-ma_fetch.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-ma_fetch.hex", "src/sw/rv32mi-p-ma_fetch_tester.log")
		}) should be (true)
	}
	"rv32mi-p-mcsr.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-mcsr.hex", "src/sw/rv32mi-p-mcsr_tester.log")
		}) should be (true)
	}
	"rv32mi-p-sbreak.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-sbreak.hex", "src/sw/rv32mi-p-sbreak_tester.log")
		}) should be (true)
	}
	"rv32mi-p-scall.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-scall.hex", "src/sw/rv32mi-p-scall_tester.log")
		}) should be (true)
	}
	"rv32mi-p-shamt.hex tester using iotesters" should "be pass test." in {
		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32mi-p-shamt.hex", "src/sw/rv32mi-p-shamt_tester.log")
		}) should be (true)
	}
}

		iotesters.Driver.execute(Array(), () => new CpuBus())(testerGen = c => {
			CpuBusTester(c, "src/sw/rv32ui-p-sw.hex", "src/sw/rv32ui-p-sw_tester.log")
		}) should be (true)


package frenda

import firrtl.options.StageMain
import frenda.stage.FrendaStage

/**
 * Entry point of Frenda.
 */

object FirrtlCompiler extends StageMain(new FrendaStage)


package frenda

/**
 * Represents a Frenda internal exception.
 *
 * @param msg message of the exception
 */

class FrendaException(msg: String) extends Exception(msg)


package frenda.stage

import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.ir.Circuit
import firrtl.options.{HasShellOptions, ShellOption, Unserializable}

import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import scala.concurrent.{ExecutionContext, Future}

sealed trait FrendaAnnotation extends Unserializable {
  this: Annotation =>
}

case class JobsAnnotation(jobs: Int)
  extends NoTargetAnnotation
    with FrendaAnnotation

object JobsAnnotation extends HasShellOptions {
  val options: Seq[ShellOption[_]] = Seq(
    new ShellOption[Int](
      longOption = "jobs",
      shortOption = Some("j"),
      toAnnotationSeq = i => Seq(JobsAnnotation(i)),
      helpText = "The number of jobs to run simultaneously, default to 1",
    )
  )
}

case class OutputDotFFileAnnotation(file: String)
  extends NoTargetAnnotation
    with FrendaAnnotation

object OutputDotFFileAnnotation extends HasShellOptions {
  val options: Seq[ShellOption[_]] = Seq(
    new ShellOption[String](
      longOption = "output-f-file",
      shortOption = Some("off"),
      toAnnotationSeq = s => Seq(OutputDotFFileAnnotation(s)),
      helpText = "The output '.f' file",
      helpValueName = Some("<file>"),
    )
  )
}

case object SilentModeAnnotation
  extends NoTargetAnnotation
    with FrendaAnnotation
    with HasShellOptions {
  val options: Seq[ShellOption[_]] = Seq(
    new ShellOption[Unit](
      longOption = "silent-mode",
      shortOption = Some("s"),
      toAnnotationSeq = _ => Seq(SilentModeAnnotation),
      helpText = "Do not display any additional information on the screen",
    )
  )
}

case object CleanBuildAnnotation
  extends NoTargetAnnotation
    with FrendaAnnotation
    with HasShellOptions {
  val options: Seq[ShellOption[_]] = Seq(
    new ShellOption[Unit](
      longOption = "clean-build",
      shortOption = Some("cb"),
      toAnnotationSeq = _ => Seq(CleanBuildAnnotation),
      helpText = "Ignores the build cache and perform a clean build",
    )
  )
}

final case class FrendaOptions(targetDir: String,
                               jobs: Int,
                               outputDotF: Option[String],
                               silentMode: Boolean,
                               cleanBuild: Boolean) {
  class GlobalExecutionContext extends ExecutionContext {
    private val threadPool = Executors.newFixedThreadPool(jobs)

    override def execute(runnable: Runnable): Unit = threadPool.submit(runnable)

    override def reportFailure(cause: Throwable): Unit = ()

    def shutdown(): Unit = threadPool.shutdown()
  }

  /** The global execution context of all `Future`s. */
  lazy val executionContext: GlobalExecutionContext = new GlobalExecutionContext

  /** Total progress. */
  var totalProgress: Int = 0

  /** Current progress. */
  private val currentProgress = new AtomicInteger

  /** The print stream of logger. */
  private val stream = System.out

  /**
   * Logs message if not in silent mode.
   *
   * @param message the message
   */

  /**
   * Logs message if not in silent mode (thread-safe).
   *
   * @param message the message
   */
  @inline def logSync(message: String): Unit = if (!silentMode) stream.synchronized {
    stream.println(message)
  }

  /**
   * Logs message with progress information if not in silent mode (thread-safe).
   *
   * @param message the message
   */
  @inline def logProgress(message: String): Unit = if (!silentMode) {
    val progress = currentProgress.incrementAndGet()
    stream.synchronized {
      stream.println(s"[$progress/$totalProgress] $message")
    }
  }
}

object FrendaOptions {
  /**
   * Gets `FrendaOptions` from annotations.
   *
   * @param annotations the sequence of annotations
   * @return options
   */
  def fromAnnotations(annotations: AnnotationSeq): FrendaOptions =
    annotations.collectFirst { case FrendaOptionsAnnotation(o) => o }.get
}

case class FrendaOptionsAnnotation(frendaOptions: FrendaOptions)
  extends NoTargetAnnotation
    with FrendaAnnotation

final case class SplitModule(name: String, circuit: Circuit)

case class FutureSplitModulesAnnotation(futures: Seq[Future[SplitModule]])
  extends NoTargetAnnotation
    with FrendaAnnotation {
  override def toString: String = s"FutureSplitModulesAnnotation(${futures.length} x Future)"
}

case class WriteDotFFileAnnotation(files: Seq[String])
  extends NoTargetAnnotation
    with FrendaAnnotation

@inline def log(message: String): Unit = if (!silentMode) stream.println(message)


package frenda.stage

import firrtl.options.Shell

/**
 * The command line interface of Frenda.
 */
trait FrendaCli {

  parser.note("Frenda Specific Options")
  Seq(
    JobsAnnotation,
    OutputDotFFileAnnotation,
    SilentModeAnnotation,
    CleanBuildAnnotation,
  ).foreach(_.addOptions(parser))
}


package frenda.stage

import firrtl.options.{Dependency, PhaseManager}
import frenda.stage.phases._

/**
 * All phases required by Frenda.
 */

object FrendaPhase {
  val targets: Seq[PhaseManager.PhaseDependency] = Seq(
    Dependency[CheckOptions],
    Dependency[AddCircuit],
    Dependency[PreTransform],
    Dependency[SplitCircuit],
    Dependency[IncrementalCompile],
    Dependency[WriteDotFFile],
  )
}

class FrendaPhase extends PhaseManager(FrendaPhase.targets)


package frenda.stage

import firrtl.AnnotationSeq
import firrtl.options.{Shell, Stage}
import firrtl.stage.FirrtlCli

/**
 * Main stage of Frenda.
 *
 * The stage will read the input FIRRTL, and then compile it incrementally.
 */
class FrendaStage extends Stage {
  val shell: Shell = new Shell(applicationName = "frenda") with FrendaCli with FirrtlCli

  def run(annotations: AnnotationSeq): AnnotationSeq = {
    val result = new FrendaPhase().transform(annotations)
    FrendaOptions.fromAnnotations(result).executionContext.shutdown()
    result
  }


package frenda.stage.phases

import firrtl.AnnotationSeq
import firrtl.options.{Dependency, Phase}
import firrtl.stage.phases
import firrtl.stage.phases.AddDefaults
import frenda.stage.FrendaOptions

class AddCircuit extends Phase {
  override def prerequisites = Seq(Dependency[CheckOptions], Dependency[AddDefaults])

  override def optionalPrerequisites = Seq()

  override def invalidates(a: Phase) = false

  override def transform(annotations: AnnotationSeq): AnnotationSeq = {
    val options = FrendaOptions.fromAnnotations(annotations)
    options.log("Parsing input circuit...")
    new phases.AddCircuit().transform(annotations)
  }
}

override def optionalPrerequisiteOf = Seq()


package frenda.stage.phases

import firrtl.AnnotationSeq
import firrtl.options.{Phase, TargetDirAnnotation}
import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlFileAnnotation, FirrtlSourceAnnotation}
import frenda.FrendaException
import frenda.stage._

/**
 * Checks command line options, including input and output.
 */
class CheckOptions extends Phase {
  override def prerequisites = Seq()

  override def optionalPrerequisites = Seq()

  override def optionalPrerequisiteOf = Seq()

  override def transform(annotations: AnnotationSeq): AnnotationSeq = {
    // check input
    val inputCount = annotations.count {
      case _: FirrtlFileAnnotation => true
      case _: FirrtlSourceAnnotation => true
      case _: FirrtlCircuitAnnotation => true
      case _ => false
    }
    if (inputCount > 1) {
      throw new FrendaException(s"Error: only one FIRRTL input should be present, but found $inputCount")
    } else if (inputCount < 1) {
      throw new FrendaException("Error: FIRRTL input not found, try -i <FIRRTL file>")
    }
    // check output
    val targetCount = annotations.count(_.isInstanceOf[TargetDirAnnotation])
    if (targetCount > 1) {
      throw new FrendaException(s"Error: there can only be 1 target directory, but found $targetCount")
    }
    val targetDir = annotations collectFirst { case TargetDirAnnotation(s) => s } getOrElse "."
    // check Frenda related options
    val jobs = annotations collectFirst { case JobsAnnotation(i) => i } getOrElse 1
    if (jobs < 1) {
      throw new FrendaException(s"Error: jobs number must be greater than 0, but found $jobs")
    }
    val outputDotF = annotations.collectFirst { case OutputDotFFileAnnotation(s) => s }
    val silentMode = annotations.exists { case SilentModeAnnotation => true; case _ => false }
    val cleanBuild = annotations.exists { case CleanBuildAnnotation => true; case _ => false }
    annotations ++ Seq(
      FrendaOptionsAnnotation(FrendaOptions(targetDir, jobs, outputDotF, silentMode, cleanBuild))
    )
  }
}

override def invalidates(a: Phase) = false


package frenda.stage.phases

import firrtl.options.{Dependency, Phase}
import firrtl.stage.transforms.Compiler
import firrtl.{AnnotationSeq, CircuitState, EmitCircuitAnnotation, EmittedVerilogCircuitAnnotation, VerilogEmitter}
import frenda.stage.{FrendaOptions, FutureSplitModulesAnnotation, SplitModule, WriteDotFFileAnnotation}

import java.nio.file.{Files, Paths}
import java.security.MessageDigest
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}

/**
 * Compiles split modules incrementally.
 */
class IncrementalCompile extends Phase {
  override def prerequisites = Seq(Dependency[SplitCircuit])

  override def optionalPrerequisiteOf = Seq()

  override def invalidates(a: Phase) = false

  /**
   * Checks if the specific module should be compiled.
   * If so, returns a function for updating the hash file of the module.
   *
   * @param options     Frenda related options
   * @param splitModule the input module
   * @return some function for updating the hash file if the module should be compiled,
   *         otherwise `None`
   */
  private def shouldBeCompiled(options: FrendaOptions,
                               splitModule: SplitModule): Option[() => Unit] = {
    // get the hash code of the current `Circuit`
    val hash = MessageDigest.getInstance("SHA-256")
      .digest(splitModule.circuit.serialize.getBytes("UTF-8"))
    // get the hash file of the current module
    val hashFile = Paths.get(options.targetDir, s"${splitModule.name}.hash")
    if (options.cleanBuild || Files.notExists(hashFile)) {
      // clean build or hash file not found, just compile
      return Some(() => Files.write(hashFile, hash))
    }
    // check the hash code
    val recompile = !(Files.readAllBytes(hashFile) sameElements hash)
    // if re-compilation required, update the hash file
    Option.when(recompile) { () => Files.write(hashFile, hash) }
  }

  /**
   * Compiles the specific module to Verilog.
   *
   * @param options     Frenda related options
   * @param annotations sequence of annotations
   * @param splitModule the input module
   * @return path to generated verilog file and recompilation flag
   */
  private def compile(options: FrendaOptions,
                      annotations: AnnotationSeq,
                      splitModule: SplitModule): Option[(String, Boolean)] = {
    // check if need to be compiled
    shouldBeCompiled(options, splitModule) match {
      case Some(updateHash) =>
        // emit the current circuit
        val circuitAnnotations = annotations ++ Seq(
          EmitCircuitAnnotation(classOf[VerilogEmitter]),
        )
        val state = CircuitState(splitModule.circuit, circuitAnnotations)
        val compiler = new Compiler(Seq(Dependency[VerilogEmitter]), PreTransform.targets)
        val newState = compiler.transform(state)
        // generate output
        options.logProgress(s"Done compiling module '${splitModule.name}'")
        newState.annotations.collectFirst {
          case EmittedVerilogCircuitAnnotation(e) =>
            // write to Verilog file and hash file
            val path = Paths.get(options.targetDir, s"${splitModule.name}.v")
            Files.writeString(path, e.value)
            updateHash()
            (path.toRealPath().toAbsolutePath.toString, true)
        }
      case None =>
        options.logProgress(s"Skipping module '${splitModule.name}'")
        val path = Paths.get(options.targetDir, s"${splitModule.name}.v")
        Option.when(Files.exists(path)) {
          (path.toRealPath().toAbsolutePath.toString, false)
        }
    }
  }

  override def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap {
    case FutureSplitModulesAnnotation(futures) =>
      val options = FrendaOptions.fromAnnotations(annotations)
      // create compilation tasks
      implicit val ec: ExecutionContext = options.executionContext
      val tasks = futures.map { f => f.map(compile(options, annotations, _)) }
      // compile and get results
      options.log(s"Compiling ${futures.length} modules...")
      val results = Await.result(Future.sequence(tasks), Duration.Inf).flatten
      options.log(s"Done compiling")
      // generate annotation if recompiled
      Option.when(results.exists(_._2)) {
        WriteDotFFileAnnotation(results.map(_._1))
      }
    case other => Some(other)
  }
}

override def optionalPrerequisites = Seq()


package frenda.stage.phases

import firrtl.options.{Dependency, Phase}
import firrtl.stage.TransformManager.TransformDependency
import firrtl.stage.transforms.Compiler
import firrtl.stage.{FirrtlCircuitAnnotation, Forms}
import firrtl.{AnnotationSeq, CircuitState}
import frenda.stage.FrendaOptions

/**
 * Run some necessary cross-module transforms for the input FIRRTL.
 */
class PreTransform extends Phase {
  override def prerequisites = Seq(Dependency[AddCircuit])

  override def optionalPrerequisiteOf = Seq()

  override def invalidates(a: Phase) = false

  override def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.map {
    case FirrtlCircuitAnnotation(circuit) =>
      val options = FrendaOptions.fromAnnotations(annotations)
      val compiler = new Compiler(PreTransform.targets)
      val state = CircuitState(circuit, annotations)
      options.log("Running pre-transforms...")
      FirrtlCircuitAnnotation(compiler.transform(state).circuit)
    case other => other
  }
}

object PreTransform {
  /** Target transforms. */
  val targets: Seq[TransformDependency] = Forms.HighForm
}


package frenda.stage.phases

import firrtl.ir.{Circuit, DefInstance, DefModule, ExtModule, Module, Statement}
import firrtl.options.{Dependency, Phase}
import firrtl.stage.FirrtlCircuitAnnotation
import firrtl.{AnnotationSeq, WDefInstanceConnector}
import frenda.FrendaException
import frenda.stage.{FrendaOptions, SplitModule, FutureSplitModulesAnnotation}

import scala.collection.mutable.ArrayBuffer
import scala.concurrent.{ExecutionContext, Future}

/**
 * Split the input circuit into single-module circuits.
 */
class SplitCircuit extends Phase {
  override def prerequisites = Seq(Dependency[PreTransform])

  override def optionalPrerequisites = Seq()

  override def invalidates(a: Phase) = false

  /**
   * Collects all instantiated modules in a module.
   *
   * @param mod the input module
   * @param map all defined modules in the circuit
   * @return sequence of defined modules
   */
  private def collectInstantiatedModules(mod: Module, map: Map[String, DefModule]): Seq[DefModule] = {
    // use list instead of set to maintain order
    val modules = ArrayBuffer.empty[DefModule]

    def onStmt(stmt: Statement): Unit = stmt match {
      case DefInstance(_, _, name, _) => modules += map(name)
      case _: WDefInstanceConnector => throw new FrendaException(s"unrecognized statement: $stmt")
      case other => other.foreachStmt(onStmt)
    }

    onStmt(mod.body)
    modules.distinct.toSeq
  }

  /**
   * Split the specific circuit to separated modules.
   *
   * @param circuit the input circuit
   * @return sequence of split FIRRTL modules (circuits)
   */
  private def splitCircuitIntoModules(options: FrendaOptions, circuit: Circuit): Seq[Future[SplitModule]] = {
    val modMap = circuit.modules.map(m => m.name -> m).toMap
    // turn each module into it's own circuit with it as the top and all instantiated modules as `ExtModules`
    circuit.modules.collect {
      case m: Module =>
        implicit val ec: ExecutionContext = options.executionContext
        Future {
          val instModules = collectInstantiatedModules(m, modMap)
          val extModules = instModules.map {
            case Module(info, name, ports, _) => ExtModule(info, name, ports, name, Seq.empty)
            case ext: ExtModule => ext
          }
          val circuit = Circuit(m.info, extModules :+ m, m.name)
          options.logProgress(s"Done splitting module '${m.name}'")
          SplitModule(m.name, circuit)
        }
    }
  }

  override def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.map {
    case FirrtlCircuitAnnotation(circuit) =>
      val options = FrendaOptions.fromAnnotations(annotations)
      // create tasks for splitting modules
      options.log("Splitting circuit...")
      val tasks = splitCircuitIntoModules(options, circuit)
      // update total progress for splitting and compiling modules
      options.totalProgress = tasks.length * 2
      FutureSplitModulesAnnotation(tasks)
    case other => other
  }
}


package frenda.stage.phases

import firrtl.AnnotationSeq
import firrtl.options.{Dependency, Phase}
import frenda.stage.{FrendaOptions, WriteDotFFileAnnotation}

import java.nio.file.{Files, Paths}

/**
 * Write paths of generated Verilog file to the output dot f file.
 */
class WriteDotFFile extends Phase {
  override def prerequisites = Seq(Dependency[IncrementalCompile])

  override def optionalPrerequisiteOf = Seq()

  override def invalidates(a: Phase) = false

  override def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap {
    case WriteDotFFileAnnotation(files) =>
      val options = FrendaOptions.fromAnnotations(annotations)
      options.outputDotF.foreach { path =>
        Files.writeString(Paths.get(path), files.mkString("\n"))
      }
      None
    case other => Some(other)
  }
}


object Dependencies {
  lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.2.14"
  private val chiselVersion = "3.5.5"
  lazy val chisel3 = "edu.berkeley.cs" %% "chisel3" % chiselVersion
  lazy val chiselCompilerPlugin = "edu.berkeley.cs" %% "chisel3-plugin" % chiselVersion


// SPDX-License-Identifier: Apache-2.0

package circt

/** A collection of implicit classes to provide additional methods to existing types */
object Implicits {

  /** Helpers for working with Boolean */
  implicit class BooleanImplicits(a: Boolean) {

    /** Construct an Option from a Boolean. */
    def option[A](b: => A): Option[A] =
      if (a)

        Some(b)
      else
        None
  }


// SPDX-License-Identifier: Apache-2.0

package circt.stage

import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{CustomFileEmission, HasShellOptions, OptionsException, ShellOption, Unserializable}
import firrtl.options.Viewer.view
import firrtl.stage.FirrtlOptions

/** An option consumed by [[circt.stage.CIRCTStage CIRCTStage]] */
sealed trait CIRCTOption extends Unserializable { this: Annotation => }

object PreserveAggregate extends HasShellOptions {
  sealed trait Type
  object OneDimVec extends Type
  object Vec extends Type
  object All extends Type

  override def options = Seq(
    new ShellOption[String](
      longOption = "preserve-aggregate",
      toAnnotationSeq = _ match {
        case "none"   => Seq.empty
        case "1d-vec" => Seq(PreserveAggregate(PreserveAggregate.OneDimVec))
        case "vec"    => Seq(PreserveAggregate(PreserveAggregate.Vec))
        case "all"    => Seq(PreserveAggregate(PreserveAggregate.All))
      },
      helpText = "Do not lower aggregate types to ground types"
    )
  )

}

/** Preserve passive aggregate types in CIRCT.
  */

/** Object storing types associated with different CIRCT target languages, e.g., RTL or SystemVerilog */
object CIRCTTarget {

  /** The parent type of all CIRCT targets */
  sealed trait Type

  /** The FIRRTL dialect */
  case object FIRRTL extends Type

  /** The HW dialect */
  case object HW extends Type

  /** The Verilog language */
  case object Verilog extends Type

  /** The SystemVerilog language */
  case object SystemVerilog extends Type

}

/** Annotation that tells [[circt.stage.phases.CIRCT CIRCT]] what target to compile to */
case class CIRCTTargetAnnotation(target: CIRCTTarget.Type) extends NoTargetAnnotation with CIRCTOption

object CIRCTTargetAnnotation extends HasShellOptions {

  override def options = Seq(
    new ShellOption[String](
      longOption = "target",
      toAnnotationSeq = _ match {
        case "firrtl"        => Seq(CIRCTTargetAnnotation(CIRCTTarget.FIRRTL))
        case "hw"            => Seq(CIRCTTargetAnnotation(CIRCTTarget.HW))
        case "verilog"       => Seq(CIRCTTargetAnnotation(CIRCTTarget.Verilog))
        case "systemverilog" => Seq(CIRCTTargetAnnotation(CIRCTTarget.SystemVerilog))
        case a               => throw new OptionsException(s"Unknown target name '$a'! (Did you misspell it?)")
      },
      helpText = "The CIRCT",
      helpValueName = Some("{firrtl|rtl|systemverilog}")
    )
  )

}

/** Annotation holding an emitted MLIR string
  *
  * @param filename the name of the file where this should be written
  * @param value a string of MLIR
  * @param suffix an optional suffix added to the filename when this is written to disk
  */
case class EmittedMLIR(
  filename: String,
  value:    String,
  suffix:   Option[String])
    extends NoTargetAnnotation
    with CustomFileEmission {

  override protected def baseFileName(annotations: AnnotationSeq): String = filename

  override def getBytes = value.getBytes

}

case class FirtoolOption(option: String) extends NoTargetAnnotation with CIRCTOption

case class PreserveAggregate(mode: PreserveAggregate.Type) extends NoTargetAnnotation with CIRCTOption


// SPDX-License-Identifier: Apache-2.0

package circt.stage

import java.io.File

/** Options associated with CIRCT
  *
  * @param inputFile the name of an input FIRRTL IR file
  * @param outputFile the name of the file where the result will be written, if not split
  * @param preserveAggregate causes CIRCT to not lower aggregate FIRRTL IR types
  * @param target the specific IR or language target that CIRCT should compile to
  */
class CIRCTOptions private[stage] (
  val inputFile:         Option[File] = None,
  val outputFile:        Option[File] = None,
  val preserveAggregate: Option[PreserveAggregate.Type] = None,
  val target:            Option[CIRCTTarget.Type] = None,

    inputFile:         Option[File] = inputFile,
    outputFile:        Option[File] = outputFile,
    preserveAggregate: Option[PreserveAggregate.Type] = preserveAggregate,
    target:            Option[CIRCTTarget.Type] = target,
    firtoolOptions:    Seq[String] = firtoolOptions
  ): CIRCTOptions = new CIRCTOptions(inputFile, outputFile, preserveAggregate, target, firtoolOptions)

}

val firtoolOptions:    Seq[String] = Seq.empty) {


// SPDX-License-Identifier: Apache-2.0

package circt.stage

import chisel3.stage.ChiselGeneratorAnnotation

import firrtl.AnnotationSeq
import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageMain}
import firrtl.stage.FirrtlCli

trait CLI { this: Shell =>
  parser.note("CIRCT (MLIR FIRRTL Compiler) options")
  Seq(
    CIRCTTargetAnnotation,
    PreserveAggregate,
    ChiselGeneratorAnnotation
  ).foreach(_.addOptions(parser))
}

/** A [[firrtl.options.Stage Stage]] used to compile FIRRTL IR using CIRCT. This is a drop-in replacement for
  * [[firrtl.stage.FirrtlStage]].
  *
  * @see [[https://github.com/llvm/circt llvm/circt]]
  */
class CIRCTStage extends Stage {

  override def prerequisites = Seq.empty
  override def optionalPrerequisites = Seq(Dependency[firrtl.stage.phases.Compiler])
  override def optionalPrerequisiteOf = Seq.empty
  override def invalidates(a: Phase) = false

  override val shell: Shell = new Shell("circt") with CLI with FirrtlCli

  final val phaseManager = new PhaseManager(
    targets = Seq(
      Dependency[circt.stage.phases.CIRCT]
    ),
    currentState = Seq(
      Dependency[firrtl.stage.phases.AddImplicitEmitter]
    )
  )

}

/** Command line utility for [[CIRCTStage]]. */
object CIRCTMain extends StageMain(new CIRCTStage)

override def run(annotations: AnnotationSeq): AnnotationSeq = phaseManager.transform(annotations)


// SPDX-License-Identifier: Apache-2.0

package circt.stage

import chisel3.RawModule
import chisel3.stage.{ChiselGeneratorAnnotation, NoRunFirrtlCompilerAnnotation}

import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation}
import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageMain}
import firrtl.options.Viewer.view
import firrtl.stage.{Forms, RunFirrtlTransformAnnotation}

/** Entry point for running Chisel with the CIRCT compiler.
  *
  * This is intended to be a replacement for [[chisel3.stage.ChiselStage]].
  *
  * @note The companion object, [[ChiselStage$]], has a cleaner API for compiling and returning a string.
  */
class ChiselStage extends Stage {

  override def prerequisites = Seq.empty
  override def optionalPrerequisites = Seq.empty
  override def optionalPrerequisiteOf = Seq.empty

  override val shell = new Shell("circt") with CLI

  override def run(annotations: AnnotationSeq): AnnotationSeq = {

    val pm = new PhaseManager(
      targets = Seq(
        Dependency[chisel3.stage.ChiselStage],
        Dependency[firrtl.stage.phases.AddImplicitOutputFile],
        Dependency[circt.stage.phases.Checks],
        Dependency[circt.stage.CIRCTStage]
      ),
      currentState = Seq(
        Dependency[firrtl.stage.phases.AddDefaults],
        Dependency[firrtl.stage.phases.Checks]
      )
    )
    pm.transform(NoRunFirrtlCompilerAnnotation +: annotations)
  }

}

/** Utilities for compiling Chisel */
object ChiselStage {

  /** Elaborate a Chisel circuit into a CHIRRTL string */
  def emitCHIRRTL(gen: => RawModule): String = chisel3.stage.ChiselStage.emitChirrtl(gen)

  /** A phase shared by all the CIRCT backends */
  private def phase = new PhaseManager(
    Seq(
      Dependency[chisel3.stage.phases.Checks],
      Dependency[chisel3.stage.phases.Elaborate],
      Dependency[chisel3.stage.phases.Convert],
      Dependency[firrtl.stage.phases.AddImplicitOutputFile],
      Dependency[chisel3.stage.phases.AddImplicitOutputAnnotationFile],
      Dependency[circt.stage.phases.Checks],
      Dependency[circt.stage.phases.CIRCT]
    )
  )

  /** Compile a Chisel circuit to FIRRTL dialect */
  def emitFIRRTLDialect(gen: => RawModule): String = phase
    .transform(
      Seq(
        ChiselGeneratorAnnotation(() => gen),
        CIRCTTargetAnnotation(CIRCTTarget.FIRRTL)
      )
    )
    .collectFirst {
      case EmittedMLIR(_, a, _) => a
    }
    .get

  /** Compile a Chisel circuit to HWS dialect */
  def emitHWDialect(gen: => RawModule): String = phase
    .transform(
      Seq(
        ChiselGeneratorAnnotation(() => gen),
        CIRCTTargetAnnotation(CIRCTTarget.HW)
      )
    )
    .collectFirst {
      case EmittedMLIR(_, a, _) => a
    }
    .get

  /** Compile a Chisel circuit to SystemVerilog
    * @param gen a call-by-name Chisel module
    * @param args additional command line arguments to pass to Chisel
    * @param firtoolOpts additional [[circt.stage.FirtoolOption]] to pass to firtool
    * @return a string containing the Verilog output
    */
  def emitSystemVerilog(
    gen:         => RawModule,
    args:        Array[String] = Array.empty,
    firtoolOpts: Array[String] = Array.empty
  ): String =
    phase
      .transform(
        Seq(
          ChiselGeneratorAnnotation(() => gen),
          CIRCTTargetAnnotation(CIRCTTarget.SystemVerilog)
        ) ++ (new circt.stage.ChiselStage).shell.parse(args) ++ firtoolOpts.map(FirtoolOption(_))
      )
      .collectFirst {
        case EmittedVerilogCircuitAnnotation(a) => a
      }
      .get
      .value

  /** Compile a Chisel circuit to SystemVerilog with file output
    * @param gen a call-by-name Chisel module
    * @param args additional command line arguments to pass to Chisel
    * @param firtoolOpts additional command line options to pass to firtool
    * @return a string containing the Verilog output
    */
  def emitSystemVerilogFile(
    gen:         => RawModule,
    args:        Array[String] = Array.empty,
    firtoolOpts: Array[String] = Array.empty
  ) = {
    val chiselArgs = Array("--target", "systemverilog") ++ args
    (new circt.stage.ChiselStage).execute(
      chiselArgs,
      Seq(ChiselGeneratorAnnotation(() => gen)) ++ firtoolOpts.map(FirtoolOption(_))
    )
  }
}

/** Command line entry point to [[ChiselStage]] */
object ChiselMain extends StageMain(new ChiselStage)


// SPDX-License-Identifier: Apache-2.0

package circt

import circt.stage.{CIRCTOption, CIRCTTargetAnnotation, PreserveAggregate}

import firrtl.AnnotationSeq
import firrtl.options.OptionsView
import firrtl.stage.{FirrtlFileAnnotation, FirrtlOption, OutputFileAnnotation}

import java.io.File

package object stage {

  implicit object CIRCTOptionsView extends OptionsView[CIRCTOptions] {

    def view(annotations: AnnotationSeq): CIRCTOptions =
      annotations.collect {

            case OutputFileAnnotation(a)  => acc.copy(outputFile = Some(new File(a)))
            case CIRCTTargetAnnotation(a) => acc.copy(target = Some(a))
            case PreserveAggregate(a)     => acc.copy(preserveAggregate = Some(a))
            case FirtoolOption(a)         => acc.copy(firtoolOptions = acc.firtoolOptions :+ a)
            case _                        => acc
          }
        }

  }

}

        case a: CIRCTOption          => a
        case a: FirrtlOption         => a
        case a: FirrtlFileAnnotation => a
      }


// SPDX-License-Identifier: Apache-2.0

package circt.stage.phases

import firrtl.AnnotationSeq
import firrtl.options.{Dependency, Phase}
import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlFileAnnotation}

import chisel3.stage.CircuitSerializationAnnotation

private[stage] class AddFIRRTLInputFile extends Phase {

  override def prerequisites = Seq.empty
  override def optionalPrerequisites = Seq.empty
  override def optionalPrerequisiteOf = Seq(Dependency[circt.stage.CIRCTStage])

  override def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap {
    case a: CircuitSerializationAnnotation => Some(FirrtlFileAnnotation(a.filename(annotations).toString))
    case a: FirrtlCircuitAnnotation        => None
    case a => Some(a)
  }

}


// SPDX-License-Identifier: Apache-2.0

package circt.stage.phases

import circt.Implicits.BooleanImplicits
import circt.stage.{CIRCTOptions, CIRCTTarget, EmittedMLIR, PreserveAggregate}

import firrtl.{AnnotationSeq, EmittedVerilogCircuit, EmittedVerilogCircuitAnnotation}
import firrtl.annotations.JsonProtocol
import firrtl.options.{
  CustomFileEmission,
  Dependency,
  OptionsException,
  OutputAnnotationFileAnnotation,
  Phase,
  StageError,
  StageOptions,
  StageUtils
}
import firrtl.options.phases.WriteOutputAnnotations
import firrtl.options.Viewer.view
import firrtl.stage.{FirrtlOptions, RunFirrtlTransformAnnotation}
import _root_.logger.LogLevel

import java.io.File

private object Helpers {
  implicit class LogLevelHelpers(logLevel: LogLevel.Value) {
    def toCIRCTOptions: Seq[String] = logLevel match {
      case LogLevel.Error => Seq.empty
      case LogLevel.Warn  => Seq.empty
      case LogLevel.Info  => Seq("-verbose-pass-executions")
      case LogLevel.Debug => Seq("-verbose-pass-executions")
      case LogLevel.Trace => Seq("-verbose-pass-executions", "-print-ir-after-all")
      case LogLevel.None  => Seq.empty
    }
  }

  /** Extract the JSON-encoded Annotation region from a single file output.
    *
    * @todo This is very janky and should be changed to something more stable.
    */
  def extractAnnotationFile(string: String, filename: String): AnnotationSeq = {
    var inAnno = false
    val filtered: String = string.lines.filter {
      case line if line.startsWith("// ----- 8< ----- FILE") && line.contains(filename) =>
        inAnno = true
        false
      case line if line.startsWith("// ----- 8< ----- FILE") =>
        inAnno = false
        false
      case line if inAnno =>
        true

      case _ => false
    }.toArray
      .mkString("\n")
    filtered.forall(_.isWhitespace) match {
      case false => JsonProtocol.deserialize(filtered, false)
      case true  => Seq.empty
    }
  }
}

/** A phase that calls and runs CIRCT, specifically `firtool`, while preserving an [[AnnotationSeq]] API.
  *
  * This is analogous to [[firrtl.stage.phases.Compiler]].
  */
class CIRCT extends Phase {

  import Helpers._
  import scala.sys.process._

  override def prerequisites = Seq(
    Dependency[firrtl.stage.phases.AddDefaults],
    Dependency[firrtl.stage.phases.AddImplicitEmitter],
    Dependency[firrtl.stage.phases.AddImplicitOutputFile]
  )
  override def optionalPrerequisites = Seq.empty
  override def optionalPrerequisiteOf = Seq.empty
  override def invalidates(a: Phase) = false

  override def transform(annotations: AnnotationSeq): AnnotationSeq = {
    val circtOptions = view[CIRCTOptions](annotations)
    val firrtlOptions = view[FirrtlOptions](annotations)
    val stageOptions = view[StageOptions](annotations)

    var blackbox, inferReadWrite = false
    var imcp = true
    var logLevel = _root_.logger.LogLevel.None
    var split = false

    val annotationsx: AnnotationSeq = annotations.flatMap {
      case a: CustomFileEmission => {
        val filename = a.filename(annotations)
        a.replacements(filename)
      }
      case _: firrtl.EmitCircuitAnnotation => Nil
      case _: firrtl.EmitAllModulesAnnotation => {
        split = true
        Nil
      }
      case a @ RunFirrtlTransformAnnotation(transform) =>
        transform match {
          /* Inlining/Flattening happen by default, so these can be dropped. */
          case _: firrtl.passes.InlineInstances | _: firrtl.transforms.Flatten => Nil
          /* ReplSeqMem is converted to a firtool option */
          case _: firrtl.passes.memlib.ReplSeqMem =>
            blackbox = true
            Nil
          /* Any emitters should not be passed to firtool. */
          case _: firrtl.Emitter => Nil
          /* Default case: leave the annotation around and let firtool warn about it. */
          case _ => Seq(a)
        }
      case firrtl.passes.memlib.InferReadWriteAnnotation =>
        inferReadWrite = true
        Nil
      case firrtl.transforms.NoConstantPropagationAnnotation =>
        imcp = false
        Nil
      case anno: _root_.logger.LogLevelAnnotation =>
        logLevel = anno.globalLogLevel
        Nil
      /* The following can be dropped. */
      case _: firrtl.transforms.CombinationalPath   => Nil
      case _: _root_.logger.ClassLogLevelAnnotation => Nil
      /* Default case: leave the annotation around and let firtool warn about it. */
      case a => Seq(a)
    }

    /* Filter the annotations to only those things which CIRCT should see. */
    (new WriteOutputAnnotations).transform(annotationsx)

    val input: String = firrtlOptions.firrtlCircuit match {
      case None          => throw new OptionsException("No input file specified!")
      case Some(circuit) => circuit.serialize
    }

    val chiselAnnotationFilename: Option[String] =
      stageOptions.annotationFileOut.map(stageOptions.getBuildFileName(_, Some(".anno.json")))

    val circtAnnotationFilename = "circt.anno.json"

    val binary = "firtool"

    val cmd =
      Seq(binary, "-format=fir", "-warn-on-unprocessed-annotations", "-verify-each=false", "-dedup") ++
        Seq("-output-annotation-file", circtAnnotationFilename) ++
        circtOptions.firtoolOptions ++
        logLevel.toCIRCTOptions ++
        /* The following options are on by default, so we disable them if they are false. */
        (circtOptions.preserveAggregate match {
          case Some(PreserveAggregate.OneDimVec) => Seq("-preserve-aggregate=1d-vec")
          case Some(PreserveAggregate.Vec)       => Seq("-preserve-aggregate=vec")
          case Some(PreserveAggregate.All)       => Seq("-preserve-aggregate=all")
          case None                              => None
        }) ++
        circtOptions.preserveAggregate.map(_ => "-preserve-public-types=0") ++
        (!inferReadWrite).option("-disable-infer-rw") ++
        (!imcp).option("-disable-imcp") ++
        /* The following options are off by default, so we enable them if they are true. */
        (blackbox).option("-blackbox-memory") ++
        /* Communicate the annotation file through a file. */
        (chiselAnnotationFilename.map(a => Seq("-annotation-file", a))).getOrElse(Seq.empty) ++
        /* Convert the target to a firtool-compatible option. */
        ((circtOptions.target, split) match {
          case (Some(CIRCTTarget.FIRRTL), false)        => Seq("-ir-fir")
          case (Some(CIRCTTarget.HW), false)            => Seq("-ir-hw")
          case (Some(CIRCTTarget.Verilog), true)        => Seq("--split-verilog", s"-o=${stageOptions.targetDir}")
          case (Some(CIRCTTarget.Verilog), false)       => None
          case (Some(CIRCTTarget.SystemVerilog), true)  => Seq("--split-verilog", s"-o=${stageOptions.targetDir}")
          case (Some(CIRCTTarget.SystemVerilog), false) => None
          case (None, _) =>
            throw new Exception(
              "No 'circtOptions.target' specified. This should be impossible if dependencies are satisfied!"
            )
          case (_, true) =>
            throw new Exception(
              s"The circtOptions.target specified (${circtOptions.target}) does not support running with an EmitAllModulesAnnotation as CIRCT only supports one-file-per-module for Verilog or SystemVerilog targets."
            )
          case _ =>
            throw new Exception(

            )
        })

    try {
      logger.info(s"""Running CIRCT: '${cmd.mkString(" ")} < $$input'""")
      println(s"""Running CIRCT: '${cmd.mkString(" ")} < $$input'""")
      val stdoutStream, stderrStream = new java.io.ByteArrayOutputStream
      val stdoutWriter = new java.io.PrintWriter(stdoutStream)
      val stderrWriter = new java.io.PrintWriter(stderrStream)
      val exitValue = (cmd #< new java.io.ByteArrayInputStream(input.getBytes))
        .!(ProcessLogger(stdoutWriter.println, stderrWriter.println))
      stdoutWriter.close()
      stderrWriter.close()
      val result = stdoutStream.toString
      logger.info(result)
      val errors = stderrStream.toString
      if (exitValue != 0) {
        StageUtils.dramaticError(s"${binary} failed.\nExitCode:\n${exitValue}\nSTDOUT:\n${result}\nSTDERR:\n${errors}")
        throw new StageError()
      }
      if (split) {
        logger.info(result)
        val file = new File(stageOptions.getBuildFileName(circtAnnotationFilename, Some(".anno.json")))
        file match {
          case file if !file.canRead() => Seq.empty
          case file => {
            val foo = JsonProtocol.deserialize(file, false)
            foo
          }
        }
      } else { // if split it has already been written out to the file system stdout not necessary.
        val outputFileName: String = stageOptions.getBuildFileName(firrtlOptions.outputFileName.get)
        val outputAnnotations = extractAnnotationFile(result, circtAnnotationFilename)
        outputAnnotations ++ (circtOptions.target match {
          case Some(CIRCTTarget.FIRRTL) =>
            Seq(EmittedMLIR(outputFileName, result, Some(".fir.mlir")))
          case Some(CIRCTTarget.HW) =>
            Seq(EmittedMLIR(outputFileName, result, Some(".hw.mlir")))
          case Some(CIRCTTarget.Verilog) =>
            Seq(EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit(outputFileName, result, ".v")))
          case Some(CIRCTTarget.SystemVerilog) =>
            Seq(EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit(outputFileName, result, ".sv")))
          case None =>
            throw new Exception(
              "No 'circtOptions.target' specified. This should be impossible if dependencies are satisfied!"
            )
        })
      }
    } catch {
      case a: java.io.IOException =>
        StageUtils.dramaticError(s"Binary '$binary' was not found on the $$PATH. (Do you have CIRCT installed?)")
        throw new StageError(cause = a)
    }

  }

}

s"Invalid combination of circtOptions.target ${circtOptions.target} and split ${split}"


// SPDX-License-Identifier: Apache-2.0

package circt.stage.phases

import circt.stage.CIRCTTargetAnnotation

import firrtl.{AnnotationSeq, EmitAllModulesAnnotation, Emitter, SystemVerilogEmitter}
import firrtl.annotations.Annotation
import firrtl.options.{Dependency, OptionsException, Phase, TargetDirAnnotation}
import firrtl.stage.OutputFileAnnotation

/** Check properties of an [[AnnotationSeq]] to look for errors before running CIRCT. */
class Checks extends Phase {

  override def prerequisites = Seq.empty
  override def optionalPrerequisiteOf = Seq(Dependency[circt.stage.phases.CIRCT])

  override def transform(annotations: AnnotationSeq): AnnotationSeq = {
    val target, outputFile, split, targetDir = collection.mutable.ArrayBuffer[Annotation]()

    annotations.foreach {
      case a: OutputFileAnnotation     => outputFile += a
      case a: EmitAllModulesAnnotation => split += a
      case a: TargetDirAnnotation      => targetDir += a
      case a: CIRCTTargetAnnotation    => target += a
      case _ =>
    }
    if ((split.size > 0) && (outputFile.size != 0)) {
      throw new OptionsException("Cannot specify both an OutputFileAnnotation and an EmitAllModulesAnnotation")
    }
    if ((split.size == 0) && (outputFile.size != 1)) {
      throw new OptionsException("An output file must be specified")
    }

    if ((split.size > 0) && (targetDir.size != 1)) {
      throw new OptionsException("If EmitAllModulesAnnotation is specified one TargetDirAnnotation is needed")
    }

    if (target.size != 1) {
      throw new OptionsException("Exactly one CIRCT target must be specified")
    }

    annotations
  }

}


// SPDX-License-Identifier: Apache-2.0

package circtTests.stage

import circt.stage.CIRCTStage

import firrtl.stage.{FirrtlFileAnnotation, OutputFileAnnotation}

import java.io.File
import java.io.{PrintWriter, Writer}

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

import scala.io.Source

class CIRCTStageSpec extends AnyFunSpec with Matchers {

  private def writeFile(file: File, string: String): Unit = {
    val writer = {
      file.getParentFile.mkdirs()
      new PrintWriter(file)
    }
    writer.write(string)
    writer.close()
  }

  describe("CIRCTStage") {

    it("should compile a FIRRTL file to Verilog") {

      val input =
        """|circuit Foo:
           |  module Foo:
           |    input a: UInt<1>
           |    output b: UInt<1>
           |    b <= not(a)
           |""".stripMargin

      val targetDir = new File("test_run_dir/CIRCTStage")
      val inputFile = new File(targetDir, "Foo.fir")

      writeFile(inputFile, input)

      val outputFile = new File(targetDir, "Foo.sv")

          targetDir.toString,
          "--input-file",
          inputFile.toString
        ),
        Seq.empty
      )

      info(s"output file '$outputFile' was created")
      outputFile should exist

      info(s"file looks like Verilog")
      Source.fromFile(outputFile).getLines.mkString should include("endmodule")

    }

  }

}

      outputFile.delete()

      val stage = new CIRCTStage

      stage.execute(


// SPDX-License-Identifier: Apache-2.0

package circtTests.stage

import chisel3.stage.ChiselGeneratorAnnotation

import circt.stage.{ChiselStage, FirtoolOption, PreserveAggregate}

import firrtl.annotations.DeletedAnnotation
import firrtl.EmittedVerilogCircuitAnnotation
import firrtl.stage.FirrtlCircuitAnnotation

import java.io.File

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

object ChiselStageSpec {

  import chisel3._

  class FooBundle extends Bundle {
    val a = Input(Bool())
    val b = Output(Bool())
  }

  class Foo extends RawModule {
    val a = IO(new FooBundle)
    val b = IO(Flipped(new FooBundle))
    b <> a
  }

  class Bar extends RawModule {
    val sel = IO(Input(UInt(3.W)))
    val in = IO(Input(Vec(8, UInt(8.W))))
    val out = IO(Output(UInt(8.W)))
    out := in(sel)
  }

  class BazBundle extends Bundle {
    val a = Input(UInt(3.W))
    val b = Input(UInt(4.W))
  }

  class Baz extends RawModule {
    val in = IO(Input(new BazBundle))
    val out = IO(Output(new BazBundle))
    out := in
  }
}

/** A fixture used that exercises features of the Trace API.
  */
class TraceSpec {

  import chisel3._
  import chisel3.experimental.Trace
  import chisel3.util.experimental.InlineInstance

  /** A mutable Chisel reference to an internal wire inside Bar. This is done to enable later use of the Trace API to find this wire. */
  var id: Option[Bool] = None

  /** A submodule that will be inlined into the parent, Foo. */
  class Bar extends RawModule with InlineInstance {

    /** The wire that we want to trace. */
    val a = WireDefault(false.B)
    id = Some(a)
    dontTouch(a)
    Trace.traceName(a)
  }

  /** The top module. */
  class Foo extends RawModule {
    val bar = Module(new Bar)
  }

}

class ChiselStageSpec extends AnyFunSpec with Matchers {

  describe("ChiselStage") {

    it("should compile a Chisel module to FIRRTL dialect") {

      val targetDir = new File("test_run_dir/ChiselStageSpec")

      val args: Array[String] = Array(
        "--target",
        "firrtl",
        "--target-dir",
        targetDir.toString
      )

      val expectedOutput = new File(targetDir, "Foo.fir.mlir")
      expectedOutput.delete()

      (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => new ChiselStageSpec.Foo)))

      info(s"'$expectedOutput' exists")
      expectedOutput should (exist)

    }

    it("should compile a Chisel module to RTL dialect") {

      val targetDir = new File("test_run_dir/ChiselStageSpec")

      val args: Array[String] = Array(
        "--target",
        "hw",
        "--target-dir",
        targetDir.toString
      )

      val expectedOutput = new File(targetDir, "Foo.hw.mlir")
      expectedOutput.delete()

      (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => new ChiselStageSpec.Foo)))

      info(s"'$expectedOutput' exists")
      expectedOutput should (exist)

    }

    it("should compile a Chisel module to SystemVerilog") {

      val targetDir = new File("test_run_dir/ChiselStageSpec")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val expectedOutput = new File(targetDir, "Foo.sv")
      expectedOutput.delete()

      (new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new ChiselStageSpec.Foo)))
        .map {
          case DeletedAnnotation(_, a) => a
          case a                       => a
        }

      info(s"'$expectedOutput' exists")
      expectedOutput should (exist)

    }

    it("should support custom firtool options") {
      val targetDir = new File("test_run_dir/ChiselStageSpec")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      info(s"output contains a case statement using --lowering-options=disallowPackedArrays")
      (new ChiselStage)
        .execute(
          args,
          Seq(
            ChiselGeneratorAnnotation(() => new ChiselStageSpec.Bar),
            FirtoolOption("--lowering-options=disallowPackedArrays")
          )
        )
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value should include("case")
    }

    it("should support aggregate preservation mode") {
      val targetDir = new File("test_run_dir/ChiselStageSpec")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      info(s"output contains a verilog struct using preserve-aggregate option")
      (new ChiselStage)
        .execute(
          args,
          Seq(ChiselGeneratorAnnotation(() => new ChiselStageSpec.Baz), PreserveAggregate(PreserveAggregate.All))
        )
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value should include("struct")
    }
  }

  describe("ChiselStage custom transform support") {

    it("should work with InlineInstance") {

      import chisel3._
      import chisel3.util.experimental.InlineInstance

      trait SimpleIO { this: RawModule =>
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))
      }

      class Bar extends RawModule with SimpleIO with InlineInstance {
        b := ~a
      }

      class Foo extends RawModule with SimpleIO {
        val bar = Module(new Bar)
        bar.a := a
        b := bar.b
      }

      val targetDir = new File("test_run_dir/InlineInstance")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      ((new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new Foo)))
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value should not).include("module Bar")
    }

    it("should work with FlattenInstance") {

      import chisel3._
      import chisel3.util.experimental.FlattenInstance

      trait SimpleIO { this: RawModule =>
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))
      }

      class Baz extends RawModule with SimpleIO {
        b := ~a
      }

      class Bar extends RawModule with SimpleIO {
        val baz = Module(new Baz)
        baz.a := a
        b := baz.b
      }

      class Foo extends RawModule with SimpleIO {
        val bar = Module(new Bar with FlattenInstance)
        bar.a := a
        b := bar.b
      }

      val targetDir = new File("test_run_dir/FlattenInstance")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val verilog = (new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new Foo)))
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value

      (verilog should not).include("module Baz")
      (verilog should not).include("module Bar")

    }

    it("should work with the Trace API for unified output") {

      import chisel3.experimental.Trace

      val fixture = new TraceSpec

      val targetDir = new File("test_run_dir/TraceAPIUnified")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val annos = (new ChiselStage).execute(
        args,
        Seq(
          ChiselGeneratorAnnotation(() => new fixture.Foo)
        )
      )

      val finalTargets = Trace.finalTarget(annos)(fixture.id.get)
      info("there is one final target")
      finalTargets should have size (1)

      val expectedTarget = firrtl.annotations.CircuitTarget("Foo").module("Foo").ref("bar_a")
      info(s"the final target is $expectedTarget")
      finalTargets.head should be(expectedTarget)

    }

    it("should work with the Trace API for split  verilog output") {

      import chisel3.experimental.Trace

      val fixture = new TraceSpec

      val targetDir = new File("test_run_dir/TraceAPISplit")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val annos = (new ChiselStage).execute(
        args,
        Seq(
          ChiselGeneratorAnnotation(() => new fixture.Foo),
          firrtl.EmitAllModulesAnnotation(classOf[firrtl.SystemVerilogEmitter])
        )
      )

      val finalTargets = Trace.finalTarget(annos)(fixture.id.get)
      info("there is one final target")
      finalTargets should have size (1)

      val expectedTarget = firrtl.annotations.CircuitTarget("Foo").module("Foo").ref("bar_a")
      info(s"the final target is $expectedTarget")
      finalTargets.head should be(expectedTarget)

    }

  }

  describe("ChiselStage DontTouchAnnotation support") {

    it("should block removal of wires and nodes") {

      import chisel3._

      class Foo extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        val w = WireDefault(a)
        dontTouch(w)

        val n = ~w
        dontTouch(n)

        b := n
      }

      val targetDir = new File("test_run_dir/DontTouch")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val verilog = (new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new Foo)))
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value

      verilog should include("wire w")
      verilog should include("wire n")

    }

  }

  describe("ChiselStage forceName support") {

    it("should work when forcing a module name") {

      import chisel3._
      import chisel3.util.experimental.{forceName, InlineInstance}

      trait SimpleIO { this: RawModule =>
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))
      }

      class Baz extends RawModule with SimpleIO {
        b := ~a
      }

      class Bar extends RawModule with SimpleIO with InlineInstance {
        val baz = Module(new Baz)
        baz.a := a
        b := baz.b
      }

      class Foo extends RawModule with SimpleIO {
        val bar = Module(new Bar)
        bar.a := a
        b := bar.b

        forceName(bar.baz)
      }

      val targetDir = new File("test_run_dir/ForceName")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val verilog: String = (new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new Foo)))
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value

      verilog should include("module Baz")

    }

  }

  describe("ChiselStage dedup behavior") {

    it("should be on by default and work") {

      import chisel3._

      class Baz extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        b := a
      }

      class Bar extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        b := a
      }

      class Foo extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        val bar = Module(new Bar)
        val baz = Module(new Baz)
        bar.a := a
        baz.a := a
        b := bar.b ^ baz.b
      }

      val targetDir = new File("test_run_dir/Dedup")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val verilog: String = (new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new Foo)))
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value

      verilog should include("module Bar")
      (verilog should not).include("module Baz")

    }

    it("should respect the doNotDedup API") {

      import chisel3._
      import chisel3.experimental.doNotDedup

      class Baz extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        b := a
      }

      class Bar extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        b := a
      }

      class Foo extends RawModule {
        val a = IO(Input(Bool()))
        val b = IO(Output(Bool()))

        val bar = Module(new Bar)
        val baz = Module(new Baz)
        bar.a := a
        baz.a := a
        b := bar.b ^ baz.b
        doNotDedup(baz)
      }

      val targetDir = new File("test_run_dir/Dedup")

      val args: Array[String] = Array(
        "--target",
        "systemverilog",
        "--target-dir",
        targetDir.toString
      )

      val verilog: String = (new ChiselStage)
        .execute(args, Seq(ChiselGeneratorAnnotation(() => new Foo)))
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value

      verilog should include("module Bar")
      verilog should include("module Baz")

    }

  }

  describe("ChiselStage$") {

    }

    it("should emit SystemVerilog to string") {

      ChiselStage.emitSystemVerilog(new ChiselStageSpec.Foo) should include("endmodule")

    }
    it("should emit SystemVerilog to string with firtool options") {

      val sv = ChiselStage
        .emitSystemVerilog(
          new ChiselStageSpec.Foo,
          firtoolOpts = Array("--strip-debug-info")
        )
      (sv should not).include("// <stdin>:")

    }
    it("should emit SystemVerilog to string with chisel arguments and firtool options") {

      val sv = ChiselStage.emitSystemVerilog(
        new ChiselStageSpec.Foo,
        Array("--show-registrations"),
        Array("--strip-debug-info")
      )
      sv should include("Generated by CIRCT")

    }

    it("emitSystemVerilogFile should support custom Chisel args and firtool options") {
      val targetDir = new File("test_run_dir/ChiselStageSpec/generated")

      val args: Array[String] = Array(
        "--target-dir",
        targetDir.toString
      )

      info(s"output contains a case statement using --lowering-options=disallowPackedArrays")
      ChiselStage
        .emitSystemVerilogFile(
          new ChiselStageSpec.Bar,
          args,
          Array("--lowering-options=disallowPackedArrays")
        )
        .collectFirst {
          case EmittedVerilogCircuitAnnotation(a) => a
        }
        .get
        .value should include("case")

      val expectedOutput = new File(targetDir, "Bar.sv")
      expectedOutput should (exist)
      info(s"'$expectedOutput' exists")
    }

  }
}

    it("should emit FIRRTL dialect") {

      ChiselStage.emitFIRRTLDialect(new ChiselStageSpec.Foo) should include(" firrtl.module")

    }


/**********************************************************
 * Author: Rahul Bera
 * Description: Implements the index generation circuitry
 * from state information
 * ********************************************************/
package pythia

import chisel3._
import chisel3.util._

class IndexGen (val iType:Int, val plane_offset:Int) extends Module {
  val io = IO(new Bundle {
    val pc = Input(UInt(32.W))
    val offset = Input(UInt(6.W))
    val delta_path = Input(UInt(32.W))
    val index = Output(UInt(7.W))
  })

  val temp = (raw_val ^ plane_offset.asUInt)

  // Robert Jenkin's 32 bit mix function for hashing
  val t0 = temp + (temp << 12)
  val t1 = t0   ^ (temp >> 22)
  val t2 = t1   + (temp << 4)
  val t3 = t2   ^ (temp >> 9)
  val t4 = t3   + (temp << 10)
  val t5 = t4   ^ (temp >> 2)
  val t6 = t5   + (temp << 7)
  val t7 = t6   ^ (temp >> 12)

  // Modulo 128
  io.index := t7(6, 0)
  // printf("[INDEXGEN] index %d\n", io.index)
}

val raw_val = if(iType == 1) {Cat(io.pc, io.offset)} else {io.delta_path}


/**********************************************************
 * Author: Rahul Bera
 * Description: The top-level Pythia module which orchestrates everything
 * ********************************************************/
package pythia

import chisel3._
import chisel3.util._

class MasterModule extends Module
{
   val io = IO(new Bundle {
      val sigUpdate = Input(Bool())
      val sigQuery = Input(Bool())
      // query inputs/outputs
      val qPC = Input(UInt(32.W))
      val qOffset = Input(UInt(6.W))
      val qDeltaPath = Input(UInt(32.W))
      val qAction = Output(UInt(4.W))
      // update input/outputs
      val uPC = Input(UInt(32.W))
      val uOffset = Input(UInt(6.W))
      val uDeltaPath = Input(UInt(32.W))
      val uAction = Input(UInt(4.W))
      val uReward = Input(UInt(8.W))
   })

   // Modules for PC+Offset feature
   val pcoIndexGen0 = Module(new IndexGen(iType = 1, plane_offset = 0xdead)) // index generator
   val pcoIndexGen1 = Module(new IndexGen(iType = 1, plane_offset = 0xdada)) // index generator
   val pcoIndexGen2 = Module(new IndexGen(iType = 1, plane_offset = 0xdeaf)) // index generator
   val vault0 = Module(new Vault()) // vault

   // Modules for delta_path feature
   val dpIndexGen0 = Module(new IndexGen(iType = 1, plane_offset = 0xdead)) // index generator
   val dpIndexGen1 = Module(new IndexGen(iType = 1, plane_offset = 0xdada)) // index generator
   val dpIndexGen2 = Module(new IndexGen(iType = 1, plane_offset = 0xdeaf)) // index generator
   val vault1 = Module(new Vault()) // vault

   // Global modules
   val qvcompare0 = Module(new QVCompare()) // Q-value comparator, one for each vault-read ports
   val qvcompare1 = Module(new QVCompare()) // Q-value comparator, one for each vault-read ports
   val max3 = Module(new MaxN()) // max reducer

   // machine states
   val ( s_idle
         :: s_update_planeR :: s_update_planeW
         :: s_query_read2 :: s_query_read4 :: s_query_read6 :: s_query_read8 :: s_query_read10 :: s_query_read12 :: s_query_read14 :: s_query_read16
         :: Nil) = Enum(11)
   val state = RegInit(s_idle)

   val update_reward = RegEnable(io.uReward, io.sigUpdate)

   //=========== Connections for PC+Offset Index Generation Modules ============//
   pcoIndexGen0.io.pc := Mux(io.sigUpdate, io.uPC, io.qPC)
   pcoIndexGen0.io.offset := Mux(io.sigUpdate, io.uOffset, io.qOffset)
   pcoIndexGen0.io.delta_path <> DontCare
   pcoIndexGen1.io.pc := Mux(io.sigUpdate, io.uPC, io.qPC)
   pcoIndexGen1.io.offset := Mux(io.sigUpdate, io.uOffset, io.qOffset)
   pcoIndexGen1.io.delta_path <> DontCare
   pcoIndexGen2.io.pc := Mux(io.sigUpdate, io.uPC, io.qPC)
   pcoIndexGen2.io.offset := Mux(io.sigUpdate, io.uOffset, io.qOffset)
   pcoIndexGen2.io.delta_path <> DontCare
   //===========================================================================//

   //========== Connections for Delta_path Index Generation Modules ============//
   dpIndexGen0.io.pc <> DontCare
   dpIndexGen0.io.offset <> DontCare
   dpIndexGen0.io.delta_path := Mux(io.sigUpdate, io.uDeltaPath, io.qDeltaPath)
   dpIndexGen1.io.pc <> DontCare
   dpIndexGen1.io.offset <> DontCare
   dpIndexGen1.io.delta_path := Mux(io.sigUpdate, io.uDeltaPath, io.qDeltaPath)
   dpIndexGen2.io.pc <> DontCare
   dpIndexGen2.io.offset <> DontCare
   dpIndexGen2.io.delta_path := Mux(io.sigUpdate, io.uDeltaPath, io.qDeltaPath)
   //===========================================================================//

   //====================== Connections for PC+Offset Vault Module =======================//
   when(io.sigUpdate){
      vault0.io.rdrow0(0) := pcoIndexGen0.io.index; vault0.io.rdrow0(1) := pcoIndexGen1.io.index; vault0.io.rdrow0(2) := pcoIndexGen2.io.index;
   }.elsewhen(io.sigQuery){
      vault0.io.rdrow0(0) := pcoIndexGen0.io.index; vault0.io.rdrow0(1) := pcoIndexGen1.io.index; vault0.io.rdrow0(2) := pcoIndexGen2.io.index;
   }.otherwise{
      vault0.io.rdrow0(0) := 0.U; vault0.io.rdrow0(1) := 0.U; vault0.io.rdrow0(2) := 0.U;
   }

   when(io.sigUpdate){
      vault0.io.rdcol0 := io.uAction
   }.elsewhen(io.sigQuery){
      when(state === s_query_read2)          { vault0.io.rdcol0 := 0.U }
      .elsewhen(state === s_query_read4)     { vault0.io.rdcol0 := 2.U }
      .elsewhen(state === s_query_read6)     { vault0.io.rdcol0 := 4.U }
      .elsewhen(state === s_query_read8)     { vault0.io.rdcol0 := 6.U }
      .elsewhen(state === s_query_read10)    { vault0.io.rdcol0 := 8.U }
      .elsewhen(state === s_query_read12)    { vault0.io.rdcol0 := 10.U }

      .elsewhen(state === s_query_read16)    { vault0.io.rdcol0 := 14.U }
      .otherwise                             { vault0.io.rdcol0 := 0.U }
   }.otherwise{
      vault0.io.rdcol0 := 0.U
   }

   when(io.sigUpdate){
      vault0.io.rdrow1(0) := 0.U; vault0.io.rdrow1(1) := 0.U; vault0.io.rdrow1(2) := 0.U;
   }.elsewhen(io.sigQuery){
      vault0.io.rdrow1(0) := pcoIndexGen0.io.index; vault0.io.rdrow1(1) := pcoIndexGen1.io.index; vault0.io.rdrow1(2) := pcoIndexGen2.io.index;
   }.otherwise{
      vault0.io.rdrow1(0) := 0.U; vault0.io.rdrow1(1) := 0.U; vault0.io.rdrow1(2) := 0.U;
   }

   when(io.sigUpdate){
      vault0.io.rdcol1 := 0.U
   }.elsewhen(io.sigQuery){
      when(state === s_query_read2)          { vault0.io.rdcol1 := 1.U }
      .elsewhen(state === s_query_read4)     { vault0.io.rdcol1 := 3.U }
      .elsewhen(state === s_query_read6)     { vault0.io.rdcol1 := 5.U }
      .elsewhen(state === s_query_read8)     { vault0.io.rdcol1 := 7.U }
      .elsewhen(state === s_query_read10)    { vault0.io.rdcol1 := 9.U }
      .elsewhen(state === s_query_read12)    { vault0.io.rdcol1 := 11.U }
      .elsewhen(state === s_query_read14)    { vault0.io.rdcol1 := 13.U }
      .elsewhen(state === s_query_read16)    { vault0.io.rdcol1 := 15.U }
      .otherwise                             { vault0.io.rdcol1 := 0.U }
   }.otherwise{
      vault0.io.rdcol1 := 0.U
   }

   vault0.io.re := false.B

   vault0.io.wrrow(0) := RegEnable(pcoIndexGen0.io.index, io.sigUpdate); vault0.io.wrrow(1) := RegEnable(pcoIndexGen1.io.index, io.sigUpdate); vault0.io.wrrow(2) := RegEnable(pcoIndexGen2.io.index, io.sigUpdate);
   vault0.io.wrcol := RegEnable(io.uAction, io.sigUpdate)
   vault0.io.wrdata(0) := RegEnable((vault0.io.rddata0(0) >> 1) + update_reward, state === s_update_planeR)
   vault0.io.wrdata(1) := RegEnable((vault0.io.rddata0(1) >> 1) + update_reward, state === s_update_planeR)
   vault0.io.wrdata(2) := RegEnable((vault0.io.rddata0(2) >> 1) + update_reward, state === s_update_planeR)
   vault0.io.we := false.B
   //======================================================================================//

   //====================== Connections for Delta_path Vault Module =======================//
   when(io.sigUpdate){
      vault1.io.rdrow0(0) := dpIndexGen0.io.index; vault1.io.rdrow0(1) := dpIndexGen1.io.index; vault1.io.rdrow0(2) := dpIndexGen2.io.index;
   }.elsewhen(io.sigQuery){
      vault1.io.rdrow0(0) := dpIndexGen0.io.index; vault1.io.rdrow0(1) := dpIndexGen1.io.index; vault1.io.rdrow0(2) := dpIndexGen2.io.index;
   }.otherwise{
      vault1.io.rdrow0(0) := 0.U; vault1.io.rdrow0(1) := 0.U; vault1.io.rdrow0(2) := 0.U;
   }

   when(io.sigUpdate){
      vault1.io.rdcol0 := io.uAction
   }.elsewhen(io.sigQuery){
      when(state === s_query_read2)          { vault1.io.rdcol0 := 0.U }
      .elsewhen(state === s_query_read4)     { vault1.io.rdcol0 := 2.U }
      .elsewhen(state === s_query_read6)     { vault1.io.rdcol0 := 4.U }
      .elsewhen(state === s_query_read8)     { vault1.io.rdcol0 := 6.U }
      .elsewhen(state === s_query_read10)    { vault1.io.rdcol0 := 8.U }
      .elsewhen(state === s_query_read12)    { vault1.io.rdcol0 := 10.U }
      .elsewhen(state === s_query_read14)    { vault1.io.rdcol0 := 12.U }
      .elsewhen(state === s_query_read16)    { vault1.io.rdcol0 := 14.U }
      .otherwise                             { vault1.io.rdcol0 := 0.U }
   }.otherwise{
      vault1.io.rdcol0 := 0.U
   }

   when(io.sigUpdate){
      vault1.io.rdrow1(0) := 0.U; vault1.io.rdrow1(1) := 0.U; vault1.io.rdrow1(2) := 0.U;
   }.elsewhen(io.sigQuery){
      vault1.io.rdrow1(0) := dpIndexGen0.io.index; vault1.io.rdrow1(1) := dpIndexGen1.io.index; vault1.io.rdrow1(2) := dpIndexGen2.io.index;
   }.otherwise{
      vault1.io.rdrow1(0) := 0.U; vault1.io.rdrow1(1) := 0.U; vault1.io.rdrow1(2) := 0.U;
   }

   when(io.sigUpdate){
      vault1.io.rdcol1 := 0.U
   }.elsewhen(io.sigQuery){
      when(state === s_query_read2)          { vault1.io.rdcol1 := 1.U }
      .elsewhen(state === s_query_read4)     { vault1.io.rdcol1 := 3.U }
      .elsewhen(state === s_query_read6)     { vault1.io.rdcol1 := 5.U }
      .elsewhen(state === s_query_read8)     { vault1.io.rdcol1 := 7.U }
      .elsewhen(state === s_query_read10)    { vault1.io.rdcol1 := 9.U }
      .elsewhen(state === s_query_read12)    { vault1.io.rdcol1 := 11.U }
      .elsewhen(state === s_query_read14)    { vault1.io.rdcol1 := 13.U }
      .elsewhen(state === s_query_read16)    { vault1.io.rdcol1 := 15.U }
      .otherwise                             { vault1.io.rdcol1 := 0.U }
   }.otherwise{
      vault1.io.rdcol1 := 0.U
   }

   vault1.io.re := false.B

   vault1.io.wrrow(0) := RegEnable(dpIndexGen0.io.index, io.sigUpdate); vault1.io.wrrow(1) := RegEnable(dpIndexGen1.io.index, io.sigUpdate); vault1.io.wrrow(2) := RegEnable(dpIndexGen2.io.index, io.sigUpdate);
   vault1.io.wrcol := RegEnable(io.uAction, io.sigUpdate)
   vault1.io.wrdata(0) := RegEnable((vault1.io.rddata0(0) >> 1) + update_reward, state === s_update_planeR)
   vault1.io.wrdata(1) := RegEnable((vault1.io.rddata0(1) >> 1) + update_reward, state === s_update_planeR)
   vault1.io.wrdata(2) := RegEnable((vault1.io.rddata0(2) >> 1) + update_reward, state === s_update_planeR)
   vault1.io.we := false.B
   //=============================================================================//

   //===================== Connections for QVCompare Module0 =====================//
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare0.io.qv0_p0 := 0.U}
      .otherwise              {qvcompare0.io.qv0_p0 := vault0.io.rddata0(0)}
   }.otherwise{
      qvcompare0.io.qv0_p0 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare0.io.qv0_p1 := 0.U}
      .otherwise              {qvcompare0.io.qv0_p1 := vault0.io.rddata0(1)}
   }.otherwise{
      qvcompare0.io.qv0_p1 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare0.io.qv0_p2 := 0.U}
      .otherwise              {qvcompare0.io.qv0_p2 := vault0.io.rddata0(2)}
   }.otherwise{
      qvcompare0.io.qv0_p2 := 0.U
   }

   when(io.sigQuery){
      when(state === s_idle)  {qvcompare0.io.qv1_p0 := 0.U}
      .otherwise              {qvcompare0.io.qv1_p0 := vault1.io.rddata0(0)}
   }.otherwise{
      qvcompare0.io.qv1_p0 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare0.io.qv1_p1 := 0.U}
      .otherwise              {qvcompare0.io.qv1_p1 := vault1.io.rddata0(1)}
   }.otherwise{
      qvcompare0.io.qv1_p1 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare0.io.qv1_p2 := 0.U}
      .otherwise              {qvcompare0.io.qv1_p2 := vault1.io.rddata0(2)}
   }.otherwise{
      qvcompare0.io.qv1_p2 := 0.U
   }
   // Add delta path signature's QV values here
   // qvcompare0.io.qv1_p0 := 0.U
   // qvcompare0.io.qv1_p1 := 0.U
   // qvcompare0.io.qv1_p2 := 0.U
   //============================================================================//

   //===================== Connections for QVCompare Module1 =====================//
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare1.io.qv0_p0 := 0.U}
      .otherwise              {qvcompare1.io.qv0_p0 := vault0.io.rddata1(0)}
   }.otherwise{
      qvcompare1.io.qv0_p0 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare1.io.qv0_p1 := 0.U}
      .otherwise              {qvcompare1.io.qv0_p1 := vault0.io.rddata1(1)}
   }.otherwise{
      qvcompare1.io.qv0_p1 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare1.io.qv0_p2 := 0.U}
      .otherwise              {qvcompare1.io.qv0_p2 := vault0.io.rddata1(2)}
   }.otherwise{
      qvcompare1.io.qv0_p2 := 0.U
   }

   when(io.sigQuery){
      when(state === s_idle)  {qvcompare1.io.qv1_p0 := 0.U}
      .otherwise              {qvcompare1.io.qv1_p0 := vault1.io.rddata1(0)}
   }.otherwise{
      qvcompare1.io.qv1_p0 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare1.io.qv1_p1 := 0.U}
      .otherwise              {qvcompare1.io.qv1_p1 := vault1.io.rddata1(1)}
   }.otherwise{
      qvcompare1.io.qv1_p1 := 0.U
   }
   when(io.sigQuery){
      when(state === s_idle)  {qvcompare1.io.qv1_p2 := 0.U}
      .otherwise              {qvcompare1.io.qv1_p2 := vault1.io.rddata1(2)}
   }.otherwise{
      qvcompare1.io.qv1_p2 := 0.U
   }
   // Add delta path signature's QV values here
   // qvcompare1.io.qv1_p0 := 0.U
   // qvcompare1.io.qv1_p1 := 0.U
   // qvcompare1.io.qv1_p2 := 0.U
   //============================================================================//

   //========================= Connections for Max Module =======================//
   when(io.sigQuery){
      when(state === s_idle) {max3.io.nums(0) := 0.U}
      .otherwise {max3.io.nums(0) := RegNext(max3.io.maxNum)}
   }.otherwise{
      max3.io.nums(0) := 0.U
   }
   when(io.sigQuery){
      max3.io.nums(1) := qvcompare0.io.qv_out
   }.otherwise{
      max3.io.nums(1) := 0.U
   }
   when(io.sigQuery){
      max3.io.nums(2) := qvcompare1.io.qv_out
   }.otherwise{
      max3.io.nums(2) := 0.U
   }

   when(io.sigQuery){
      when(state === s_idle) {max3.io.ids(0) := 0.U}
      .otherwise {max3.io.ids(0) := RegNext(max3.io.maxId)}
   }.otherwise{
      max3.io.ids(0) := 0.U
   }
   when(io.sigQuery){
      when(state === s_query_read2)            {max3.io.ids(1) := 0.U}
      .elsewhen(state === s_query_read4)       {max3.io.ids(1) := 2.U}
      .elsewhen(state === s_query_read6)       {max3.io.ids(1) := 4.U}
      .elsewhen(state === s_query_read8)       {max3.io.ids(1) := 6.U}
      .elsewhen(state === s_query_read10)      {max3.io.ids(1) := 8.U}
      .elsewhen(state === s_query_read12)      {max3.io.ids(1) := 10.U}
      .elsewhen(state === s_query_read14)      {max3.io.ids(1) := 12.U}
      .elsewhen(state === s_query_read16)      {max3.io.ids(1) := 14.U}
      .otherwise                               {max3.io.ids(1) := 0.U}
   }.otherwise{
      max3.io.ids(1) := 0.U
   }
   when(io.sigQuery){
      when(state === s_query_read2)            {max3.io.ids(2) := 1.U}
      .elsewhen(state === s_query_read4)       {max3.io.ids(2) := 3.U}
      .elsewhen(state === s_query_read6)       {max3.io.ids(2) := 5.U}
      .elsewhen(state === s_query_read8)       {max3.io.ids(2) := 7.U}
      .elsewhen(state === s_query_read10)      {max3.io.ids(2) := 9.U}
      .elsewhen(state === s_query_read12)      {max3.io.ids(2) := 11.U}
      .elsewhen(state === s_query_read14)      {max3.io.ids(2) := 13.U}
      .elsewhen(state === s_query_read16)      {max3.io.ids(2) := 15.U}
      .otherwise                               {max3.io.ids(2) := 0.U}
   }.otherwise{
      max3.io.ids(2) := 0.U
   }
   //=====================================================================//

   // Output Connection
   io.qAction := Mux(io.sigQuery && state === s_query_read16, max3.io.maxId, 0.U)

   // *********************************************************************//
   // *********************** STATE MACHINE DEFINITION ********************//
   // *********************************************************************//
   when(state === s_idle){
      // printf("[IDLE] ================ CHILLING HOMES ===============\n")
      vault0.io.re := false.B
      vault0.io.we := false.B
      vault1.io.re := false.B
      vault1.io.we := false.B
      when (io.sigUpdate){
         state := s_update_planeR
      }.elsewhen(io.sigQuery){
         state := s_query_read2
      }
   }
   .elsewhen(state === s_update_planeR){
      vault0.io.re := true.B // read the old value from plane
      vault0.io.we := false.B
      vault1.io.re := true.B // read the old value from plane
      vault1.io.we := false.B
      state := s_update_planeW
      // printf("[UPDATE-VAULT0-R-PLANE-0] row %d col %d value-read %d\n", vault0.io.rdrow0(0), vault0.io.rdcol0, vault0.io.rddata0(0))
      // printf("[UPDATE-VAULT0-R-PLANE-1] row %d col %d value-read %d\n", vault0.io.rdrow0(1), vault0.io.rdcol0, vault0.io.rddata0(1))
      // printf("[UPDATE-VAULT0-R-PLANE-2] row %d col %d value-read %d\n", vault0.io.rdrow0(2), vault0.io.rdcol0, vault0.io.rddata0(2))
      // printf("[UPDATE-VAULT1-R-PLANE-0] row %d col %d value-read %d\n", vault1.io.rdrow0(0), vault1.io.rdcol0, vault1.io.rddata0(0))
      // printf("[UPDATE-VAULT1-R-PLANE-1] row %d col %d value-read %d\n", vault1.io.rdrow0(1), vault1.io.rdcol0, vault1.io.rddata0(1))
      // printf("[UPDATE-VAULT1-R-PLANE-2] row %d col %d value-read %d\n", vault1.io.rdrow0(2), vault1.io.rdcol0, vault1.io.rddata0(2))
   }
   .elsewhen(state === s_update_planeW){
      vault0.io.re := false.B
      vault0.io.we := true.B // write the new value
      vault1.io.re := false.B
      vault1.io.we := true.B // write the new value
      state := s_idle
      // printf("[UPDATE-VAULT0-W-PLANE-0] row %d col %d value-written %d\n", vault0.io.wrrow(0), vault0.io.wrcol, vault0.io.wrdata(0))
      // printf("[UPDATE-VAULT0-W-PLANE-1] row %d col %d value-written %d\n", vault0.io.wrrow(1), vault0.io.wrcol, vault0.io.wrdata(1))
      // printf("[UPDATE-VAULT0-W-PLANE-2] row %d col %d value-written %d\n", vault0.io.wrrow(2), vault0.io.wrcol, vault0.io.wrdata(2))
      // printf("[UPDATE-VAULT1-W-PLANE-0] row %d col %d value-written %d\n", vault1.io.wrrow(0), vault1.io.wrcol, vault1.io.wrdata(0))
      // printf("[UPDATE-VAULT1-W-PLANE-1] row %d col %d value-written %d\n", vault1.io.wrrow(1), vault1.io.wrcol, vault1.io.wrdata(1))
      // printf("[UPDATE-VAULT1-W-PLANE-2] row %d col %d value-written %d\n", vault1.io.wrrow(2), vault1.io.wrcol, vault1.io.wrdata(2))
   }
   // query state transitions
   .elsewhen(state === s_query_read2 || state === s_query_read4 || state === s_query_read6 || state === s_query_read8 || state === s_query_read10 || state === s_query_read12 || state === s_query_read14 || state === s_query_read16){
      vault0.io.re := true.B
      vault1.io.re := true.B
      // printf("[QUERY-VAULT0-PLANE-0] row0 %d col0 %d val0 %d\n", vault0.io.rdrow0(0), vault0.io.rdcol0, vault0.io.rddata0(0))
      // printf("[QUERY-VAULT0-PLANE-1] row0 %d col0 %d val0 %d\n", vault0.io.rdrow0(1), vault0.io.rdcol0, vault0.io.rddata0(1))
      // printf("[QUERY-VAULT0-PLANE-2] row0 %d col0 %d val0 %d\n", vault0.io.rdrow0(2), vault0.io.rdcol0, vault0.io.rddata0(2))
      // printf("[QUERY-VAULT0-PLANE-0] row1 %d col1 %d val1 %d\n", vault0.io.rdrow1(0), vault0.io.rdcol1, vault0.io.rddata1(0))
      // printf("[QUERY-VAULT0-PLANE-1] row1 %d col1 %d val1 %d\n", vault0.io.rdrow1(1), vault0.io.rdcol1, vault0.io.rddata1(1))
      // printf("[QUERY-VAULT0-PLANE-2] row1 %d col1 %d val1 %d\n", vault0.io.rdrow1(2), vault0.io.rdcol1, vault0.io.rddata1(2))
      //
      // printf("[QUERY-VAULT1-PLANE-0] row0 %d col0 %d val0 %d\n", vault1.io.rdrow0(0), vault1.io.rdcol0, vault1.io.rddata0(0))
      // printf("[QUERY-VAULT1-PLANE-1] row0 %d col0 %d val0 %d\n", vault1.io.rdrow0(1), vault1.io.rdcol0, vault1.io.rddata0(1))
      // printf("[QUERY-VAULT1-PLANE-2] row0 %d col0 %d val0 %d\n", vault1.io.rdrow0(2), vault1.io.rdcol0, vault1.io.rddata0(2))
      // printf("[QUERY-VAULT1-PLANE-0] row1 %d col1 %d val1 %d\n", vault1.io.rdrow1(0), vault1.io.rdcol1, vault1.io.rddata1(0))
      // printf("[QUERY-VAULT1-PLANE-1] row1 %d col1 %d val1 %d\n", vault1.io.rdrow1(1), vault1.io.rdcol1, vault1.io.rddata1(1))
      // printf("[QUERY-VAULT1-PLANE-2] row1 %d col1 %d val1 %d\n", vault1.io.rdrow1(2), vault1.io.rdcol1, vault1.io.rddata1(2))
      //
      // printf("[QUERY-VAULT0] max3 %d maxId %d\n", max3.io.maxNum, max3.io.maxId)

      when(state === s_query_read2)          { state := s_query_read4 }
      .elsewhen(state === s_query_read4)     { state := s_query_read6 }
      .elsewhen(state === s_query_read6)     { state := s_query_read8 }
      .elsewhen(state === s_query_read8)     { state := s_query_read10 }
      .elsewhen(state === s_query_read10)    { state := s_query_read12 }
      .elsewhen(state === s_query_read12)    { state := s_query_read14 }
      .elsewhen(state === s_query_read14)    { state := s_query_read16 }
      .elsewhen(state === s_query_read16)    { state := s_idle }
      .otherwise                             { state := s_idle }
   }
   // *********************************************************************//
}

.elsewhen(state === s_query_read14)    { vault0.io.rdcol0 := 12.U }


/* Author: computes Max of N numbers */
package pythia

import chisel3._

class MaxN extends Module
{
   // private def Max2(x:UInt, y:UInt) = Mux(x>=y, x, y)

   val io = IO(new Bundle{
      val nums = Input(Vec(3, UInt(16.W)))
      val ids = Input(Vec(3, UInt(4.W)))
      val maxNum = Output(UInt(16.W))
      val maxId = Output(UInt(4.W))
   })

   io.maxNum := 0.U
   io.maxId := 0.U

   // TODO: make the ugly definition more generic
   when(io.nums(0) >= io.nums(1)){
      when(io.nums(0) >= io.nums(2)){
         io.maxId := io.ids(0)
         io.maxNum := io.nums(0)
      }.otherwise{
         io.maxId := io.ids(2)
         io.maxNum := io.nums(2)
      }
   }.otherwise{
      when(io.nums(1) >= io.nums(2)){

         io.maxId := io.ids(1)
         io.maxNum := io.nums(1)
      }.otherwise{
         io.maxId := io.ids(2)


/**********************************************************
 * Author: Rahul Bera
 * Description: Implements a 128x16 plane of QVStore
 * Read ports: 2
 * Write ports: 1
 * ********************************************************/

package pythia

import chisel3._
import chisel3.util._

class Plane extends Module {
  val io = IO(new Bundle {
    val we = Input(Bool())
    val wrrow = Input(UInt(7.W))
    val wrcol = Input(UInt(4.W))
    val wrdata = Input(UInt(16.W))
    val re = Input(Bool())
    val rdrow0 = Input(UInt(7.W))
    val rdcol0 = Input(UInt(4.W))
    val rddata0 = Output(UInt(16.W))
    val rdrow1 = Input(UInt(7.W))
    val rdcol1 = Input(UInt(4.W))
    val rddata1 = Output(UInt(16.W))
  })

  val mem = Mem(2048, UInt(16.W))

  val rdindex0 = Wire(UInt(11.W))
  val rdindex1 = Wire(UInt(11.W))
  val wrindex = Wire(UInt(11.W))
  rdindex0 := (io.rdrow0 << 4) + io.rdcol0
  rdindex1 := (io.rdrow1 << 4) + io.rdcol1

  when(io.re) {
    io.rddata0 := mem(rdindex0)
    io.rddata1 := mem(rdindex1)
    // printf("[PLANE READ] row0 %d col0 %d index0 %d val %d\n", io.rdrow0, io.rdcol0, rdindex0, io.rddata0)
    // printf("[PLANE READ] row1 %d col1 %d index1 %d val %d\n", io.rdrow1, io.rdcol1, rdindex1, io.rddata1)
  }
}

  wrindex := (io.wrrow << 4) + io.wrcol

  when(io.we) {
    mem(wrindex) := io.wrdata
    // printf("[PLANE WRITE] row %d col %d index %d val %d\n", io.wrrow, io.wrcol, wrindex, io.wrdata)