blob: 0e52e4b00b99a64ef39969948f3f48d496f272b3 [file] [log] [blame]
package kelvin.soc
import chisel3._
import chisel3.util.MixedVec
import bus._
import kelvin.Parameters
import kelvin.CoreTlul
/**
* This is the IO bundle for the unified Chisel subsystem.
*/
class KelvinChiselSubsystemIO(val hostParams: Seq[bus.TLULParameters], val deviceParams: Seq[bus.TLULParameters], val enableTestHarness: Boolean) extends Bundle {
val cfg = SoCChiselConfig.crossbar
// --- Clocks and Resets ---
val clk_i = Input(Clock())
val rst_ni = Input(AsyncReset())
// --- Dynamic Asynchronous Clock/Reset Ports ---
val asyncHostDomains = cfg.hosts(enableTestHarness).map(_.clockDomain).distinct.filter(_ != "main")
val async_ports_hosts = new Bundle {
val clocks = Input(Vec(asyncHostDomains.length, Clock()))
val resets = Input(Vec(asyncHostDomains.length, AsyncReset()))
}
val asyncDeviceDomains = cfg.devices.map(_.clockDomain).distinct.filter(_ != "main")
val async_ports_devices = new Bundle {
val clocks = Input(Vec(asyncDeviceDomains.length, Clock()))
val resets = Input(Vec(asyncDeviceDomains.length, AsyncReset()))
}
// --- Identify Internal vs. External Connections ---
val internalHosts = SoCChiselConfig.modules.flatMap(_.hostConnections.values).toSet
val internalDevices = SoCChiselConfig.modules.flatMap(_.deviceConnections.values).toSet
val externalHostPorts = cfg.hosts(enableTestHarness).filterNot(h => internalHosts.contains(h.name))
val externalDevicePorts = cfg.devices.filterNot(d => internalDevices.contains(d.name))
// --- Create External TileLink Ports ---
val external_hosts = Flipped(new Bundle {
val ports = MixedVec(externalHostPorts.map { h =>
new OpenTitanTileLink.Host2Device(hostParams(cfg.hosts(enableTestHarness).indexWhere(_.name == h.name)))
})
})
val external_devices = new Bundle {
val ports = MixedVec(externalDevicePorts.map { d =>
new OpenTitanTileLink.Host2Device(deviceParams(cfg.devices.indexWhere(_.name == d.name)))
})
}
// --- Manually define peripheral ports for now ---
val allExternalPortsConfig = SoCChiselConfig.modules.flatMap(_.externalPorts)
val external_ports = MixedVec(allExternalPortsConfig.map { p =>
val port = p.portType match {
case kelvin.soc.Clk => Clock()
case kelvin.soc.Bool => Bool()
}
if (p.direction == kelvin.soc.In) Input(port) else Output(port)
})
}
import chisel3.experimental.BaseModule
import chisel3.reflect.DataMirror
import scala.collection.mutable
/**
* A generator for the entire Chisel-based subsystem of the Kelvin SoC.
*/
class KelvinChiselSubsystem(val hostParams: Seq[bus.TLULParameters], val deviceParams: Seq[bus.TLULParameters], val enableTestHarness: Boolean) extends RawModule {
override val desiredName = if (enableTestHarness) "KelvinChiselSubsystemTestHarness" else "KelvinChiselSubsystem"
val io = IO(new KelvinChiselSubsystemIO(hostParams, deviceParams, enableTestHarness))
val cfg = SoCChiselConfig.crossbar
/**
* A helper function to recursively traverse a Chisel Bundle and populate a
* map with the full hierarchical path to every port and sub-port.
*/
def populatePorts(prefix: String, data: Data, map: mutable.Map[String, Data]): Unit = {
map(prefix) = data
data match {
case b: Record =>
b.elements.foreach { case (name, child) =>
populatePorts(s"$prefix.$name", child, map)
}
case v: Vec[_] =>
v.zipWithIndex.foreach { case (child, i) =>
populatePorts(s"$prefix($i)", child, map)
}
case _ => // Leaf element
}
}
withClockAndReset(io.clk_i, (!io.rst_ni.asBool).asAsyncReset) {
// --- Instantiate Core Chisel Components ---
val xbar = Module(new KelvinXbar(hostParams, deviceParams, enableTestHarness))
// --- Dynamic Module Instantiation ---
def instantiateModule(config: ChiselModuleConfig): BaseModule = {
config.params match {
case p: CoreTlulParameters =>
val core_p = new Parameters
core_p.m = p.memoryRegions
core_p.lsuDataBits = p.lsuDataBits
core_p.enableRvv = p.enableRvv
core_p.enableFetchL0 = p.enableFetchL0
core_p.fetchDataBits = p.fetchDataBits
core_p.enableVector = p.enableVector
core_p.enableFloat = p.enableFloat
Module(new CoreTlul(core_p, config.name))
case p: Spi2TlulParameters =>
val spi_p = new Parameters
spi_p.lsuDataBits = p.lsuDataBits
Module(new Spi2TLUL(spi_p))
}
}
val instantiatedModules = SoCChiselConfig.modules.map {
config =>
config.name -> instantiateModule(config)
}.toMap
// --- Dynamic Wiring ---
val hostMap = cfg.hosts(enableTestHarness).map(_.name).zipWithIndex.toMap
val deviceMap = cfg.devices.map(_.name).zipWithIndex.toMap
val externalPortsMap = io.allExternalPortsConfig.map(_.name).zip(io.external_ports).toMap
// Create a map of all ports on all instantiated modules for easy lookup.
val modulePorts = mutable.Map[String, Data]()
instantiatedModules.foreach { case (moduleName, module) =>
DataMirror.modulePorts(module).foreach { case (portName, port) =>
populatePorts(s"$moduleName.$portName", port, modulePorts)
}
}
// --- Clock & Reset Connections ---
xbar.io.clk_i := io.clk_i
xbar.io.rst_ni := io.rst_ni
instantiatedModules.foreach { case (name, module) =>
modulePorts.get(s"$name.io.clk").foreach(_ := io.clk_i)
modulePorts.get(s"$name.io.clock").foreach(_ := io.clk_i)
modulePorts.get(s"$name.io.rst_ni").foreach(_ := io.rst_ni)
modulePorts.get(s"$name.io.reset").foreach(_ := io.rst_ni)
}
// Connect all modules based on the configuration.
SoCChiselConfig.modules.foreach {
config =>
config.hostConnections.foreach { case (modulePort, xbarPort) =>
modulePorts(s"${config.name}.$modulePort") <> xbar.io.hosts(hostMap(xbarPort))
}
config.deviceConnections.foreach { case (modulePort, xbarPort) =>
xbar.io.devices(deviceMap(xbarPort)) <> modulePorts(s"${config.name}.$modulePort")
}
config.externalPorts.foreach {
extPort =>
val moduleIo = modulePorts(s"${config.name}.${extPort.modulePort}")
val topIo = externalPortsMap(extPort.name)
if (extPort.direction == In) moduleIo := topIo else topIo := moduleIo
}
}
// Connect external-facing TileLink ports
io.externalHostPorts.map(_.name).zip(io.external_hosts.ports).foreach { case (name, port) =>
xbar.io.hosts(hostMap(name)) <> port
}
io.externalDevicePorts.map(_.name).zip(io.external_devices.ports).foreach { case (name, port) =>
port <> xbar.io.devices(deviceMap(name))
}
// Connect async clocks
val asyncHostDomainMap = io.asyncHostDomains.zipWithIndex.toMap
asyncHostDomainMap.foreach {
case (domainName, index) =>
val xbarPort = xbar.io.async_ports_hosts
val ioPort = io.async_ports_hosts
if (index < xbarPort.length) {
xbarPort(index).clock := ioPort.clocks(index)
xbarPort(index).reset := ioPort.resets(index)
}
}
val asyncDeviceDomainMap = io.asyncDeviceDomains.zipWithIndex.toMap
asyncDeviceDomainMap.foreach {
case (domainName, index) =>
val xbarPort = xbar.io.async_ports_devices
val ioPort = io.async_ports_devices
if (index < xbarPort.length) {
xbarPort(index).clock := ioPort.clocks(index)
xbarPort(index).reset := ioPort.resets(index)
}
}
}
}
import _root_.circt.stage.ChiselStage
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths, StandardOpenOption}
object KelvinChiselSubsystemEmitter extends App {
val enableTestHarness = args.contains("--enableTestHarness")
val chiselArgs = args.filterNot(a => a.startsWith("--enableTestHarness") || a.startsWith("--target-dir="))
val hostParams = SoCChiselConfig.crossbar.hosts(enableTestHarness).map {
host =>
val p = new Parameters
p.lsuDataBits = host.width
new bus.TLULParameters(p)
}
val deviceParams = SoCChiselConfig.crossbar.devices.map {
device =>
val p = new Parameters
p.lsuDataBits = device.width
new bus.TLULParameters(p)
}
// Manually parse arguments to find the target directory.
var targetDir: Option[String] = None
args.foreach {
case s if s.startsWith("--target-dir=") => targetDir = Some(s.stripPrefix("--target-dir="))
case "--enableTestHarness" => // Already handled by filterNot
case _ => // Ignore other arguments
}
// The subsystem module must be created in the ChiselStage context.
lazy val subsystem = new KelvinChiselSubsystem(hostParams, deviceParams, enableTestHarness)
val firtoolOpts = Array("-enable-layers=Verification")
val systemVerilogSource = ChiselStage.emitSystemVerilog(
subsystem, chiselArgs.toArray, firtoolOpts)
// CIRCT adds extra data to the end of the file. Remove it.
val resourcesSeparator =
"// ----- 8< ----- FILE \"firrtl_black_box_resource_files.f\" ----- 8< -----"
val strippedVerilogSource = systemVerilogSource.split(resourcesSeparator)(0)
// Write the stripped Verilog to the target directory.
targetDir.foreach {
dir =>
Files.write(
Paths.get(dir, subsystem.name + ".sv"),
strippedVerilogSource.getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING)
}
}