diff --git a/build.sbt b/build.sbt index 383579e6..e39fc703 100644 --- a/build.sbt +++ b/build.sbt @@ -126,7 +126,8 @@ lazy val rocketchip = freshProject("rocketchip", rocketChipDir) libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value, "org.json4s" %% "json4s-jackson" % "3.6.6", - "org.scalatest" %% "scalatest" % "3.2.0" % "test" + "org.scalatest" %% "scalatest" % "3.2.0" % "test", + "org.scala-graph" %% "graph-core" % "1.13.5" ) ) .settings( // Settings for scalafix diff --git a/common.mk b/common.mk index 0e1fc422..9d772077 100644 --- a/common.mk +++ b/common.mk @@ -20,7 +20,8 @@ HELP_COMPILATION_VARIABLES += \ " ENABLE_YOSYS_FLOW = if set, add compilation flags to enable the vlsi flow for yosys(tutorial flow)" \ " EXTRA_CHISEL_OPTIONS = additional options to pass to the Chisel compiler" \ " EXTRA_BASE_FIRRTL_OPTIONS = additional options to pass to the Scala FIRRTL compiler" \ -" MFC_BASE_LOWERING_OPTIONS = override lowering options to pass to the MLIR FIRRTL compiler" +" MFC_BASE_LOWERING_OPTIONS = override lowering options to pass to the MLIR FIRRTL compiler" \ +" ASPECTS = comma separated list of Chisel aspect flows to run (e.x. chipyard.upf.ChipTopUPFAspect)" EXTRA_GENERATOR_REQS ?= $(BOOTROM_TARGETS) EXTRA_SIM_CXXFLAGS ?= @@ -29,6 +30,11 @@ EXTRA_SIM_SOURCES ?= EXTRA_SIM_REQS ?= ENABLE_CUSTOM_FIRRTL_PASS += $(ENABLE_YOSYS_FLOW) +ifneq ($(ASPECTS), ) + comma = , + ASPECT_ARGS = $(foreach aspect, $(subst $(comma), , $(ASPECTS)), --with-aspect $(aspect)) +endif + #---------------------------------------------------------------------------- HELP_SIMULATION_VARIABLES += \ " EXTRA_SIM_FLAGS = additional runtime simulation flags (passed within +permissive)" \ @@ -134,6 +140,7 @@ $(FIRRTL_FILE) $(ANNO_FILE) $(CHISEL_LOG_FILE) &: $(CHIPYARD_CLASSPATH_TARGETS) --name $(long_name) \ --top-module $(MODEL_PACKAGE).$(MODEL) \ --legacy-configs $(CONFIG_PACKAGE):$(CONFIG) \ + $(ASPECT_ARGS) \ $(EXTRA_CHISEL_OPTIONS)) | tee $(CHISEL_LOG_FILE)) define mfc_extra_anno_contents diff --git a/docs/VLSI/Advanced-Usage.rst b/docs/VLSI/Advanced-Usage.rst index 28b03d8b..00c3b1d3 100644 --- a/docs/VLSI/Advanced-Usage.rst +++ b/docs/VLSI/Advanced-Usage.rst @@ -106,3 +106,22 @@ With the Synopsys plugin, hierarchical RTL and gate-level simulation is supporte * ``-$(VLSI_TOP)`` suffixes denote simulations/power analysis on a submodule in a hierarchical flow (remember to override this variable). Note that you must provide the testbenches for these modules since the default testbench only simulates a Chipyard-based ``ChipTop`` DUT instance. The simulation configuration (e.g. binaries) can be edited for your design. See the ``Makefile`` and refer to Hammer's documentation for how to set up simulation parameters for your design. + +UPF Generation Flow +------------------------------- +This VLSI flow experimentally supports generating Chisel-based `UPF `__ files using `Chisel Aspects `__. + +To generate UPF for any design, first modify the ``UPFInputs`` object in ``generators/chipyard/src/main/scala/upf/UPFInputs.scala`` to fit your design power specifications. + +This involves filling in the ``upfInfo`` list with ``PowerDomainInput`` objects representing all the power domains you want in your design, along with specifying hierarchy and domain attributes. + +The given example in ``UPFInputs`` corresponds to a dual-core Rocket config with 3 power domains (1 parent domain with all uncore modules and 2 children corresponding to the Rocket tiles). + +To run the flow: + +.. code-block:: shell + + cd chipyard/vlsi + make verilog ASPECTS=chipyard.upf.ChipTopUPFAspect + +The output UPF files will be dumped in ``vlsi/generated-src/upf``. diff --git a/generators/chipyard/src/main/scala/upf/ChipTopUPF.scala b/generators/chipyard/src/main/scala/upf/ChipTopUPF.scala new file mode 100644 index 00000000..9356d591 --- /dev/null +++ b/generators/chipyard/src/main/scala/upf/ChipTopUPF.scala @@ -0,0 +1,90 @@ +// See LICENSE for license details +package chipyard.upf + +import scala.collection.mutable.ListBuffer +import scalax.collection.mutable.Graph +import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ + +import chipyard.TestHarness +import freechips.rocketchip.diplomacy.LazyModule + +object ChipTopUPF { + + def default: UPFFunc.UPFFunction = { + case top: LazyModule => { + val modulesList = getLazyModules(top) + val pdList = createPowerDomains(modulesList) + val g = connectPDHierarchy(pdList) + traverseGraph(g, UPFGenerator.generateUPF) + } + } + + def getLazyModules(top: LazyModule): ListBuffer[LazyModule] = { + var i = 0 + var result = new ListBuffer[LazyModule]() + result.append(top) + while (i < result.length) { + val lazyMod = result(i) + for (child <- lazyMod.getChildren) { + result.append(child) + } + i += 1 + } + return result + } + + def createPowerDomains(modulesList: ListBuffer[LazyModule]): ListBuffer[PowerDomain] = { + var pdList = ListBuffer[PowerDomain]() + for (pdInput <- UPFInputs.upfInfo) { + val pd = new PowerDomain(name=pdInput.name, modules=getPDModules(pdInput, modulesList), + isTop=pdInput.isTop, isGated=pdInput.isGated, + highVoltage=pdInput.highVoltage, lowVoltage=pdInput.lowVoltage) + pdList.append(pd) + } + return pdList + } + + def getPDModules(pdInput: PowerDomainInput, modulesList: ListBuffer[LazyModule]): ListBuffer[LazyModule] = { + var pdModules = ListBuffer[LazyModule]() + for (moduleName <- pdInput.moduleList) { + var module = modulesList.filter(_.module.name == moduleName) + if (module.length == 1) { // filter returns a collection + pdModules.append(module(0)) + } else { + module = modulesList.filter(_.module.instanceName == moduleName) + if (module.length == 1) { + pdModules.append(module(0)) + } else { + module = modulesList.filter(_.module.pathName == moduleName) + if (module.length == 1) { + pdModules.append(module(0)) + } else { + throw new Exception(s"PowerDomainInput module list doesn't exist in design.") + } + } + } + } + return pdModules + } + + def connectPDHierarchy(pdList: ListBuffer[PowerDomain]): Graph[PowerDomain, DiEdge] = { + var g = Graph[PowerDomain, DiEdge]() + for (pd <- pdList) { + val pdInput = UPFInputs.upfInfo.filter(_.name == pd.name)(0) + val childPDs = pdList.filter(x => pdInput.childrenPDs.contains(x.name)) + for (childPD <- childPDs) { + g += (pd ~> childPD) // directed edge from pd to childPD + } + } + return g + } + + def traverseGraph(g: Graph[PowerDomain, DiEdge], action: (PowerDomain, Graph[PowerDomain, DiEdge]) => Unit): Unit = { + for (node <- g.nodes.filter(_.diPredecessors.isEmpty)) { // all nodes without parents + g.outerNodeTraverser(node).foreach(pd => action(pd, g)) + } + } + +} + +case object ChipTopUPFAspect extends UPFAspect[chipyard.TestHarness](ChipTopUPF.default) diff --git a/generators/chipyard/src/main/scala/upf/UPFAspect.scala b/generators/chipyard/src/main/scala/upf/UPFAspect.scala new file mode 100644 index 00000000..e09ad910 --- /dev/null +++ b/generators/chipyard/src/main/scala/upf/UPFAspect.scala @@ -0,0 +1,23 @@ +// See LICENSE for license details +package chipyard.upf + +import chisel3.aop.Aspect +import firrtl.{AnnotationSeq} +import chipyard.TestHarness +import freechips.rocketchip.stage.phases.TargetDirKey +import freechips.rocketchip.diplomacy.LazyModule + +abstract class UPFAspect[T <: TestHarness](upf: UPFFunc.UPFFunction) extends Aspect[T] { + + final override def toAnnotation(top: T): AnnotationSeq = { + UPFFunc.UPFPath = top.p(TargetDirKey) + "/upf" + upf(top.lazyDut) + AnnotationSeq(Seq()) // noop + } + +} + +object UPFFunc { + type UPFFunction = PartialFunction[LazyModule, Unit] + var UPFPath = "" // output dir path +} diff --git a/generators/chipyard/src/main/scala/upf/UPFGen.scala b/generators/chipyard/src/main/scala/upf/UPFGen.scala new file mode 100644 index 00000000..47475727 --- /dev/null +++ b/generators/chipyard/src/main/scala/upf/UPFGen.scala @@ -0,0 +1,264 @@ +// See LICENSE for license details +package chipyard.upf + +import java.io.FileWriter +import java.nio.file.{Paths, Files} +import scala.collection.mutable.ListBuffer +import scalax.collection.mutable.Graph +import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ + +import freechips.rocketchip.diplomacy.LazyModule + +case class PowerDomain (val name: String, val modules: ListBuffer[LazyModule], + val isTop: Boolean, val isGated: Boolean, + val highVoltage: Double, val lowVoltage: Double) { + val mainVoltage = isGated match { + case true => highVoltage // gated nets should have access to high voltage rail (since they are being gated to optimize power) + case false => lowVoltage // currently assuming non-gated nets are on low voltage rail + } +} + +object UPFGenerator { + + def generateUPF(pd: PowerDomain, g: Graph[PowerDomain, DiEdge]): Unit = { + val node = g.get(pd) + val children = node.diSuccessors.map(x => x.toOuter).toList + val pdList = g.nodes.map(x => x.toOuter).toList + val filePath = UPFFunc.UPFPath + val fileName = s"${pd.name}.upf" + writeFile(filePath, fileName, createMessage(pd, children, pdList)) + } + + def createMessage(pd: PowerDomain, children: List[PowerDomain], pdList: List[PowerDomain]): String = { + var message = "" + message += loadUPF(pd, children) + message += createPowerDomains(pd) + message += createSupplyPorts(pd) + message += createSupplyNets(pd) + message += connectSupplies(pd) + message += setDomainNets(pd) + message += createPowerSwitches(pd) + message += createPowerStateTable(pd, getPorts(pd, children)) + message += createLevelShifters(pd, pdList) + return message + } + + def writeFile(filePath: String, fileName: String, message: String): Unit = { + if (!Files.exists(Paths.get(filePath))) { + Files.createDirectories(Paths.get(filePath)) + } + val fw = new FileWriter(s"${filePath}/${fileName}", false) + fw.write(message) + fw.close() + } + + def getPorts(pd: PowerDomain, children: List[PowerDomain]): ListBuffer[String] = { + var portsList = ListBuffer[String]() + portsList += "VDDH" + portsList += "VDDL" + if (pd.isGated) { + portsList += s"VDD_${pd.name}" + } + for (child <- children) { + if (child.isGated) { + portsList += s"VDD_${child.name}" + } + } + return portsList + } + + def loadUPF(pd: PowerDomain, children: List[PowerDomain]): String = { + var message = "##### Set Scope and Load UPF #####\n" + var subMessage = s"set_scope /${pd.modules(0).module.name}\n" // + children.foreach{ + child => { + subMessage += s"load_upf ${child.name}.upf -scope ${child.modules(0).module.name}\n" + } + } + message += subMessage + message += "\n" + return message + } + + def createPowerDomains(pd: PowerDomain): String = { + var message = "##### Create Power Domains #####\n" + var subMessage = "" + pd.isTop match { + case true => subMessage += s"create_power_domain ${pd.name} -include_scope\n" + case false => { + subMessage += s"create_power_domain ${pd.name} -elements { " + for (module <- pd.modules) { + subMessage += s"${module.module.name} " + } + subMessage += "}\n" + } + } + message += subMessage + message += "\n" + return message + } + + def createSupplyPorts(pd: PowerDomain): String = { + if (!pd.isTop) { + return "" + } + var message = "##### Create Supply Ports #####\n" + var subMessage = pd.isTop match { + case true => { + s"create_supply_port VDDH -direction in -domain ${pd.name}\n" + + s"create_supply_port VDDL -direction in -domain ${pd.name}\n" + + s"create_supply_port VSS -direction in -domain ${pd.name}\n" + } + case false => "" + } + message += subMessage + message += "\n" + return message + } + + def createSupplyNets(pd: PowerDomain): String = { + var message = "##### Create Supply Nets #####\n" + var subMessage = pd.isTop match { + case true => { + s"create_supply_net VDDH -domain ${pd.name}\n" + + s"create_supply_net VDDL -domain ${pd.name}\n" + + s"create_supply_net VSS -domain ${pd.name}\n" + } + case false => { + s"create_supply_net VDDH -domain ${pd.name} -reuse\n" + + s"create_supply_net VDDL -domain ${pd.name} -reuse\n" + + s"create_supply_net VSS -domain ${pd.name} -reuse\n" + } + } + if (pd.isGated) { + subMessage += s"create_supply_net VDD_${pd.name} -domain ${pd.name}\n" + } + message += subMessage + message += "\n" + return message + } + + def connectSupplies(pd: PowerDomain): String = { + var message = "##### Connect Supply Nets and Ports #####\n" + var subMessage = "connect_supply_net VDDH -ports VDDH\n" + + "connect_supply_net VDDL -ports VDDL\n" + + "connect_supply_net VSS -ports VSS\n" + message += subMessage + message += "\n" + return message + } + + def setDomainNets(pd: PowerDomain): String = { + var message = "##### Set Domain Supply Nets #####\n" + var subMessage = pd.isGated match { + case true => s"set_domain_supply_net ${pd.name} -primary_power_net VDD_${pd.name} -primary_ground_net VSS\n" + case false => s"set_domain_supply_net ${pd.name} -primary_power_net VDDL -primary_ground_net VSS\n" + } + message += subMessage + message += "\n" + return message + } + + def createPowerSwitches(pd: PowerDomain): String = { + if (!pd.isGated) { + return "" + } + var message = "##### Power Switches #####\n" + var subMessage = pd.isGated match { + case true => s"""create_power_switch sw_${pd.name} -domain ${pd.name} -input_supply_port "psw_VDDH VDDH" """ + + s"""-output_supply_port "psw_VDD_${pd.name} VDD_${pd.name}" """ + + s"""-control_port "psw_${pd.name}_en ${pd.modules(0).module.name}/${pd.modules(0).module.name}_en" """ + + s"""-on_state "psw_${pd.name}_ON psw_VDDH { !psw_${pd.name}_en }"""" + "\n" + case false => "" + } + message += subMessage + message += "\n" + return message + } + + def createPowerStateTable(pd: PowerDomain, portsList: ListBuffer[String]): String = { + if (!pd.isTop) { + return "" + } + var message = "##### Power State Table #####\n" + var portStates = "" + var createPST = "create_pst pst_table -supplies { " + + for (port <- portsList) { + createPST += s"${port} " + if (port == "VDDH") { + portStates += s"add_port_state ${port} -state { HighVoltage ${pd.highVoltage} }\n" + } else if (port == "VDDL") { + portStates += s"add_port_state ${port} -state { LowVoltage ${pd.lowVoltage} }\n" + } else { // gated + portStates += s"add_port_state ${port} -state { HighVoltage ${pd.highVoltage } -state { ${port}_OFF off }\n" + } + } + portStates += "\n" + createPST += "}\n\n" + + var pstStates = "" + for (state <- UPFInputs.states.keys) { + val stateVal = getStateVal(pd, state) + pstStates += s"add_pst_state ${state} -pst pst_table -state { " + for (port <- portsList) { + if (port == "VDDH") { + pstStates += s"HighVoltage " + } else if (port == "VDDL") { + pstStates += s"LowVoltage " + } else { // gated + stateVal match { + case 0 => pstStates += s"${port}_OFF " + case 1 => pstStates += s"HighVoltage " + } + } + } + pstStates += "}\n" + } + message += portStates + message += createPST + message += pstStates + message += "\n" + return message + } + + def getStateVal(pd: PowerDomain, state: String): Int = { + val stateVals = UPFInputs.states(state).split(",").map(_.trim.toInt) + val index = UPFInputs.domains.indexOf(pd.name) + return stateVals(index) + } + + // current strategy: for each power domain, create level shifters for outputs going to all other pds + // not creating level shifters for inputs since every pd will already shift its outputs + // creating level shifters going to every other pd since not sure how to check if there is communication or not between any 2 + def createLevelShifters(pd: PowerDomain, pdList: List[PowerDomain]): String = { + var message = "##### Level Shifters #####\n" + for (pd2 <- pdList) { + if (pd != pd2) { + val voltage1 = pd.mainVoltage + val voltage2 = pd2.mainVoltage + var subMessage = voltage1 match { + case x if x < voltage2 => { + s"set_level_shifter LtoH_${pd.name}_to_${pd2.name} " + + s"-domain ${pd.name} " + + "-applies_to outputs " + + "rule low_to_high " + + "-location self\n" + } + case y if y > voltage2 => { + s"set_level_shifter HtoL_${pd.name}_to_${pd2.name} " + + s"-domain ${pd.name} " + + "-applies_to outputs " + + "rule high_to_low " + + "-location self\n" + } + case _ => "" + } + message += subMessage + } + } + message += "\n" + return message + } + +} diff --git a/generators/chipyard/src/main/scala/upf/UPFInputs.scala b/generators/chipyard/src/main/scala/upf/UPFInputs.scala new file mode 100644 index 00000000..438b7e7b --- /dev/null +++ b/generators/chipyard/src/main/scala/upf/UPFInputs.scala @@ -0,0 +1,54 @@ +// See LICENSE for license details +package chipyard.upf + +// outputs are dumped in vlsi/generated-src/upf +object UPFInputs { + + /** + * UPF info + * each PowerDomainInput represents a desired power domain + * each input will contain all the necessary info to describe a power domain in UPF, including hierarchy + */ + val upfInfo = List( + PowerDomainInput(name="PD_top", isTop=true, moduleList=List("DigitalTop"), + parentPD="", childrenPDs=List("PD_RocketTile1", "PD_RocketTile2"), + isGated=false, highVoltage=3.9, lowVoltage=3.4), + PowerDomainInput(name="PD_RocketTile1", isTop=false, moduleList=List("RocketTile"), + parentPD="PD_top", childrenPDs=List(), + isGated=false, highVoltage=3.9, lowVoltage=3.1), + PowerDomainInput(name="PD_RocketTile2", isTop=false, moduleList=List("RocketTile_1"), + parentPD="PD_top", childrenPDs=List(), + isGated=false, highVoltage=3.9, lowVoltage=3.2), + ) + + + /** + * PST info + * experimental Power State Table input, used to gate power domains based on specified power states + * place names of all power domains to be gated in the domains list + * states will map different keywords (arbitrary strings) to a binary on or off (1 or 0) to form a power state + * order of domains in list corresponds to order of values in each states mapping + */ + val domains = List("PD_top", "PD_RocketTile1", "PD_RocketTile2") + val states = Map( + "ON" -> "1, 1, 1", + "OFF" -> "0, 0, 0" + ) + +} + +/** + * Representation of a power domain used to generate UPF. + * + * @param name name of the power domain. + * @param isTop if the power domain is the top level or not. + * @param moduleList refers to all the Verilog modules belonging to this power domain. Can be module name, instance name, or full path name. + * @param parentPD the name of the parent power domain to this one. + * @param childrenPDs names of all the children power domains to this one. + * @param isGated if the power domain is gated or not. + * @param highVoltage voltage value of the high voltage rail (currently, gated nets have access to high voltage since they are optimized to save power). + * @param lowVoltage voltage value of the low voltage rail (currently, non-gated nets default to the low voltage rail). + */ +case class PowerDomainInput(name: String, isTop: Boolean, moduleList: List[String], + parentPD: String, childrenPDs: List[String], + isGated: Boolean, highVoltage: Double, lowVoltage: Double)