// See LICENSE for license details package barstools.iocell.chisel import chisel3._ import chisel3.util.{Cat, HasBlackBoxResource} import chisel3.experimental.{Analog, BaseModule, DataMirror, IO} // The following four IO cell bundle types are bare-minimum functional connections // for modeling 4 different IO cell scenarios. The intention is that the user // would create wrapper modules that extend these interfaces with additional // control signals. These are loosely similar to the sifive-blocks PinCtrl bundles // (https://github.com/sifive/sifive-blocks/blob/master/src/main/scala/devices/pinctrl/PinCtrl.scala), // but we want to avoid a dependency on an external libraries. /** The base IO bundle for an analog signal (typically something with no digital buffers inside) * pad: off-chip (external) connection * core: internal connection */ class AnalogIOCellBundle extends Bundle { val pad = Analog(1.W) // Pad/bump signal (off-chip) val core = Analog(1.W) // core signal (on-chip) } /** The base IO bundle for a signal with runtime-controllable direction * pad: off-chip (external) connection * i: input to chip logic (output from IO cell) * ie: enable signal for i * o: output from chip logic (input to IO cell) * oe: enable signal for o */ class DigitalGPIOCellBundle extends Bundle { val pad = Analog(1.W) val i = Output(Bool()) val ie = Input(Bool()) val o = Input(Bool()) val oe = Input(Bool()) } /** The base IO bundle for a digital output signal * pad: off-chip (external) connection * o: output from chip logic (input to IO cell) * oe: enable signal for o */ class DigitalOutIOCellBundle extends Bundle { val pad = Output(Bool()) val o = Input(Bool()) val oe = Input(Bool()) } /** The base IO bundle for a digital input signal * pad: off-chip (external) connection * i: input to chip logic (output from IO cell) * ie: enable signal for i */ class DigitalInIOCellBundle extends Bundle { val pad = Input(Bool()) val i = Output(Bool()) val ie = Input(Bool()) } trait IOCell extends BaseModule { var iocell_name : Option[String] = None /** Set IOCell name * @param s Proposed name for the IOCell * * @return An inherited IOCell with given the proposed name */ def suggestName(s: String) : this.type = { iocell_name = Some(s) super.suggestName(s) } } trait AnalogIOCell extends IOCell { val io: AnalogIOCellBundle } trait DigitalGPIOCell extends IOCell { val io: DigitalGPIOCellBundle } trait DigitalInIOCell extends IOCell { val io: DigitalInIOCellBundle } trait DigitalOutIOCell extends IOCell { val io: DigitalOutIOCellBundle } // The following Generic IO cell black boxes have verilog models that mimic a very simple // implementation of an IO cell. For building a real chip, it is important to implement // and use similar classes which wrap the foundry-specific IO cells. abstract class GenericIOCell extends BlackBox with HasBlackBoxResource { addResource("/barstools/iocell/vsrc/IOCell.v") } class GenericAnalogIOCell extends GenericIOCell with AnalogIOCell { val io = IO(new AnalogIOCellBundle) } class GenericDigitalGPIOCell extends GenericIOCell with DigitalGPIOCell { val io = IO(new DigitalGPIOCellBundle) } class GenericDigitalInIOCell extends GenericIOCell with DigitalInIOCell { val io = IO(new DigitalInIOCellBundle) } class GenericDigitalOutIOCell extends GenericIOCell with DigitalOutIOCell { val io = IO(new DigitalOutIOCellBundle) } trait IOCellTypeParams { def analog(): AnalogIOCell def gpio(): DigitalGPIOCell def input(): DigitalInIOCell def output(): DigitalOutIOCell } case class GenericIOCellParams() extends IOCellTypeParams { def analog() = Module(new GenericAnalogIOCell) def gpio() = Module(new GenericDigitalGPIOCell) def input() = Module(new GenericDigitalInIOCell) def output() = Module(new GenericDigitalOutIOCell) } object IOCell { /** From within a RawModule or MultiIOModule context, generate new module IOs from a given * signal and return the new IO and a Seq containing all generated IO cells. * @param coreSignal The signal onto which to add IO cells * @param name An optional name or name prefix to use for naming IO cells * @param abstractResetAsAsync When set, will coerce abstract resets to * AsyncReset, and otherwise to Bool (sync reset) * @return A tuple of (the generated IO data node, a Seq of all generated IO cell instances) */ def generateIOFromSignal[T <: Data]( coreSignal: T, name: String, typeParams: IOCellTypeParams = GenericIOCellParams(), abstractResetAsAsync: Boolean = false ): (T, Seq[IOCell]) = { val padSignal = IO(DataMirror.internal.chiselTypeClone[T](coreSignal)).suggestName(name) val resetFn = if (abstractResetAsAsync) toAsyncReset else toSyncReset val iocells = IOCell.generateFromSignal(coreSignal, padSignal, Some(s"iocell_$name"), typeParams, resetFn) (padSignal, iocells) } /** Connect two identical signals together by adding IO cells between them and return a Seq * containing all generated IO cells. * @param coreSignal The core-side (internal) signal onto which to connect/add IO cells * @param padSignal The pad-side (external) signal onto which to connect IO cells * @param name An optional name or name prefix to use for naming IO cells * @return A Seq of all generated IO cell instances */ val toSyncReset: (Reset) => Bool = _.asBool() val toAsyncReset: (Reset) => AsyncReset = _.asAsyncReset def generateFromSignal[T <: Data, R <: Reset]( coreSignal: T, padSignal: T, name: Option[String] = None, typeParams: IOCellTypeParams = GenericIOCellParams(), concretizeResetFn: (Reset) => R = toSyncReset ): Seq[IOCell] = { def genCell[T <: Data]( castToBool: (T) => Bool, castFromBool: (Bool) => T )(coreSignal: T, padSignal: T ): Seq[IOCell] = { DataMirror.directionOf(coreSignal) match { case ActualDirection.Input => { val iocell = typeParams.input() name.foreach(n => { iocell.suggestName(n) }) coreSignal := castFromBool(iocell.io.i) iocell.io.ie := true.B iocell.io.pad := castToBool(padSignal) Seq(iocell) } case ActualDirection.Output => { val iocell = typeParams.output() name.foreach(n => { iocell.suggestName(n) }) iocell.io.o := castToBool(coreSignal) iocell.io.oe := true.B padSignal := castFromBool(iocell.io.pad) Seq(iocell) } case _ => throw new Exception(s"Signal does not have a direction and cannot be matched to an IOCell") } } def genCellForClock = genCell[Clock](_.asUInt.asBool, _.asClock) _ def genCellForAsyncReset = genCell[AsyncReset](_.asBool, _.asAsyncReset) _ def genCellForAbstractReset = genCell[Reset](_.asBool, concretizeResetFn) _ (coreSignal, padSignal) match { case (coreSignal: Analog, padSignal: Analog) => { if (coreSignal.getWidth == 0) { Seq() } else { require( coreSignal.getWidth == 1, "Analogs wider than 1 bit are not supported because we can't bit-select Analogs (https://github.com/freechipsproject/chisel3/issues/536)" ) val iocell = typeParams.analog() name.foreach(n => iocell.suggestName(n)) iocell.io.core <> coreSignal padSignal <> iocell.io.pad Seq(iocell) } } case (coreSignal: Clock, padSignal: Clock) => genCellForClock(coreSignal, padSignal) case (coreSignal: AsyncReset, padSignal: AsyncReset) => genCellForAsyncReset(coreSignal, padSignal) case (coreSignal: Bits, padSignal: Bits) => { require(padSignal.getWidth == coreSignal.getWidth, "padSignal and coreSignal must be the same width") if (padSignal.getWidth == 0) { // This dummy assignment will prevent invalid firrtl from being emitted DataMirror.directionOf(coreSignal) match { case ActualDirection.Input => coreSignal := 0.U case _ => {} } Seq() } else { DataMirror.directionOf(coreSignal) match { case ActualDirection.Input => { val iocells = padSignal.asBools.zipWithIndex.map { case (sig, i) => val iocell = typeParams.input() // Note that we are relying on chisel deterministically naming this in the index order (which it does) // This has the side-effect of naming index 0 with no _0 suffix, which is how chisel names other signals // An alternative solution would be to suggestName(n + "_" + i) name.foreach(n => { iocell.suggestName(n) }) iocell.io.pad := sig iocell.io.ie := true.B iocell } // Note that the reverse here is because Cat(Seq(a,b,c,d)) yields abcd, but a is index 0 of the Seq coreSignal := Cat(iocells.map(_.io.i).reverse) iocells } case ActualDirection.Output => { val iocells = coreSignal.asBools.zipWithIndex.map { case (sig, i) => val iocell = typeParams.output() // Note that we are relying on chisel deterministically naming this in the index order (which it does) // This has the side-effect of naming index 0 with no _0 suffix, which is how chisel names other signals // An alternative solution would be to suggestName(n + "_" + i) name.foreach(n => { iocell.suggestName(n) }) iocell.io.o := sig iocell.io.oe := true.B iocell } // Note that the reverse here is because Cat(Seq(a,b,c,d)) yields abcd, but a is index 0 of the Seq padSignal := Cat(iocells.map(_.io.pad).reverse) iocells } case _ => throw new Exception("Bits signal does not have a direction and cannot be matched to IOCell(s)") } } } case (coreSignal: Reset, padSignal: Reset) => genCellForAbstractReset(coreSignal, padSignal) case (coreSignal: Vec[_], padSignal: Vec[_]) => { require(padSignal.size == coreSignal.size, "size of Vec for padSignal and coreSignal must be the same") coreSignal.zip(padSignal).zipWithIndex.foldLeft(Seq.empty[IOCell]) { case (total, ((core, pad), i)) => val ios = IOCell.generateFromSignal(core, pad, name.map(_ + "_" + i), typeParams) total ++ ios } } case (coreSignal: Record, padSignal: Record) => { coreSignal.elements.foldLeft(Seq.empty[IOCell]) { case (total, (eltName, core)) => val pad = padSignal.elements(eltName) val ios = IOCell.generateFromSignal(core, pad, name.map(_ + "_" + eltName), typeParams) total ++ ios } } case _ => { throw new Exception("Oops, I don't know how to handle this signal.") } } } }