diff --git a/iocell/src/main/resources/barstools/iocell/vsrc/Analog.v b/iocell/src/main/resources/barstools/iocell/vsrc/Analog.v new file mode 100644 index 00000000..0a9abf03 --- /dev/null +++ b/iocell/src/main/resources/barstools/iocell/vsrc/Analog.v @@ -0,0 +1,11 @@ +// See LICENSE for license details + +`timescale 1ns/1ps + +module AnalogConst #(CONST, WIDTH) ( + output [WIDTH-1:0] io +); + + assign io = CONST; + +endmodule diff --git a/iocell/src/main/resources/barstools/iocell/vsrc/IOCell.v b/iocell/src/main/resources/barstools/iocell/vsrc/IOCell.v new file mode 100644 index 00000000..b3ee47ce --- /dev/null +++ b/iocell/src/main/resources/barstools/iocell/vsrc/IOCell.v @@ -0,0 +1,46 @@ +// See LICENSE for license details + +`timescale 1ns/1ps + +module GenericAnalogIOCell( + inout pad, + inout core +); + + assign core = 1'bz; + assign pad = core; + +endmodule + +module GenericDigitalGPIOCell( + inout pad, + output i, + input ie, + input o, + input oe +); + + assign pad = oe ? o : 1'bz; + assign i = ie ? pad : 1'b0; + +endmodule + +module GenericDigitalInIOCell( + input pad, + output i, + input ie +); + + assign i = ie ? pad : 1'b0; + +endmodule + +module GenericDigitalOutIOCell( + output pad, + input o, + output oe +); + + assign pad = oe ? o : 1'bz; + +endmodule diff --git a/iocell/src/main/scala/chisel/Analog.scala b/iocell/src/main/scala/chisel/Analog.scala new file mode 100644 index 00000000..e1b4fc78 --- /dev/null +++ b/iocell/src/main/scala/chisel/Analog.scala @@ -0,0 +1,16 @@ +// See LICENSE for license details + +package barstools.iocell.chisel + +import chisel3._ +import chisel3.util.{HasBlackBoxResource} +import chisel3.experimental.{Analog, IntParam} + +class AnalogConst(value: Int, width: Int = 1) extends BlackBox(Map("CONST" -> IntParam(value), "WIDTH" -> IntParam(width))) with HasBlackBoxResource{ + val io = IO(new Bundle {val io = Analog(width.W) } ) + addResource("/barstools/iocell/vsrc/Analog.v") +} + +object AnalogConst { + def apply(value: Int, width: Int = 1) = Module(new AnalogConst(value, width)).io.io +} diff --git a/iocell/src/main/scala/chisel/IOCell.scala b/iocell/src/main/scala/chisel/IOCell.scala new file mode 100644 index 00000000..340a4e9d --- /dev/null +++ b/iocell/src/main/scala/chisel/IOCell.scala @@ -0,0 +1,248 @@ +// See LICENSE for license details + +package barstools.iocell.chisel + +import chisel3._ +import chisel3.util.{Cat, HasBlackBoxResource} +import chisel3.experimental.{Analog, 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()) +} + +abstract class IOCell extends BlackBox + +abstract class AnalogIOCell extends IOCell { + val io: AnalogIOCellBundle +} + +abstract class DigitalGPIOCell extends IOCell { + val io: DigitalGPIOCellBundle +} + +abstract class DigitalInIOCell extends IOCell { + val io: DigitalInIOCellBundle +} + +abstract class 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. + +trait IsGenericIOCell extends HasBlackBoxResource { + addResource("/barstools/iocell/vsrc/IOCell.v") +} + +class GenericAnalogIOCell extends AnalogIOCell with IsGenericIOCell { + val io = IO(new AnalogIOCellBundle) +} + +class GenericDigitalGPIOCell extends DigitalGPIOCell with IsGenericIOCell { + val io = IO(new DigitalGPIOCellBundle) +} + +class GenericDigitalInIOCell extends DigitalInIOCell with IsGenericIOCell { + val io = IO(new DigitalInIOCellBundle) +} + +class GenericDigitalOutIOCell extends DigitalOutIOCell with IsGenericIOCell { + val io = IO(new DigitalOutIOCellBundle) +} + +object IOCell { + + def genericAnalog() = Module(new GenericAnalogIOCell) + def genericGPIO() = Module(new GenericDigitalGPIOCell) + def genericInput() = Module(new GenericDigitalInIOCell) + def genericOutput() = Module(new GenericDigitalOutIOCell) + + /** + * 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 inFn A function to generate a DigitalInIOCell to use for input signals + * @param outFn A function to generate a DigitalOutIOCell to use for output signals + * @param anaFn A function to generate an AnalogIOCell to use for analog signals + * @return A tuple of (the generated IO data node, a Seq of all generated IO cell instances) + */ + def generateIOFromSignal[T <: Data](coreSignal: T, name: Option[String] = None, + inFn: () => DigitalInIOCell = IOCell.genericInput, + outFn: () => DigitalOutIOCell = IOCell.genericOutput, + anaFn: () => AnalogIOCell = IOCell.genericAnalog): (T, Seq[IOCell]) = + { + val padSignal = IO(DataMirror.internal.chiselTypeClone[T](coreSignal)) + val iocells = IOCell.generateFromSignal(coreSignal, padSignal, name, inFn, outFn, anaFn) + (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 + * @param inFn A function to generate a DigitalInIOCell to use for input signals + * @param outFn A function to generate a DigitalOutIOCell to use for output signals + * @param anaFn A function to generate an AnalogIOCell to use for analog signals + * @return A Seq of all generated IO cell instances + */ + def generateFromSignal[T <: Data](coreSignal: T, padSignal: T, name: Option[String] = None, + inFn: () => DigitalInIOCell = IOCell.genericInput, + outFn: () => DigitalOutIOCell = IOCell.genericOutput, + anaFn: () => AnalogIOCell = IOCell.genericAnalog): Seq[IOCell] = + { + (coreSignal: T, padSignal: T) 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 = anaFn() + name.foreach(n => iocell.suggestName(n)) + iocell.io.core <> coreSignal + padSignal <> iocell.io.pad + Seq(iocell) + } + } + case (coreSignal: Clock, padSignal: Clock) => { + DataMirror.directionOf(coreSignal) match { + case ActualDirection.Input => { + val iocell = inFn() + name.foreach(n => iocell.suggestName(n)) + coreSignal := iocell.io.i.asClock + iocell.io.ie := true.B + iocell.io.pad := padSignal.asUInt.asBool + Seq(iocell) + } + case ActualDirection.Output => { + val iocell = outFn() + name.foreach(n => iocell.suggestName(n)) + iocell.io.o := coreSignal.asUInt.asBool + iocell.io.oe := true.B + padSignal := iocell.io.pad.asClock + Seq(iocell) + } + case _ => throw new Exception("Clock signal does not have a direction and cannot be matched to an IOCell") + } + } + 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 = inFn() + // 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 = outFn() + // 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: Vec[Data], padSignal: Vec[Data]) => { + 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), inFn, outFn, anaFn) + 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), inFn, outFn, anaFn) + total ++ ios + } + } + case _ => { throw new Exception("Oops, I don't know how to handle this signal.") } + } + } + +}