// SPDX-License-Identifier: Apache-2.0

package chiselTests

import chisel3._
import chisel3.stage.ChiselStage
import chisel3.util._
import chisel3.testers.{BasicTester, TesterDriver}
import chisel3.experimental.{Analog, BaseModule, attach}

// IO for Modules that just connect bus to out
class AnalogReaderIO extends Bundle {
  val bus = Analog(32.W)
  val out = Output(UInt(32.W))
}
// IO for Modules that drive bus from in (there should be only 1)
class AnalogWriterIO extends Bundle {
  val bus = Analog(32.W)
  val in = Input(UInt(32.W))
}

trait AnalogReader {
  def out: UInt
  def bus: Analog
}

class AnalogReaderBlackBox extends BlackBox with AnalogReader {
  val io = IO(new AnalogReaderIO)
  def out = io.out
  def bus = io.bus
}

class AnalogReaderWrapper extends Module with AnalogReader {
  val io = IO(new AnalogReaderIO)
  def out = io.out
  def bus = io.bus
  val mod = Module(new AnalogReaderBlackBox)
  io <> mod.io
}
class AnalogWriterBlackBox extends BlackBox {
  val io = IO(new AnalogWriterIO)
}
// Connects two Analog ports
class AnalogConnector extends Module {
  val io = IO(new Bundle {
    val bus1 = Analog(32.W)
    val bus2 = Analog(32.W)
  })
  io.bus1 <> io.bus2
}

class VecAnalogReaderWrapper extends RawModule with AnalogReader {
  val vecbus = IO(Vec(1, Analog(32.W)))
  val out = IO(Output(UInt(32.W)))
  val mod = Module(new AnalogReaderBlackBox)
  def bus = vecbus(0)
  mod.io.bus <> bus
  out := mod.io.out
}

class VecBundleAnalogReaderWrapper extends RawModule with AnalogReader {
  val vecBunBus = IO(Vec(1, new Bundle {
    val analog = Analog(32.W)
  }))
  def bus = vecBunBus(0).analog
  val out = IO(Output(UInt(32.W)))
  val mod = Module(new AnalogReaderBlackBox)
  mod.io.bus <> bus
  out := mod.io.out
}

// Parent class for tests connecing up AnalogReaders and AnalogWriters
abstract class AnalogTester extends BasicTester {
  final val BusValue = "hdeadbeef".U

  final val (cycle, done) = Counter(true.B, 2)
  when (done) { stop() }

  final val writer = Module(new AnalogWriterBlackBox)
  writer.io.in := BusValue

  final def check(reader: BaseModule with AnalogReader): Unit =
    assert(reader.out === BusValue)
}

class AnalogSpec extends ChiselFlatSpec with Utils {
  behavior of "Analog"

  it should "NOT be bindable to registers" in {
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new Module {
        val io = IO(new Bundle {})
        val reg = Reg(Analog(32.W))
      }}
    }
  }

  it should "NOT be bindable to a direction" in {
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new Module {
        val io = IO(new Bundle {
          val a = Input(Analog(32.W))
        })
      }}
    }
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new Module {
        val io = IO(new Bundle {
          val a = Output(Analog(32.W))
        })
      }}
    }
  }

  it should "be flippable" in {
    ChiselStage.elaborate { new Module {
      val io = IO(new Bundle {
        val a = Flipped(Analog(32.W))
      })
    }}
  }

  // There is no binding on the type of a memory
  // Should this be an error?
  ignore should "NOT be a legal type for Mem" in {
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new Module {
        val io = IO(new Bundle {})
        val mem = Mem(16, Analog(32.W))
      }}
    }
  }

  it should "NOT be bindable to Mem ports" in {
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new Module {
        val io = IO(new Bundle {})
        val mem = Mem(16, Analog(32.W))
        val port = mem(5.U)
      }}
    }
  }

  // TODO This should probably be caught in Chisel
  // Also note this relies on executing Firrtl from Chisel directly
  it should "NOT be connectable to UInts" in {
    a [Exception] should be thrownBy {
      runTester { new BasicTester {
        val uint = WireDefault(0.U(32.W))
        val sint = Wire(Analog(32.W))
        sint := uint
      }}
    }
  }

  it should "work with 2 blackboxes bulk connected" in {
    assertTesterPasses(new AnalogTester {
      val mod = Module(new AnalogReaderBlackBox)
      mod.io.bus <> writer.io.bus
      check(mod)
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  it should "error if any bulk connected more than once" in {
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate(new Module {
        val io = IO(new Bundle {})
        val wires = List.fill(3)(Wire(Analog(32.W)))
        wires(0) <> wires(1)
        wires(0) <> wires(2)
      })
    }
    a [ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate(new Module {
        val io = IO(new Bundle {})
        val wires = List.fill(2)(Wire(Analog(32.W)))
        wires(0) <> DontCare
        wires(0) <> wires(1)
      })
    }
  }

  it should "allow DontCare connection" in {
    ChiselStage.elaborate(new Module {
      val io = IO(new Bundle {
        val a = Analog(1.W)
      })
      io.a := DontCare
    })
    ChiselStage.elaborate(new Module {
      val io = IO(new Bundle {
        val a = Analog(1.W)
      })
      io.a <> DontCare
    })
  }

  it should "work in bidirectional Aggregate wires" in {
    class MyBundle extends Bundle {
      val x = Input(UInt(8.W))
      val y = Analog(8.W)
    }
    ChiselStage.elaborate(new Module {
      val io = IO(new Bundle {
        val a = new MyBundle
      })
      val w = Wire(new MyBundle)
      w <> io.a
    })
    ChiselStage.elaborate(new Module {
      val io = IO(new Bundle {
        val a = Vec(1, new MyBundle)
      })
      val w = Wire(Vec(1, new MyBundle))
      w <> io.a
    })
  }

  it should "work with 3 blackboxes attached" in {
    assertTesterPasses(new AnalogTester {
      val mods = Seq.fill(2)(Module(new AnalogReaderBlackBox))
      attach(writer.io.bus, mods(0).io.bus, mods(1).io.bus)
      mods.foreach(check(_))
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  it should "work with 3 blackboxes separately attached via a wire" in {
    assertTesterPasses(new AnalogTester {
      val mods = Seq.fill(2)(Module(new AnalogReaderBlackBox))
      val busWire = Wire(Analog(32.W))
      attach(busWire, writer.io.bus)
      attach(busWire, mods(0).io.bus)
      attach(mods(1).io.bus, busWire)
      mods.foreach(check(_))
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  // This does not currently work in Verilator unless Firrtl does constant prop and dead code
  // elimination on these wires
  ignore should "work with intermediate wires attached to each other" in {
    assertTesterPasses(new AnalogTester {
      val mod = Module(new AnalogReaderBlackBox)
      val busWire = Seq.fill(2)(Wire(Analog(32.W)))
      attach(busWire(0), writer.io.bus)
      attach(busWire(1), mod.io.bus)
      attach(busWire(0), busWire(1))
      check(mod)
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  it should "work with blackboxes at different levels of the module hierarchy" in {
    assertTesterPasses(new AnalogTester {
      val mods = Seq(Module(new AnalogReaderBlackBox), Module(new AnalogReaderWrapper))
      val busWire = Wire(writer.io.bus.cloneType)
      attach(writer.io.bus, mods(0).bus, mods(1).bus)
      mods.foreach(check(_))
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  // This does not currently work in Verilator, but does work in VCS
  ignore should "support two analog ports in the same module" in {
    assertTesterPasses(new AnalogTester {
      val reader = Module(new AnalogReaderBlackBox)
      val connector = Module(new AnalogConnector)
      connector.io.bus1 <> writer.io.bus
      reader.io.bus <> connector.io.bus2
      check(reader)
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  it should "NOT support conditional connection of analog types" in {
    a [ChiselException] should be thrownBy {
      assertTesterPasses(new AnalogTester {
        val mod = Module(new AnalogReaderBlackBox)
        when (cycle > 3.U) {
          mod.io.bus <> writer.io.bus
        }
        check(mod)
      }, Seq("/chisel3/AnalogBlackBox.v"))
    }
  }

  it should "work with Vecs of Analog" in {
    assertTesterPasses(new AnalogTester {
      val mod = Module(new VecAnalogReaderWrapper)
      mod.bus <> writer.io.bus
      check(mod)
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }

  it should "work with Vecs of Bundles of Analog" in {
    assertTesterPasses(new AnalogTester {
      val mod = Module(new VecBundleAnalogReaderWrapper)
      mod.bus <> writer.io.bus
      check(mod)
    }, Seq("/chisel3/AnalogBlackBox.v"), TesterDriver.verilatorOnly)
  }
}
