From c6e3e006738a353d31da139cdd241bb7ee0ed082 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 29 May 2024 17:14:57 +0300 Subject: [PATCH] ecp5: very close. --- .../ee/hrzn/chryse/platform/ChryseTop.scala | 41 +++++++++---- .../hrzn/chryse/platform/PlatformBoard.scala | 6 +- .../chryse/platform/ecp5/ECP5Platform.scala | 60 +++++++++++++++---- .../hrzn/chryse/platform/ecp5/ECP5Top.scala | 29 +++++++++ .../ee/hrzn/chryse/platform/ecp5/LPF.scala | 39 ++++++++++++ .../platform/ecp5/OrangeCrabPlatform.scala | 5 +- .../chryse/platform/ecp5/ULX3SPlatform.scala | 23 +++---- .../chryse/platform/ice40/ICE40Platform.scala | 3 +- .../hrzn/chryse/platform/ice40/ICE40Top.scala | 35 ++++++----- .../platform/ice40/IceBreakerPlatform.scala | 42 +++++++------ .../ee/hrzn/chryse/platform/ice40/PCF.scala | 7 ++- .../chryse/platform/resource/Button.scala | 2 +- .../platform/resource/ClockSource.scala | 2 +- .../hrzn/chryse/platform/resource/LED.scala | 2 +- .../platform/PlatformBoardResourcesSpec.scala | 59 +++++++++++------- .../ee/hrzn/chryse/platform/SimPlatform.scala | 33 ++++++---- .../ee/hrzn/chryse/platform/SimTop.scala | 1 - .../hrzn/chryse/platform/ecp5/LPFSpec.scala | 32 ++++++++++ .../hrzn/chryse/platform/ice40/PCFSpec.scala | 12 ++-- 19 files changed, 316 insertions(+), 117 deletions(-) create mode 100644 src/main/scala/ee/hrzn/chryse/platform/ecp5/LPF.scala create mode 100644 src/test/scala/ee/hrzn/chryse/platform/ecp5/LPFSpec.scala diff --git a/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala b/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala index 2881fec..c1684d7 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala @@ -1,9 +1,12 @@ package ee.hrzn.chryse.platform import chisel3._ +import chisel3.experimental.Param import chisel3.experimental.noPrefix import ee.hrzn.chryse.chisel.directionOf +import ee.hrzn.chryse.platform.resource.ClockSource import ee.hrzn.chryse.platform.resource.Pin +import ee.hrzn.chryse.platform.resource.ResourceData import scala.collection.mutable import scala.language.existentials @@ -14,21 +17,23 @@ trait ChryseTop extends RawModule { case class ConnectedResource( pin: Pin, - frequencyHz: Option[Int], + attributes: Map[String, Param], + frequencyHz: Option[BigInt], ) - object ConnectedResource { - implicit def pin2Cr(pin: Pin): ConnectedResource = - ConnectedResource(pin, None) - } + sealed trait PlatformConnectResult + case class PlatformConnectResultUsePorts(topIo: Data, portIo: Data) + extends PlatformConnectResult + case object PlatformConnectResultFallthrough extends PlatformConnectResult + case object PlatformConnectResultNoop extends PlatformConnectResult protected def platformConnect( name: String, - res: resource.ResourceData[_ <: Data], - ): Option[(Data, Data)] = None + res: ResourceData[_ <: Data], + ): PlatformConnectResult = PlatformConnectResultFallthrough protected def platformPort[HW <: Data]( - res: resource.ResourceData[HW], + res: ResourceData[HW], topIo: Data, portIo: Data, ): Unit = { @@ -49,7 +54,7 @@ trait ChryseTop extends RawModule { for { res <- platform.resources.all } { val name = res.name.get res match { - case res: resource.ClockSource => + case res: ClockSource => if (res.ioInst.isDefined) { throw new Exception( "clock sources must be manually handled for now", @@ -60,21 +65,31 @@ trait ChryseTop extends RawModule { // can't then sink like we would in the resource.Base[_] case. connected += name -> ConnectedResource( res.pinId.get, + res.attributes, Some(platform.clockHz), ) clock.get := noPrefix(IO(Input(Clock())).suggestName(name)) case _ => platformConnect(name, res) match { - case Some((topIo, portIo)) => - connected += name -> res.pinId.get + case PlatformConnectResultUsePorts(topIo, portIo) => + connected += name -> ConnectedResource( + res.pinId.get, + res.attributes, + None, + ) platformPort(res, topIo, portIo) - case None => + case PlatformConnectResultFallthrough => if (res.ioInst.isDefined) { - connected += name -> res.pinId.get + connected += name -> ConnectedResource( + res.pinId.get, + res.attributes, + None, + ) val (topIo, portIo) = res.makeIoConnection() platformPort(res, topIo, portIo) } + case PlatformConnectResultNoop => } } } diff --git a/src/main/scala/ee/hrzn/chryse/platform/PlatformBoard.scala b/src/main/scala/ee/hrzn/chryse/platform/PlatformBoard.scala index 9a55154..c9fe732 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/PlatformBoard.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/PlatformBoard.scala @@ -6,15 +6,17 @@ import ee.hrzn.chryse.ChryseApp trait PlatformBoard[PBR <: PlatformBoardResources] extends ElaboratablePlatform { + type BuildResult + def yosysSynthCommand(top: String): String def build( chryse: ChryseApp, topPlatform: TopPlatform[_], jsonPath: String, - ): String + ): BuildResult - def program(binPath: String): Unit + def program(buildResult: BuildResult): Unit val resources: PBR } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Platform.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Platform.scala index 1787ebf..3b05a2f 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Platform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Platform.scala @@ -8,9 +8,11 @@ import ee.hrzn.chryse.tasks.BaseTask trait ECP5Platform { this: PlatformBoard[_ <: PlatformBoardResources] => type TopPlatform[Top <: Module] = ECP5Top[Top] + case class BuildResult(bitPath: String, svfPath: String) val ecp5Variant: ECP5Variant val ecp5Package: String + val ecp5Speed: Int override def apply[Top <: Module](genTop: => Top) = { resources.setNames() @@ -23,7 +25,7 @@ trait ECP5Platform { this: PlatformBoard[_ <: PlatformBoardResources] => chryse: ChryseApp, topPlatform: ECP5Top[_], jsonPath: String, - ): String = + ): BuildResult = buildImpl(this, chryse, topPlatform, jsonPath) private object buildImpl extends BaseTask { @@ -32,18 +34,56 @@ trait ECP5Platform { this: PlatformBoard[_ <: PlatformBoardResources] => chryse: ChryseApp, topPlatform: ECP5Top[_], jsonPath: String, - ): String = ??? + ): BuildResult = { + val name = chryse.name + + val lpfPath = s"$buildDir/${platform.id}/$name.lpf" + writePath(lpfPath, topPlatform.lpf.toString()) + + val textcfgPath = s"$buildDir/${platform.id}/$name.config" + val textcfgCu = CompilationUnit( + Some(jsonPath), + Seq(lpfPath), + textcfgPath, + Seq( + "nextpnr-ecp5", + "-q", + "--log", + s"$buildDir/${platform.id}/$name.tim", + "--json", + jsonPath, + "--lpf", + lpfPath, + "--textcfg", + textcfgPath, + ecp5Variant.arg, + "--package", + ecp5Package, + "--speed", + s"$ecp5Speed", + ), + ) + runCu(CmdStepPNR, textcfgCu) + + val bitPath = s"$buildDir/${platform.id}/$name.bit" + val svfPath = s"$buildDir/${platform.id}/$name.svf" + val bitCu = CompilationUnit( + Some(textcfgPath), + Seq(), + bitPath, + Seq("ecppack", "--input", textcfgPath, "--bit", bitPath, "--svf", + svfPath), + ) + runCu(CmdStepPack, bitCu) + + BuildResult(bitPath, svfPath) + } } - def program(binPath: String): Unit = - programImpl(binPath) + def program(bitAndSvf: BuildResult): Unit = + programImpl(bitAndSvf) private object programImpl extends BaseTask { - def apply(binPath: String): Unit = ??? + def apply(bitAndSvf: BuildResult): Unit = ??? } - - val nextpnrBinary = "nextpnr-ecp5" - lazy val nextpnrArgs = Seq(ecp5Variant.arg, "--package", ecp5Package) - val packBinary = "ecppack" - val programBinary = "dfu-util" } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Top.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Top.scala index e8336d8..2bf34a3 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Top.scala @@ -5,12 +5,23 @@ import ee.hrzn.chryse.platform.ChryseTop import ee.hrzn.chryse.platform.Platform import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources +import ee.hrzn.chryse.platform.resource.PinString +import ee.hrzn.chryse.platform.resource.ResourceData class ECP5Top[Top <: Module]( platform: PlatformBoard[_ <: PlatformBoardResources], genTop: => Top, ) extends RawModule with ChryseTop { + override protected def platformConnect( + name: String, + res: ResourceData[_ <: Data], + ): PlatformConnectResult = { + println(s"evaluating: $name / $res") + // TODO (ECP5): USRMCLK + PlatformConnectResultFallthrough + } + private val clki = Wire(Clock()) private val gsr0 = Wire(Bool()) @@ -32,5 +43,23 @@ class ECP5Top[Top <: Module]( private val top = withClockAndReset(clki, false.B)(Module(genTop)) + // TODO (ECP5): allow clock source override. + val connectedResources = connectResources(platform, Some(clki)) + + val lpf = LPF( + connectedResources + .flatMap { case (name, cr) => + cr.pin match { + case pin: PinString => + Some((name, (pin, cr.attributes))) + case _ => + None + } + } + .to(Map), + connectedResources + .flatMap { case (name, cr) => cr.frequencyHz.map((name, _)) } + .to(Map), + ) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/LPF.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/LPF.scala new file mode 100644 index 0000000..f711330 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/LPF.scala @@ -0,0 +1,39 @@ +package ee.hrzn.chryse.platform.ecp5 + +import chisel3.experimental.IntParam +import chisel3.experimental.Param +import chisel3.experimental.StringParam +import ee.hrzn.chryse.platform.resource.PinString + +final case class LPF( + ios: Map[String, (PinString, Map[String, Param])], + freqs: Map[String, BigInt], +) { + private val attr2String: Param => String = { + case IntParam(v) => + v.toString() + case StringParam(v) => v + case v => throw new Exception(s"unhandled attribute: $v") + } + + override def toString(): String = { + val sb = new StringBuilder + sb.append("BLOCK ASYNCPATHS;\n") + sb.append("BLOCK RESETPATHS;\n") + + for { (name, (pin, attrs)) <- ios } { + sb.append(s"LOCATE COMP \"$name\" SITE \"$pin\";\n") + if (!attrs.isEmpty) { + sb.append(s"IOBUF PORT \"$name\""); + for { (k, v) <- attrs } + sb.append(s" $k=${attr2String(v)}") + sb.append(";\n"); + } + } + + for { (name, freq) <- freqs } + sb.append(s"FREQUENCY PORT \"$name\" $freq HZ;\n") + + sb.toString() + } +} diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/OrangeCrabPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/OrangeCrabPlatform.scala index 6c730c7..bc98bc1 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/OrangeCrabPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/OrangeCrabPlatform.scala @@ -3,7 +3,7 @@ package ee.hrzn.chryse.platform.ecp5 import chisel3._ import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.resource +import ee.hrzn.chryse.platform.resource.ClockSource // TODO: restrict the variants to those the OrangeCrab was delivered with. case class OrangeCrabPlatform(ecp5Variant: ECP5Variant) @@ -13,10 +13,11 @@ case class OrangeCrabPlatform(ecp5Variant: ECP5Variant) val clockHz = 48_000_000 val ecp5Package = "csfBGA285" + val ecp5Speed = 8 val resources = new OrangeCrabPlatformResources } class OrangeCrabPlatformResources extends PlatformBoardResources { - val clock = resource.ClockSource(48_000_000).onPin("A9") + val clock = ClockSource(48_000_000).onPin("A9") } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ULX3SPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ULX3SPlatform.scala index 097ae5b..03bd9b5 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ULX3SPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ULX3SPlatform.scala @@ -4,7 +4,11 @@ import chisel3._ import chisel3.experimental.Param import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.resource +import ee.hrzn.chryse.platform.resource.Button +import ee.hrzn.chryse.platform.resource.ClockSource +import ee.hrzn.chryse.platform.resource.ResourceData +import ee.hrzn.chryse.platform.resource.SPIFlash +import ee.hrzn.chryse.platform.resource.UART // TODO: restrict the variants to those the ULX3S was delivered with. // TODO: try one of these: https://github.com/emard/ulx3s/blob/master/doc/MANUAL.md#programming-over-wifi-esp32-micropython @@ -14,7 +18,8 @@ case class ULX3SPlatform(ecp5Variant: ECP5Variant) val id = s"ulx3s-${ecp5Variant.id}" val clockHz = 25_000_000 - val ecp5Package = "caBGA381" + val ecp5Package = "CABGA381" + val ecp5Speed = 6 val resources = new ULX3SPlatformResources } @@ -22,18 +27,15 @@ case class ULX3SPlatform(ecp5Variant: ECP5Variant) class ULX3SPlatformResources extends PlatformBoardResources { override val defaultAttributes = Map("IO_TYPE" -> IOType.LVCMOS33) - val clock = resource.ClockSource(25_000_000).onPin("G2") + val clock = ClockSource(25_000_000).onPin("G2") val program = - resource.Button().inverted.onPin("M4").withAttributes("PULLMODE" -> "UP") + Button().inverted.onPin("M4").withAttributes("PULLMODE" -> "UP") // TODO: also expose RTS, DTR. - var uart = resource - .UART() + var uart = UART() .onPins(rx = "M1", tx = "L4") - var uartTxEnable = new resource.ResourceData[Bool](Bool()) { - name = Some("uartTxEnable") - } + var uartTxEnable = ResourceData(Output(Bool())).onPin("L3") // val leds = // resource @@ -41,8 +43,7 @@ class ULX3SPlatformResources extends PlatformBoardResources { // .onPins("B2", "C2", "C1", "D2", "D1", "E2", "E1", "H3") // .withAttributes("DRIVE" -> "4") - val spiFlash = resource - .SPIFlash() + val spiFlash = SPIFlash() .onPins( csN = "R2", clock = USRMCLK, copi = "W2", cipo = "V2", wpN = "Y2", holdN = "W1", diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Platform.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Platform.scala index 00db26f..44c4878 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Platform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Platform.scala @@ -8,6 +8,7 @@ import ee.hrzn.chryse.tasks.BaseTask trait ICE40Platform { this: PlatformBoard[_ <: PlatformBoardResources] => type TopPlatform[Top <: Module] = ICE40Top[Top] + type BuildResult = String val ice40Variant: ICE40Variant val ice40Package: String @@ -36,7 +37,7 @@ trait ICE40Platform { this: PlatformBoard[_ <: PlatformBoardResources] => val name = chryse.name val pcfPath = s"$buildDir/${platform.id}/$name.pcf" - writePath(pcfPath, topPlatform.lastPCF.get.toString()) + writePath(pcfPath, topPlatform.pcf.toString()) val ascPath = s"$buildDir/${platform.id}/$name.asc" val ascCu = CompilationUnit( 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 18e03d5..57d5964 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala @@ -10,7 +10,8 @@ import ee.hrzn.chryse.chisel.directionOf import ee.hrzn.chryse.platform.ChryseTop import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.resource +import ee.hrzn.chryse.platform.resource.PinInt +import ee.hrzn.chryse.platform.resource.ResourceData import scala.collection.mutable @@ -21,8 +22,8 @@ class ICE40Top[Top <: Module]( with ChryseTop { override protected def platformConnect( name: String, - res: resource.ResourceData[_ <: Data], - ): Option[(Data, Data)] = { + res: ResourceData[_ <: Data], + ): PlatformConnectResult = { if (name == "ubtn" && ubtn_reset.isDefined) { if (res.ioInst.isDefined) throw new Exception("ubtnReset requested but ubtn used in design") @@ -33,13 +34,14 @@ class ICE40Top[Top <: Module]( ubtn_reset.get := topIo val portIo = IO(res.makeIo()).suggestName("ubtn") - return Some((topIo, portIo)) + return PlatformConnectResultUsePorts(topIo, portIo) } - None + + PlatformConnectResultFallthrough } override protected def platformPort[HW <: Data]( - res: resource.ResourceData[HW], + res: ResourceData[HW], topIo: Data, portIo: Data, ) = { @@ -119,17 +121,14 @@ class ICE40Top[Top <: Module]( private val connectedResources = connectResources(platform, Some(clki)) - val lastPCF = Some( - PCF( - connectedResources - .map { case (name, cr) => - // iCE40 has no PinPlatforms. - (name, cr.pin.asInstanceOf[resource.PinConnected]) - } - .to(Map), - connectedResources - .flatMap { case (name, cr) => cr.frequencyHz.map((name, _)) } - .to(Map), - ), + val pcf = PCF( + connectedResources + .map { case (name, cr) => + (name, cr.pin.asInstanceOf[PinInt]) + } + .to(Map), + connectedResources + .flatMap { case (name, cr) => cr.frequencyHz.map((name, _)) } + .to(Map), ) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/IceBreakerPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/IceBreakerPlatform.scala index 6fb215e..b0d2a5d 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/IceBreakerPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/IceBreakerPlatform.scala @@ -4,7 +4,13 @@ import chisel3._ import chisel3.experimental.Param import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.resource +import ee.hrzn.chryse.platform.resource.Connector +import ee.hrzn.chryse.platform.resource.SPIFlash +import ee.hrzn.chryse.platform.resource.LED +import ee.hrzn.chryse.platform.resource.UART +import ee.hrzn.chryse.platform.resource.Button +import ee.hrzn.chryse.platform.resource.ClockSource +import ee.hrzn.chryse.platform.resource.InOut case class IceBreakerPlatform( ubtnReset: Boolean = false, @@ -22,26 +28,26 @@ case class IceBreakerPlatform( class IceBreakerPlatformResources extends PlatformBoardResources { override val defaultAttributes = Map("IO_STANDARD" -> IOStandard.LVCMOS) - val clock = resource.ClockSource(12_000_000).onPin(35) + val clock = ClockSource(12_000_000).onPin(35) - val ubtn = resource.Button().inverted.onPin(10) + val ubtn = Button().inverted.onPin(10) - val uart = resource - .UART() - .onPins(rx = 6, tx = 9) - .withAttributes("IO_STANDARD" -> IOStandard.LVTTL, "PULLUP" -> 1) + val uart = + UART() + .onPins(rx = 6, tx = 9) + .withAttributes("IO_STANDARD" -> IOStandard.LVTTL, "PULLUP" -> 1) - val ledg = resource.LED().inverted.onPin(37) - val ledr = resource.LED().inverted.onPin(11) + val ledg = LED().inverted.onPin(37) + val ledr = LED().inverted.onPin(11) - var spiFlash = resource - .SPIFlash() - .onPins(csN = 16, clock = 15, copi = 14, cipo = 17, wpN = 12, holdN = 13) + var spiFlash = + SPIFlash() + .onPins(csN = 16, clock = 15, copi = 14, cipo = 17, wpN = 12, holdN = 13) // Ideally (per Amaranth) a user can refer to these connectors to make their // own resources, instead of just getting pins out of them. - val pmod1a = resource.Connector( - resource.InOut(), + val pmod1a = Connector( + InOut(), 1 -> 4, 2 -> 2, 3 -> 47, @@ -51,8 +57,8 @@ class IceBreakerPlatformResources extends PlatformBoardResources { 9 -> 46, 10 -> 44, ) - val pmod1b = resource.Connector( - resource.InOut(), + val pmod1b = Connector( + InOut(), 1 -> 43, 2 -> 38, 3 -> 34, @@ -62,8 +68,8 @@ class IceBreakerPlatformResources extends PlatformBoardResources { 9 -> 32, 10 -> 28, ) - val pmod2 = resource.Connector( - resource.InOut(), + val pmod2 = Connector( + InOut(), 1 -> 27, 2 -> 25, 3 -> 21, diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala index e287138..897a0d5 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala @@ -1,8 +1,11 @@ package ee.hrzn.chryse.platform.ice40 -import ee.hrzn.chryse.platform.resource.PinConnected +import ee.hrzn.chryse.platform.resource.PinInt -case class PCF(ios: Map[String, PinConnected], freqs: Map[String, Int]) { +final case class PCF( + ios: Map[String, PinInt], + freqs: Map[String, BigInt], +) { for { name <- freqs.keysIterator } if (!ios.isDefinedAt(name)) throw new IllegalArgumentException( diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/Button.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/Button.scala index 71a9043..e186553 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/Button.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/Button.scala @@ -3,7 +3,7 @@ package ee.hrzn.chryse.platform.resource import chisel3._ class Button - extends ResourceData[Bool](Input(Bool())) + extends ResourceData(Input(Bool())) with ResourceDataUserInvertible {} object Button { diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/ClockSource.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/ClockSource.scala index 14f4592..d80539b 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/ClockSource.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/ClockSource.scala @@ -2,7 +2,7 @@ package ee.hrzn.chryse.platform.resource import chisel3._ -case class ClockSource(hz: Int) extends ResourceData[Clock](Input(Clock())) {} +case class ClockSource(hz: Int) extends ResourceData(Input(Clock())) {} object ClockSource { def apply(hz: Int) = new ClockSource(hz) diff --git a/src/main/scala/ee/hrzn/chryse/platform/resource/LED.scala b/src/main/scala/ee/hrzn/chryse/platform/resource/LED.scala index 13bc164..23f3f82 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/resource/LED.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/resource/LED.scala @@ -3,7 +3,7 @@ package ee.hrzn.chryse.platform.resource import chisel3._ class LED - extends ResourceData[Bool](Output(Bool())) + extends ResourceData(Output(Bool())) with ResourceDataUserInvertible {} object LED { diff --git a/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala b/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala index a102912..8b7fbcb 100644 --- a/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala +++ b/src/test/scala/ee/hrzn/chryse/platform/PlatformBoardResourcesSpec.scala @@ -3,6 +3,7 @@ package ee.hrzn.chryse.platform import chisel3._ import chisel3.simulator.EphemeralSimulator._ import circt.stage.ChiselStage +import ee.hrzn.chryse.platform.resource.Pin import ee.hrzn.chryse.verilog import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should._ @@ -26,25 +27,35 @@ class PlatformBoardResourcesSpec extends AnyFlatSpec with Matchers { (rtl, top) } + def assertConnected(top: SimTop[_], rhs: Map[String, Pin]): Unit = { + val namesPins: Map[String, Pin] = top.connectedResources.map { + case (name, cr) => (name, cr.pin) + } + + namesPins should be(rhs) + } + it should "detect resource use" in { val (_, top) = simSVAndTop(new DetectionTop()(_)) - top.connectedResources should be( - Map[String, top.ConnectedResource]( - "ledg" -> resource.Pin("E5"), - "uart_rx" -> resource.Pin("C3"), - "uart_tx" -> resource.Pin("D4"), - "ubtn" -> resource.Pin("B2"), + assertConnected( + top, + Map( + "ledg" -> "E5", + "uart_rx" -> "C3", + "uart_tx" -> "D4", + "ubtn" -> "B2", ), ) } it should "invert inputs as requested and use the correct top-level IO names" in { val (rtl, top) = simSVAndTop(new InversionTop()(_)) - top.connectedResources should be( - Map[String, top.ConnectedResource]( - "ledg" -> resource.Pin("E5"), - "uart_tx" -> resource.Pin("D4"), - "ubtn" -> resource.Pin("B2"), + assertConnected( + top, + Map( + "ledg" -> "E5", + "uart_tx" -> "D4", + "ubtn" -> "B2", ), ) @@ -73,16 +84,17 @@ class PlatformBoardResourcesSpec extends AnyFlatSpec with Matchers { it should "handle in/out resources" in { val (rtl, top) = simSVAndTop(new InOutTop()(_)) - top.connectedResources should be( - Map[String, top.ConnectedResource]( - "ubtn" -> resource.Pin("B2"), - "uart_rx" -> resource.Pin("C3"), - "uart_tx" -> resource.Pin("D4"), - "ledr" -> resource.Pin("F6"), - "pmod1" -> resource.Pin("H8"), - "pmod2" -> resource.Pin("I9"), - "pmod7" -> resource.Pin("L12"), - "pmod8" -> resource.Pin("M13"), + assertConnected( + top, + Map( + "ubtn" -> "B2", + "uart_rx" -> "C3", + "uart_tx" -> "D4", + "ledr" -> "F6", + "pmod1" -> "H8", + "pmod2" -> "I9", + "pmod7" -> "L12", + "pmod8" -> "M13", ), ) @@ -135,3 +147,8 @@ class InOutTop(implicit platform: Platform) extends Module { plat.resources.pmod(7).o := plat.resources.ubtn plat.resources.ledr := plat.resources.pmod(8).i } + +class USRMCLKTop(implicit platform: Platform) extends Module { + val plat = platform.asInstanceOf[SimPlatform] + plat.resources.spiFlash.copi := plat.resources.spiFlash.clock +} diff --git a/src/test/scala/ee/hrzn/chryse/platform/SimPlatform.scala b/src/test/scala/ee/hrzn/chryse/platform/SimPlatform.scala index 089824e..d3b500b 100644 --- a/src/test/scala/ee/hrzn/chryse/platform/SimPlatform.scala +++ b/src/test/scala/ee/hrzn/chryse/platform/SimPlatform.scala @@ -4,10 +4,16 @@ import chisel3._ import ee.hrzn.chryse.ChryseApp import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.resource +import ee.hrzn.chryse.platform.ecp5.USRMCLK +import ee.hrzn.chryse.platform.resource.Connector +import ee.hrzn.chryse.platform.resource.LED +import ee.hrzn.chryse.platform.resource.UART +import ee.hrzn.chryse.platform.resource.Button +import ee.hrzn.chryse.platform.resource.InOut final case class SimPlatform() extends PlatformBoard[SimPlatformResources] { type TopPlatform[Top <: Module] = SimTop[Top] + type BuildResult = Nothing override def apply[Top <: Module](genTop: => Top) = { // TODO: detect when `this` isn't the same as the Platform the Top was @@ -21,9 +27,9 @@ final case class SimPlatform() extends PlatformBoard[SimPlatformResources] { chryse: ChryseApp, topPlatform: SimTop[_], jsonPath: String, - ): String = ??? + ): Nothing = ??? - override def program(binPath: String): Unit = ??? + override def program(buildResult: Nothing): Unit = ??? val id = "sim" val clockHz = 1_000_000 @@ -32,16 +38,16 @@ final case class SimPlatform() extends PlatformBoard[SimPlatformResources] { } class SimPlatformResources extends PlatformBoardResources { - val ubtn = resource.Button().inverted.onPin("B2") + val ubtn = Button().inverted.onPin("B2") - val uart = resource.UART().onPins(rx = "C3", tx = "D4") + val uart = UART().onPins(rx = "C3", tx = "D4") - val ledg = resource.LED().inverted.onPin("E5") - val ledr = resource.LED().inverted.onPin("F6") - val led3 = resource.LED().inverted.onPin("G7") + val ledg = LED().inverted.onPin("E5") + val ledr = LED().inverted.onPin("F6") + val led3 = LED().inverted.onPin("G7") - val pmod = resource.Connector( - resource.InOut(), + val pmod = Connector( + InOut(), 1 -> "H8", 2 -> "I9", 3 -> "J10", @@ -51,4 +57,11 @@ class SimPlatformResources extends PlatformBoardResources { 9 -> "N14", 10 -> "O15", ) + + val spiFlash = resource + .SPIFlash() + .onPins( + csN = "R2", clock = USRMCLK, copi = "W2", cipo = "V2", wpN = "Y2", + holdN = "W1", + ) } diff --git a/src/test/scala/ee/hrzn/chryse/platform/SimTop.scala b/src/test/scala/ee/hrzn/chryse/platform/SimTop.scala index f700d7a..5e3144d 100644 --- a/src/test/scala/ee/hrzn/chryse/platform/SimTop.scala +++ b/src/test/scala/ee/hrzn/chryse/platform/SimTop.scala @@ -3,7 +3,6 @@ package ee.hrzn.chryse.platform import chisel3._ import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.resource class SimTop[Top <: Module]( platform: PlatformBoard[_ <: PlatformBoardResources], diff --git a/src/test/scala/ee/hrzn/chryse/platform/ecp5/LPFSpec.scala b/src/test/scala/ee/hrzn/chryse/platform/ecp5/LPFSpec.scala new file mode 100644 index 0000000..b132bb1 --- /dev/null +++ b/src/test/scala/ee/hrzn/chryse/platform/ecp5/LPFSpec.scala @@ -0,0 +1,32 @@ +package ee.hrzn.chryse.platform.ecp5 + +import chisel3._ +import ee.hrzn.chryse.platform.resource.PinString +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should._ + +import java.io.StringWriter + +class LPFSpec extends AnyFlatSpec with Matchers { + behavior.of("LPF") + + it should "format IOs, attributes and frequencies correctly" in { + LPF( + Map( + "abc" -> (PinString("J1"), Map( + "IO_TYPE" -> IOType.LVCMOS33, + "DRIVE" -> 4, + )), + "xy" -> (PinString("A9"), Map()), + ), + Map("clk" -> 48_000_000), + ).toString() should be("""BLOCK ASYNCPATHS; + |BLOCK RESETPATHS; + |LOCATE COMP "abc" SITE "J1"; + |IOBUF PORT "abc" IO_TYPE=LVCMOS33 DRIVE=4; + |LOCATE COMP "xy" SITE "A9"; + |FREQUENCY PORT "clk" 48000000 HZ; + |""".stripMargin) + + } +} diff --git a/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala b/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala index 03e7311..4eeb81b 100644 --- a/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala +++ b/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala @@ -5,23 +5,25 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should._ import java.io.StringWriter +import ee.hrzn.chryse.platform.resource.PinInt class PCFSpec extends AnyFlatSpec with Matchers { behavior.of("PCF") it should "format IOs correctly" in { - PCF(Map("abc" -> 12, "xy" -> "A9"), Map()).toString() should be( + PCF(Map("abc" -> PinInt(12), "xy" -> PinInt(34)), Map()) + .toString() should be( """set_io abc 12 - |set_io xy A9 + |set_io xy 34 |""".stripMargin, ) } it should "format attached frequencies correctly" in { - PCF(Map("abc" -> 12, "xy" -> "A9"), Map("xy" -> 120_000_000)) + PCF(Map("abc" -> PinInt(12), "xy" -> PinInt(34)), Map("xy" -> 120_000_000)) .toString() should be( """set_io abc 12 - |set_io xy A9 + |set_io xy 34 |set_frequency xy 120.0 |""".stripMargin, ) @@ -29,7 +31,7 @@ class PCFSpec extends AnyFlatSpec with Matchers { it should "detect unattached frequencies" in { an[IllegalArgumentException] should be thrownBy PCF( - Map("abc" -> 12), + Map("abc" -> PinInt(12)), Map("xy" -> 100), ) }