blob: bde5c3036017ac3ac95aea25c581a841b862a49d [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kelvin.soc
import chisel3._
/**
* A simple case class for defining memory regions.
*
* @param base The base address of the memory region.
* @param size The size of the memory region in bytes.
*/
case class AddressRange(base: BigInt, size: BigInt) {
/**
* Checks if a given dynamic address is within this range.
* @param addr The address to check.
* @return A Chisel Bool indicating if the address is contained.
*/
def contains(addr: UInt): Bool = {
(addr >= base.U) && (addr < (base + size).U)
}
}
/**
* Defines the parameters for a host (master) in the crossbar.
* @param name The unique name of the host.
* @param width The data width of the host interface.
*/
case class HostConfig(name: String, width: Int, clockDomain: String = "main")
/**
* Defines the parameters for a device (slave) in the crossbar.
* @param name The unique name of the device.
* @param addr A sequence of AddressRanges that this device occupies.
* @param clockDomain An identifier for the clock domain this device belongs to.
* @param width The data width of the device interface.
*/
case class DeviceConfig(
name: String,
addr: Seq[AddressRange],
clockDomain: String = "main",
width: Int = 32
)
/**
* This object contains the complete, concrete configuration for the Kelvin SoC crossbar,
* translated from the original tl_config.hjson file.
*/
object CrossbarConfig {
// List of all host (master) interfaces.
def hosts(enableTestHarness: Boolean): Seq[HostConfig] = {
val baseHosts = Seq(
HostConfig("kelvin_core", width = 128),
HostConfig("spi2tlul", width = 128)
)
if (enableTestHarness) {
baseHosts :+ HostConfig("test_host_32", width = 32, clockDomain = "test")
} else {
baseHosts
}
}
// List of all device (slave) interfaces with their address maps.
val devices = Seq(
DeviceConfig("kelvin_device", Seq(
AddressRange(0x00000000, 0x2000), // 8kB
AddressRange(0x00010000, 0x8000), // 32kB
AddressRange(0x00030000, 0x1000) // 4kB
), width = 128),
DeviceConfig("rom", Seq(AddressRange(0x10000000, 0x8000))), // 32kB
DeviceConfig("sram", Seq(AddressRange(0x20000000, 0x400000))), // 4MB
DeviceConfig("uart0", Seq(AddressRange(0x40000000, 0x1000))),
DeviceConfig("uart1", Seq(AddressRange(0x40010000, 0x1000)))
)
// A map defining which hosts are allowed to connect to which devices.
def connections(enableTestHarness: Boolean): Map[String, Seq[String]] = {
val baseConnections = Map(
"kelvin_core" -> Seq("sram", "uart1", "kelvin_device", "rom", "uart0"),
"spi2tlul" -> Seq("kelvin_device", "sram")
)
if (enableTestHarness) {
baseConnections + ("test_host_32" -> Seq("rom", "sram", "uart0", "kelvin_device"))
} else {
baseConnections
}
}
}
/**
* A standalone validator for the CrossbarConfig.
*
* This object can be run to check for configuration errors, such as overlapping
* address ranges between devices.
*/
object CrossbarConfigValidator extends App {
val devices = CrossbarConfig.devices
println("Running CrossbarConfig validation...")
// Check for address range collisions
for (i <- devices.indices) {
for (j <- i + 1 until devices.length) {
val dev1 = devices(i)
val dev2 = devices(j)
for (range1 <- dev1.addr) {
for (range2 <- dev2.addr) {
val start1 = range1.base
val end1 = range1.base + range1.size
val start2 = range2.base
val end2 = range2.base + range2.size
// Check for overlap: max(start1, start2) < min(end1, end2)
val overlap = (start1 < end2) && (start2 < end1)
if (overlap) {
val errorMsg =
s"""
|FATAL: Address range collision detected!
| Device 1: ${dev1.name} -> Range [0x${start1.toString(16)}, 0x${(end1 - 1).toString(16)}]
| Device 2: ${dev2.name} -> Range [0x${start2.toString(16)}, 0x${(end2 - 1).toString(16)}]
"""
System.err.println(errorMsg)
throw new Exception("Crossbar configuration validation failed.")
}
}
}
}
}
println("Validation successful: No address range collisions found.")
def printConfig(enableTestHarness: Boolean): Unit = {
println(s"\n--- Crossbar Configuration (TestHarness: $enableTestHarness) ---")
println("Hosts:")
CrossbarConfig.hosts(enableTestHarness).foreach(h => println(s" - ${h.name}"))
println("\nDevices:")
CrossbarConfig.devices.foreach {
d =>
println(s" - ${d.name} (${d.clockDomain} clock domain)")
d.addr.foreach {
a =>
println(f" - 0x${a.base}%08x - 0x${a.base + a.size - 1}%08x (Size: ${a.size / 1024}kB)")
}
}
println("\nConnections:")
CrossbarConfig.connections(enableTestHarness).foreach {
case (host, devices) =>
println(s" - ${host} -> [${devices.mkString(", ")}]")
}
println("\n--------------------------")
}
printConfig(false)
printConfig(true)
}