From 44cf0016ea8df6c67c1c6663654eabf9d0366835 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 21 May 2024 18:41:34 +0300 Subject: [PATCH] generate PCFs in place. --- .../scala/ee/hrzn/chryse/ChryseModule.scala | 2 +- .../ee/hrzn/chryse/platform/GenericTop.scala | 2 - .../hrzn/chryse/platform/ecp5/ECP5Top.scala | 6 +-- .../hrzn/chryse/platform/ice40/ICE40Top.scala | 23 ++++++----- .../ee/hrzn/chryse/platform/ice40/PCF.scala | 24 +++++++++++ .../ee/hrzn/chryse/tasks/BuildTask.scala | 15 ++++--- .../chryse/platform/BoardResourcesSpec.scala | 40 +++++++++++++------ .../hrzn/chryse/platform/ice40/PCFSpec.scala | 36 +++++++++++++++++ 8 files changed, 115 insertions(+), 33 deletions(-) create mode 100644 src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala create mode 100644 src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala diff --git a/src/main/scala/ee/hrzn/chryse/ChryseModule.scala b/src/main/scala/ee/hrzn/chryse/ChryseModule.scala index 554f73b..b4102c3 100644 --- a/src/main/scala/ee/hrzn/chryse/ChryseModule.scala +++ b/src/main/scala/ee/hrzn/chryse/ChryseModule.scala @@ -3,5 +3,5 @@ package ee.hrzn.chryse import chisel3._ class ChryseModule extends RawModule { - var lastPCF: Option[String] = None + override def desiredName = "chrysetop" } diff --git a/src/main/scala/ee/hrzn/chryse/platform/GenericTop.scala b/src/main/scala/ee/hrzn/chryse/platform/GenericTop.scala index cd7f94d..924fb8b 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/GenericTop.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/GenericTop.scala @@ -6,8 +6,6 @@ import ee.hrzn.chryse.platform.Platform class GenericTop[Top <: Module](platform: Platform, genTop: => Top) extends ChryseModule { - override def desiredName = "top" - val clock = IO(Input(Clock())) val reset = IO(Input(Bool())) 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 62da7d4..c340f67 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/ECP5Top.scala @@ -1,15 +1,13 @@ package ee.hrzn.chryse.platform.ecp5 import chisel3._ -import ee.hrzn.chryse.platform.Platform import ee.hrzn.chryse.ChryseModule -import ee.hrzn.chryse.platform.BoardResources import ee.hrzn.chryse.platform.BoardPlatform +import ee.hrzn.chryse.platform.BoardResources +import ee.hrzn.chryse.platform.Platform class ECP5Top[Top <: Module](platform: Platform, genTop: => Top) extends ChryseModule { - override def desiredName = "top" - private val clki = IO(Input(Clock())) // TODO: GSR stuff. (details follow.) 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 4dbe370..77b42eb 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/ICE40Top.scala @@ -13,14 +13,15 @@ import ee.hrzn.chryse.platform.Platform import ee.hrzn.chryse.platform.resource import java.lang.reflect.Modifier +import scala.collection.mutable class ICE40Top[Top <: Module]( platform: BoardPlatform[_ <: BoardResources], genTop: => Top, ) extends ChryseModule { - override def desiredName = "ice40top" + var lastPCF: Option[PCF] = None - val clki = Wire(Clock()) + private val clki = Wire(Clock()) private val clk_gb = Module(new SB_GB) clk_gb.USER_SIGNAL_TO_GLOBAL_BUFFER := clki @@ -38,7 +39,7 @@ class ICE40Top[Top <: Module]( resetTimerReg := resetTimerReg + 1.U } - val finalReset = noPrefix { + private val finalReset = noPrefix { // TODO: this no longer works. :) if (platform.asInstanceOf[IceBreakerPlatform].ubtnReset) { val io_ubtn = IO(Input(Bool())) @@ -52,8 +53,12 @@ class ICE40Top[Top <: Module]( withClockAndReset(clk, finalReset)(Module(genTop)) // TODO: allow clock override. - // TODO: refactor this out to a non-ICE40Top level. - val sb = new StringBuilder + + // TODO: refactor out the main machinations to a non-ICE40Top level; override + // the PCF generation specifically (needs to tie in with ICE40-specific build + // process). + private val ios = mutable.Map[String, resource.Pin]() + private val freqs = mutable.Map[String, Int]() for { f <- platform.resources.getClass().getDeclaredFields() } { val name = f.getName() f.setAccessible(true) @@ -65,13 +70,14 @@ class ICE40Top[Top <: Module]( // NOTE: we can't just say clki := platform.resources.clock in our top // here, since that'll define an input IO in *this* module which we // can't then sink like we would in the resource.Base[_] case. - sb.append(s"set_io $name ${clock.pinId.get}\n") + ios += name -> clock.pinId.get + freqs += name -> platform.clockHz val io = IO(Input(Clock())).suggestName(name) clki := io case res: resource.Base[_] => if (res.ioInst.isDefined) { - sb.append(s"set_io $name ${res.pinId.get}\n") + ios += name -> res.pinId.get val io = IO(res.makeIo()).suggestName(name) DirectionOf(io) match { case SpecifiedDirection.Input => @@ -84,10 +90,9 @@ class ICE40Top[Top <: Module]( } case _ => } - - lastPCF = Some(sb.toString()) } + lastPCF = Some(PCF(ios.iterator.to(Map), freqs.iterator.to(Map))) } object ICE40Top { diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala new file mode 100644 index 0000000..8cb3cb5 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/PCF.scala @@ -0,0 +1,24 @@ +package ee.hrzn.chryse.platform.ice40 + +import ee.hrzn.chryse.platform.resource.Pin + +case class PCF(ios: Map[String, Pin], freqs: Map[String, Int]) { + for { name <- freqs.keysIterator } + if (!ios.isDefinedAt(name)) + throw new IllegalArgumentException( + s"frequency $name doesn't have corresponding io", + ) + + override def toString(): String = { + val sb = new StringBuilder + for { (name, pin) <- ios } { + sb.append(s"set_io $name $pin\n") + freqs + .get(name) + .foreach { freq => + sb.append(s"set_frequency $name ${freq.toDouble / 1_000_000.0}\n") + } + } + sb.toString() + } +} diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala index f19d507..f302ceb 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala @@ -4,6 +4,8 @@ import chisel3._ import circt.stage.ChiselStage import ee.hrzn.chryse.platform.BoardPlatform import ee.hrzn.chryse.platform.Platform +import ee.hrzn.chryse.platform.ice40.ICE40Top +import ee.hrzn.chryse.platform.ice40.PCF import java.nio.file.Files import java.nio.file.Paths @@ -22,13 +24,16 @@ object BuildTask extends BaseTask { Files.createDirectories(Paths.get(buildDir)) - val verilogPath = s"$buildDir/$name-${platform.id}.sv" - var lastPCF: Option[String] = None + val verilogPath = s"$buildDir/$name-${platform.id}.sv" + var lastPCF: Option[PCF] = None val verilog = ChiselStage.emitSystemVerilog( { val elaborated = platform(genTop) - lastPCF = elaborated.lastPCF + lastPCF = elaborated match { + case ice40: ICE40Top[_] => ice40.lastPCF + case _ => None + } elaborated }, if (fullStacktrace) Array("--full-stacktrace") else Array.empty, @@ -45,7 +50,7 @@ object BuildTask extends BaseTask { writePath( yosysScriptPath, s"""read_verilog -sv $verilogPath - |synth_ice40 -top top + |synth_ice40 -top chrysetop |write_json $jsonPath""".stripMargin, ) @@ -66,7 +71,7 @@ object BuildTask extends BaseTask { runCu("synthesis", yosysCu) val pcfPath = s"$buildDir/$name-${platform.id}.pcf" - writePath(pcfPath, lastPCF.get) + writePath(pcfPath, lastPCF.get.toString()) val ascPath = s"$buildDir/$name-${platform.id}.asc" val ascCu = CompilationUnit( diff --git a/src/test/scala/ee/hrzn/chryse/platform/BoardResourcesSpec.scala b/src/test/scala/ee/hrzn/chryse/platform/BoardResourcesSpec.scala index 9432182..295ffae 100644 --- a/src/test/scala/ee/hrzn/chryse/platform/BoardResourcesSpec.scala +++ b/src/test/scala/ee/hrzn/chryse/platform/BoardResourcesSpec.scala @@ -6,6 +6,7 @@ import ee.hrzn.chryse.ChryseModule import ee.hrzn.chryse.chisel.BuilderContext import ee.hrzn.chryse.platform.ice40.ICE40Top import ee.hrzn.chryse.platform.ice40.IceBreakerPlatform +import ee.hrzn.chryse.platform.ice40.PCF import ee.hrzn.chryse.platform.resource.BaseBool.Implicits._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should._ @@ -18,12 +19,19 @@ class BoardResourcesSpec extends AnyFlatSpec with Matchers { val top = BuilderContext { plat(new DetectionTop(_)) } - top.lastPCF.get.linesIterator.toList.sorted.mkString("\n") should be( - """set_io clock 35 - |set_io ledg 37 - |set_io uart_rx 6 - |set_io uart_tx 9 - |set_io ubtn 10""".stripMargin, + top.lastPCF should be( + Some( + PCF( + Map( + "clock" -> 35, + "ledg" -> 37, + "uart_rx" -> 6, + "uart_tx" -> 9, + "ubtn" -> 10, + ), + Map("clock" -> 12_000_000), + ), + ), ) } @@ -37,12 +45,20 @@ class BoardResourcesSpec extends AnyFlatSpec with Matchers { }, firtoolOpts = Array("-strip-debug-info"), ) - top.lastPCF.get.linesIterator.toList.sorted.mkString("\n") should be( - """set_io clock 35 - |set_io ledg 37 - |set_io uart_tx 9 - |set_io ubtn 10""".stripMargin, + top.lastPCF should be( + Some( + PCF( + Map( + "clock" -> 35, + "ledg" -> 37, + "uart_tx" -> 9, + "ubtn" -> 10, + ), + Map("clock" -> 12_000_000), + ), + ), ) + rtl should include("ledg_int = view__ubtn_int") (rtl should not).include("uart_tx_int = view__ubtn_int") rtl should include("uart_tx_int = ~view__ubtn_int") @@ -50,7 +66,7 @@ class BoardResourcesSpec extends AnyFlatSpec with Matchers { // HACK: this is brittle. Parse the Verilog or something. "\\s+".r .replaceAllIn(rtl, " ") should include( - "module ice40top( input clock, ubtn, output uart_tx, ledg );", + "module chrysetop( input clock, ubtn, output uart_tx, ledg );", ) } } diff --git a/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala b/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala new file mode 100644 index 0000000..03e7311 --- /dev/null +++ b/src/test/scala/ee/hrzn/chryse/platform/ice40/PCFSpec.scala @@ -0,0 +1,36 @@ +package ee.hrzn.chryse.platform.ice40 + +import chisel3._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should._ + +import java.io.StringWriter + +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( + """set_io abc 12 + |set_io xy A9 + |""".stripMargin, + ) + } + + it should "format attached frequencies correctly" in { + PCF(Map("abc" -> 12, "xy" -> "A9"), Map("xy" -> 120_000_000)) + .toString() should be( + """set_io abc 12 + |set_io xy A9 + |set_frequency xy 120.0 + |""".stripMargin, + ) + } + + it should "detect unattached frequencies" in { + an[IllegalArgumentException] should be thrownBy PCF( + Map("abc" -> 12), + Map("xy" -> 100), + ) + } +}