From 083100b5732c6d9146dce0bd628a741f299bd812 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Sat, 25 May 2024 15:04:30 +0300 Subject: [PATCH] SB_IO use correct for simple IO (no tristate). --- .../ee/hrzn/chryse/chisel/DirectionOf.scala | 19 ++++++- .../ee/hrzn/chryse/platform/ChryseTop.scala | 38 ++++++------- .../platform/PlatformBoardResources.scala | 1 + .../hrzn/chryse/platform/ice40/ICE40Top.scala | 53 ++++++++++++++++--- .../hrzn/chryse/platform/ice40/PinType.scala | 3 +- .../hrzn/chryse/platform/ice40/SB_GB_IO.scala | 4 +- .../ee/hrzn/chryse/platform/ice40/SB_IO.scala | 22 ++++---- .../chryse/platform/resource/Connector.scala | 4 ++ .../hrzn/chryse/platform/resource/InOut.scala | 5 ++ .../platform/resource/ResourceBase.scala | 2 + .../platform/resource/ResourceData.scala | 32 ++++++----- .../chryse/platform/resource/SPIFlash.scala | 10 ++++ .../hrzn/chryse/platform/resource/UART.scala | 5 ++ .../platform/PlatformBoardResourcesSpec.scala | 30 +++++------ 14 files changed, 162 insertions(+), 66 deletions(-) diff --git a/src/main/scala/ee/hrzn/chryse/chisel/DirectionOf.scala b/src/main/scala/ee/hrzn/chryse/chisel/DirectionOf.scala index 0595012..d3f95af 100644 --- a/src/main/scala/ee/hrzn/chryse/chisel/DirectionOf.scala +++ b/src/main/scala/ee/hrzn/chryse/chisel/DirectionOf.scala @@ -2,10 +2,25 @@ package ee.hrzn.chryse.chisel import chisel3._ +import scala.language.implicitConversions + private[chryse] object DirectionOf { - def apply[T <: Data](data: T): SpecifiedDirection = + def apply[T <: Data](data: T): Direction = classOf[Data] .getMethod("specifiedDirection") // private[chisel3] .invoke(data) - .asInstanceOf[SpecifiedDirection] + .asInstanceOf[SpecifiedDirection] match { + case SpecifiedDirection.Input => Input + case SpecifiedDirection.Output => Output + case dir => throw new Exception(s"unhandled direction $dir") + } + + sealed trait Direction + final case object Input extends Direction + final case object Output extends Direction + + implicit val dir2SpecifiedDirection: Direction => SpecifiedDirection = { + case Input => SpecifiedDirection.Input + case Output => SpecifiedDirection.Output + } } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala b/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala index 46b370d..e2371a9 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala @@ -5,6 +5,7 @@ import chisel3.experimental.noPrefix import ee.hrzn.chryse.chisel.DirectionOf import scala.collection.mutable +import scala.language.existentials import scala.language.implicitConversions trait ChryseTop extends RawModule { @@ -20,7 +21,20 @@ trait ChryseTop extends RawModule { protected def platformConnect( name: String, res: resource.ResourceData[_ <: Data], - ): Option[Data] = None + ): Option[(Data, Data)] = None + + protected def platformPort( + res: resource.ResourceData[_ <: Data], + topIo: Data, + portIo: Data, + ): Unit = { + DirectionOf(topIo) match { + case DirectionOf.Input => + topIo := portIo + case DirectionOf.Output => + portIo := topIo + } + } protected def connectResources( platform: PlatformBoard[_ <: PlatformBoardResources], @@ -47,27 +61,15 @@ trait ChryseTop extends RawModule { clock.get := noPrefix(IO(Input(Clock())).suggestName(name)) case _ => - val topIo: Option[Data] = platformConnect(name, res) match { - case Some(t) => + platformConnect(name, res) match { + case Some((topIo, portIo)) => connected += name -> res.pinId.get - Some(t) + platformPort(res, topIo, portIo) case None => if (res.ioInst.isDefined) { connected += name -> res.pinId.get - Some(res.makeIoConnection()) - } else None - } - topIo match { - case None => - case Some(t) => - val port = IO(res.makeIo()).suggestName(name) - DirectionOf(port) match { - case SpecifiedDirection.Input => - t := port - case SpecifiedDirection.Output => - port := t - case dir => - throw new Exception(s"unhandled direction: $dir") + val (topIo, portIo) = res.makeIoConnection() + platformPort(res, topIo, portIo) } } } diff --git a/src/main/scala/ee/hrzn/chryse/platform/PlatformBoardResources.scala b/src/main/scala/ee/hrzn/chryse/platform/PlatformBoardResources.scala index de7a6fd..13cdb37 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/PlatformBoardResources.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/PlatformBoardResources.scala @@ -15,6 +15,7 @@ abstract class PlatformBoardResources { f.setAccessible(true) f.get(this) match { case res: ResourceBase => + res.setDefaultAttributes(defaultAttributes) res.setName(f.getName()) case _ => } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala index 30224bf..498486d 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala @@ -1,9 +1,12 @@ package ee.hrzn.chryse.platform.ice40 import chisel3._ +import chisel3.experimental.IntParam +import chisel3.experimental.StringParam import chisel3.experimental.noPrefix import chisel3.util._ import chisel3.util.experimental.forceName +import ee.hrzn.chryse.chisel.DirectionOf import ee.hrzn.chryse.platform.ChryseTop import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources @@ -19,21 +22,59 @@ class ICE40Top[Top <: Module]( override protected def platformConnect( name: String, res: resource.ResourceData[_ <: Data], - ): Option[Data] = { + ): Option[(Data, Data)] = { if (name == "ubtn" && ubtn_reset.isDefined) { if (res.ioInst.isDefined) throw new Exception("ubtnReset requested but ubtn used in design") - // ubtn_reset.get := IO(res.makeIo()).suggestName("ubtn") - val topIo = Wire(res.makeIo()) + // XXX: do we need to bother hooking up res.topIoInst/portIoInst? + + val topIo = Wire(res.makeIo()).suggestName("ubtn_top") ubtn_reset.get := topIo - return Some(topIo) - } + val portIo = IO(res.makeIo()).suggestName("ubtn") + return Some((topIo, portIo)) + } None } - // TODO (iCE40): actually create IO buffers. + override protected def platformPort( + res: resource.ResourceData[_ <: Data], + topIo: Data, + portIo: Data, + ) = { + // when we do DDR we'll need to use PIN_INPUT_DDR. + val i_type = PinType.PIN_INPUT + + // as above, PIN_OUTPUT_{REGISTERED,DDR}_ENABLE_REGISTERED + val o_type = DirectionOf(portIo) match { + case DirectionOf.Input => PinType.PIN_NO_OUTPUT + case DirectionOf.Output => PinType.PIN_OUTPUT_TRISTATE + } + + val buffer = Module( + new SB_IO( + i_type | o_type, + res.attributes("IO_STANDARD").asInstanceOf[StringParam].value, + res.attributes + .get("PULLUP") + .map(_.asInstanceOf[IntParam].value == 1) + .getOrElse(false), + ), + ).suggestName(s"${res.name.get}_SB_IO") + + DirectionOf(portIo) match { + case DirectionOf.Input => + buffer.PACKAGE_PIN := portIo + topIo := buffer.D_IN_0 + buffer.OUTPUT_ENABLE := DontCare + buffer.D_OUT_0 := DontCare + case DirectionOf.Output => + portIo := buffer.PACKAGE_PIN + buffer.OUTPUT_ENABLE := true.B + buffer.D_OUT_0 := topIo + } + } private val clki = Wire(Clock()) diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/PinType.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/PinType.scala index 048b414..79cd0e7 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/PinType.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/PinType.scala @@ -2,7 +2,8 @@ package ee.hrzn.chryse.platform.ice40 // See SiliconBlue ICEā„¢ Technology Library. object PinType { - val PIN_INPUT = 0x1 + val PIN_INPUT = 0x1 + val PIN_INPUT_DDR = 0x0 // REGISTERED val PIN_NO_OUTPUT = 0x0 val PIN_OUTPUT = 0x18 diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_GB_IO.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_GB_IO.scala index e103f14..d646c24 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_GB_IO.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_GB_IO.scala @@ -3,11 +3,11 @@ package ee.hrzn.chryse.platform.ice40 import chisel3._ import chisel3.experimental.ExtModule -class SB_GB_IO(pinType: Int = (PinType.PIN_INPUT | PinType.PIN_NO_OUTPUT)) +class SB_GB_IO extends ExtModule( Map( "IO_STANDARD" -> IOStandard.LVCMOS, - "PIN_TYPE" -> pinType, + "PIN_TYPE" -> (PinType.PIN_INPUT | PinType.PIN_NO_OUTPUT), ), ) { val PACKAGE_PIN = IO(Input(Clock())) diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_IO.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_IO.scala index 92dd249..7fd2429 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_IO.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/SB_IO.scala @@ -16,17 +16,19 @@ class SB_IO( ), ) { - private def genPin(): Data = { - if (pinType == PinType.PIN_INPUT) - Input(Bool()) - else if (pinType == PinType.PIN_OUTPUT) + // XXX: hyperspecific to ICE40Top's SB_IO generation and doesn't support + // tristates. + private val isOutput = (pinType & PinType.PIN_OUTPUT_TRISTATE) != 0 + + private def genPin(): Bool = { + if (isOutput) Output(Bool()) - else if (pinType == (PinType.PIN_INPUT | PinType.PIN_OUTPUT_TRISTATE)) - Analog(1.W) else - throw new IllegalArgumentException(s"unhandled pinType: $pinType") - + Input(Bool()) } - val PACKAGE_PIN = IO(genPin()) - val GLOBAL_BUFFER_OUTPUT = IO(genPin()) + + val PACKAGE_PIN = IO(genPin()) + val OUTPUT_ENABLE = IO(Input(Bool())) + val D_IN_0 = IO(Output(Bool())) + val D_OUT_0 = IO(Input(Bool())) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/Connector.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/Connector.scala index 9e3161b..a8d7058 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/Connector.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/Connector.scala @@ -1,6 +1,7 @@ package ee.hrzn.chryse.platform.resource import chisel3._ +import chisel3.experimental.Param class Connector[Ix, E <: ResourceSinglePin]( gen: => E, @@ -17,6 +18,9 @@ class Connector[Ix, E <: ResourceSinglePin]( def setName(name: String): Unit = mappings.foreach { case (i, e) => e.setName(s"$name$i") } + def setDefaultAttributes(defaultAttributes: Map[String, Param]): Unit = + mappings.foreach(_._2.setDefaultAttributes(defaultAttributes)) + def data: Seq[ResourceData[_ <: Data]] = mappings.flatMap(_._2.data).toSeq } diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/InOut.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/InOut.scala index df72976..6a22218 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/InOut.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/InOut.scala @@ -27,6 +27,11 @@ class InOut extends ResourceBase with ResourceSinglePin { this } + def setDefaultAttributes(defaultAttributes: Map[String, Param]): Unit = { + i.setDefaultAttributes(defaultAttributes) + o.setDefaultAttributes(defaultAttributes) + } + def data: Seq[ResourceData[_ <: Data]] = Seq(i, o) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceBase.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceBase.scala index 91f61d1..788ee8f 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceBase.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceBase.scala @@ -1,12 +1,14 @@ package ee.hrzn.chryse.platform.resource import chisel3._ +import chisel3.experimental.Param import ee.hrzn.chryse.platform.PlatformBoardResources import scala.collection.mutable.ArrayBuffer trait ResourceBase { def setName(name: String): Unit + def setDefaultAttributes(defaultAttributes: Map[String, Param]): Unit def data: Seq[ResourceData[_ <: Data]] } diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceData.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceData.scala index b9581d4..59caf72 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceData.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/ResourceData.scala @@ -19,13 +19,14 @@ abstract class ResourceData[HW <: Data](gen: => HW, invert: Boolean = false) final private[chryse] var pinId: Option[Pin] = None final var name: Option[String] = None final protected var _invert = invert - final protected var _attribs = Map[String, Param]() + final var attributes = Map[String, Param]() // Should return Chisel datatype with Input/Output attached. def makeIo(): HW = gen - final private[chryse] var ioInst: Option[HW] = None - final private[chryse] var topIoInst: Option[HW] = None + final private[chryse] var ioInst: Option[HW] = None + final private[chryse] var topIoInst: Option[HW] = None + final private[chryse] var portIoInst: Option[HW] = None /* Instantiate an IO in the module at the point of connecting to this * resource. These will be connected to in turn by the platform toplevel @@ -40,24 +41,27 @@ abstract class ResourceData[HW <: Data](gen: => HW, invert: Boolean = false) } } - final def makeIoConnection(): HW = { + final def makeIoConnection(): (HW, HW) = { if (topIoInst.isDefined) throw new IllegalStateException("topIoInst already defined") - // val topIo = IO(makeIo()).suggestName(name.get) - val topIo = Wire(makeIo()) // .suggestName(name.get) + if (portIoInst.isDefined) + throw new IllegalStateException("portIoInst already defined") + val topIo = Wire(makeIo()).suggestName(s"${name.get}_top") topIoInst = Some(topIo) connectIo(ioInst.get, topIo) - topIo + + val portIo = IO(makeIo()).suggestName(name.get) + portIoInst = Some(portIo) + + (topIo, portIo) } protected def connectIo(user: HW, top: HW): Unit = { DirectionOf(top) match { - case SpecifiedDirection.Input => + case DirectionOf.Input => user := (if (!_invert) top else ~top.asInstanceOf[Bits]) - case SpecifiedDirection.Output => + case DirectionOf.Output => top := (if (!_invert) user else ~user.asInstanceOf[Bits]) - case dir => - throw new Exception(s"unhandled direction: $dir") } } @@ -69,10 +73,14 @@ abstract class ResourceData[HW <: Data](gen: => HW, invert: Boolean = false) } def withAttributes(attribs: (String, Param)*): this.type = { - _attribs = attribs.to(Map) + attributes = attribs.to(Map) this } + def setDefaultAttributes(defaultAttributes: Map[String, Param]): Unit = + for { (name, value) <- defaultAttributes if !attributes.isDefinedAt(name) } + attributes += name -> value + def data: Seq[ResourceData[_ <: Data]] = Seq(this) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/SPIFlash.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/SPIFlash.scala index 8e850c0..f56a19c 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/SPIFlash.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/SPIFlash.scala @@ -1,6 +1,7 @@ package ee.hrzn.chryse.platform.resource import chisel3._ +import chisel3.experimental.Param class SPIFlash extends ResourceBase { // TODO: DSPI, QSPI @@ -21,6 +22,15 @@ class SPIFlash extends ResourceBase { hold.setName(s"${name}_hold") } + def setDefaultAttributes(defaultAttributes: Map[String, Param]): Unit = { + cs.setDefaultAttributes(defaultAttributes) + clock.setDefaultAttributes(defaultAttributes) + copi.setDefaultAttributes(defaultAttributes) + cipo.setDefaultAttributes(defaultAttributes) + wp.setDefaultAttributes(defaultAttributes) + hold.setDefaultAttributes(defaultAttributes) + } + def onPins( csN: Pin, clock: Pin, diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/UART.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/UART.scala index a888623..c0fe70c 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/UART.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/UART.scala @@ -18,6 +18,11 @@ class UART extends ResourceBase { this } + def setDefaultAttributes(defaultAttributes: Map[String, Param]): Unit = { + rx.setDefaultAttributes(defaultAttributes) + tx.setDefaultAttributes(defaultAttributes) + } + def withAttributes(attribs: (String, Param)*): this.type = { rx.withAttributes(attribs: _*) tx.withAttributes(attribs: _*) diff --git a/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala b/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala index 2731ba6..a102912 100644 --- a/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala +++ b/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala @@ -54,13 +54,13 @@ class PlatformBoardResourcesSpec extends AnyFlatSpec with Matchers { // inverted components, uart.tx isn't. Note that what we poke and peek are // the signals on the pins, not transformed for the user, otherwise // there'd be nothing to test. - plat.resources.ubtn.topIoInst.get.poke(false.B) - plat.resources.ledg.topIoInst.get.expect(false.B) // inverted twice - plat.resources.uart.tx.topIoInst.get.expect(true.B) + plat.resources.ubtn.portIoInst.get.poke(false.B) + plat.resources.ledg.portIoInst.get.expect(false.B) // inverted twice + plat.resources.uart.tx.portIoInst.get.expect(true.B) - plat.resources.ubtn.topIoInst.get.poke(true.B) - plat.resources.ledg.topIoInst.get.expect(true.B) - plat.resources.uart.tx.topIoInst.get.expect(false.B) + plat.resources.ubtn.portIoInst.get.poke(true.B) + plat.resources.ledg.portIoInst.get.expect(true.B) + plat.resources.uart.tx.portIoInst.get.expect(false.B) } verilog.InterfaceExtractor(rtl) should contain( @@ -89,15 +89,15 @@ class PlatformBoardResourcesSpec extends AnyFlatSpec with Matchers { val plat = SimPlatform() simulate(plat(new InOutTop()(plat))) { c => for { v <- Seq(true, false) } { - plat.resources.uart.rx.topIoInst.get.poke(v.B) - plat.resources.pmod(2).i.topIoInst.get.poke(v.B) - plat.resources.ubtn.topIoInst.get.poke(v.B) - plat.resources.pmod(8).i.topIoInst.get.poke(v.B) - - plat.resources.pmod(1).o.topIoInst.get.expect(v.B) - plat.resources.uart.tx.topIoInst.get.expect(v.B) - plat.resources.pmod(7).o.topIoInst.get.expect((!v).B) - plat.resources.ledr.topIoInst.get.expect((!v).B) + plat.resources.uart.rx.portIoInst.get.poke(v.B) + plat.resources.pmod(2).i.portIoInst.get.poke(v.B) + plat.resources.ubtn.portIoInst.get.poke(v.B) + plat.resources.pmod(8).i.portIoInst.get.poke(v.B) + + plat.resources.pmod(1).o.portIoInst.get.expect(v.B) + plat.resources.uart.tx.portIoInst.get.expect(v.B) + plat.resources.pmod(7).o.portIoInst.get.expect((!v).B) + plat.resources.ledr.portIoInst.get.expect((!v).B) } }