1
0
rocket-chip/uncore/src/main/scala/tilelink2/Parameters.scala
Wesley W. Terpstra 9cd2991fb3 tilelink2: AddressSet always has an assigned base address
The consensus seems to be that TileLink should not be assigning
addresses dynamically. The reasons:

1. We can come up with another scheme for assigning addresses that is
   independent of TileLink.  This decoupling is good, because it would
   allow us to use the same mechanism for different buses in the SoC.

2. The informational flow of addresses is more likely to naturally follow
   the module hierarchy than the TileLike bus topology. Thus, it seems
   better to pass address parameterization using Module constructors.

3. Addresses are still checked by TileLink, so using a Module-centric
   flow for addresses will not pose a correctness concern.

4. An address need only be provided to a slave on its construction and
   TileLink parameterization spreads this globally. Thus, the burden to
   manually assign an address is low.
2016-09-05 20:58:39 -07:00

323 lines
12 KiB
Scala

// See LICENSE for license details.
package uncore.tilelink2
import Chisel._
import scala.math.max
/** Options for memory regions */
object RegionType {
sealed trait T
case object CACHED extends T
case object TRACKED extends T
case object UNCACHED extends T
case object PUT_EFFECTS extends T
case object GET_EFFECTS extends T // GET_EFFECTS => PUT_EFFECTS
val cases = Seq(CACHED, TRACKED, UNCACHED, PUT_EFFECTS, GET_EFFECTS)
}
// A non-empty half-open range; [start, end)
case class IdRange(start: Int, end: Int)
{
require (start >= 0)
require (start < end) // not empty
// This is a strict partial ordering
def <(x: IdRange) = end <= x.start
def >(x: IdRange) = x < this
def overlaps(x: IdRange) = start < x.end && x.start < end
def contains(x: IdRange) = start <= x.start && x.end <= end
// contains => overlaps (because empty is forbidden)
def contains(x: Int) = start <= x && x < end
def contains(x: UInt) =
if (start+1 == end) { UInt(start) === x }
else if (((end-1) & ~start) == end-start-1)
{ ((UInt(start) ^ x) & ~UInt(end-start-1)) === UInt(0) }
else { UInt(start) <= x && x < UInt(end) }
def shift(x: Int) = IdRange(start+x, end+x)
def size = end - start
}
// An potentially empty inclusive range of 2-powers [min, max] (in bytes)
case class TransferSizes(min: Int, max: Int)
{
def this(x: Int) = this(x, x)
require (min <= max)
require (min != 0 || max == 0)
require (max == 0 || isPow2(max))
require (min == 0 || isPow2(min))
require (max <= TransferSizes.maxAllowed)
def none = min == 0
def contains(x: Int) = isPow2(x) && min <= x && x <= max
def containsLg(x: Int) = contains(1 << x)
def containsLg(x: UInt) =
if (none) Bool(false)
else if (min == max) { UInt(log2Ceil(min)) === x }
else { UInt(log2Ceil(min)) <= x && x <= UInt(log2Ceil(max)) }
def contains(x: TransferSizes) = x.none || (min <= x.min && x.max <= max)
def intersect(x: TransferSizes) =
if (x.max < min || min < x.max) TransferSizes.none
else TransferSizes(scala.math.max(min, x.min), scala.math.min(max, x.max))
}
object TransferSizes {
def apply(x: Int) = new TransferSizes(x)
val none = new TransferSizes(0)
val maxAllowed = 4096 // transfers larger than 4kB are forbidden in TL2
implicit def asBool(x: TransferSizes) = !x.none
}
// AddressSets specify the address space managed by the manager
// Base is the base address, and mask are the bits consumed by the manager
// e.g: base=0x200, mask=0xff describes a device managing 0x200-0x2ff
// e.g: base=0x1000, mask=0xf0f decribes a device managing 0x1000-0x100f, 0x1100-0x110f, ...
case class AddressSet(base: BigInt, mask: BigInt)
{
// Forbid misaligned base address (and empty sets)
require ((base & mask) == 0)
def contains(x: BigInt) = ((x ^ base) & ~mask) == 0
def contains(x: UInt) = ((x ^ UInt(base)) & ~UInt(mask)) === UInt(0)
// overlap iff bitwise: both care (~mask0 & ~mask1) => both equal (base0=base1)
def overlaps(x: AddressSet) = (~(mask | x.mask) & (base ^ x.base)) == 0
// contains iff bitwise: x.mask => mask && contains(x.base)
def contains(x: AddressSet) = ((x.mask | (base ^ x.base)) & ~mask) == 0
// 1 less than the number of bytes to which the manager should be aligned
def alignment1 = ((mask + 1) & ~mask) - 1
def max = base | mask
// A strided slave serves discontiguous ranges
def strided = alignment1 != mask
}
case class TLManagerParameters(
address: Seq[AddressSet],
sinkId: IdRange = IdRange(0, 1),
regionType: RegionType.T = RegionType.GET_EFFECTS,
// Supports both Acquire+Release+Finish of these sizes
supportsAcquire: TransferSizes = TransferSizes.none,
supportsArithmetic: TransferSizes = TransferSizes.none,
supportsLogical: TransferSizes = TransferSizes.none,
supportsGet: TransferSizes = TransferSizes.none,
supportsPutFull: TransferSizes = TransferSizes.none,
supportsPutPartial: TransferSizes = TransferSizes.none,
supportsHint: Boolean = false,
// If fifoId=Some, all accesses sent to the same fifoId are executed and ACK'd in FIFO order
fifoId: Option[Int] = None)
{
address.combinations(2).foreach({ case Seq(x,y) =>
require (!x.overlaps(y))
})
address.foreach({ case a =>
require (supportsAcquire.none || a.alignment1 >= supportsAcquire.max-1)
})
// Largest support transfer of all types
val maxTransfer = List(
supportsAcquire.max,
supportsArithmetic.max,
supportsLogical.max,
supportsGet.max,
supportsPutFull.max,
supportsPutPartial.max).max
}
case class TLManagerPortParameters(managers: Seq[TLManagerParameters], beatBytes: Int)
{
require (!managers.isEmpty)
require (isPow2(beatBytes))
// Require disjoint ranges for Ids and addresses
managers.combinations(2).foreach({ case Seq(x,y) =>
require (!x.sinkId.overlaps(y.sinkId))
x.address.foreach({ a => y.address.foreach({ b =>
require (!a.overlaps(b))
})})
})
// Bounds on required sizes
def endSinkId = managers.map(_.sinkId.end).max
def maxAddress = managers.map(_.address.map(_.max).max).max
def maxTransfer = managers.map(_.maxTransfer).max
// Operation sizes supported by all outward Managers
val allSupportAcquire = managers.map(_.supportsAcquire) .reduce(_ intersect _)
val allSupportArithmetic = managers.map(_.supportsArithmetic).reduce(_ intersect _)
val allSupportLogical = managers.map(_.supportsLogical) .reduce(_ intersect _)
val allSupportGet = managers.map(_.supportsGet) .reduce(_ intersect _)
val allSupportPutFull = managers.map(_.supportsPutFull) .reduce(_ intersect _)
val allSupportPutPartial = managers.map(_.supportsPutPartial).reduce(_ intersect _)
val allSupportHint = managers.map(_.supportsHint) .reduce(_ && _)
// Operation supported by at least one outward Managers
val anySupportAcquire = managers.map(!_.supportsAcquire.none) .reduce(_ || _)
val anySupportArithmetic = managers.map(!_.supportsArithmetic.none).reduce(_ || _)
val anySupportLogical = managers.map(!_.supportsLogical.none) .reduce(_ || _)
val anySupportGet = managers.map(!_.supportsGet.none) .reduce(_ || _)
val anySupportPutFull = managers.map(!_.supportsPutFull.none) .reduce(_ || _)
val anySupportPutPartial = managers.map(!_.supportsPutPartial.none).reduce(_ || _)
val anySupportHint = managers.map( _.supportsHint) .reduce(_ || _)
// These return Option[TLManagerParameters] for your convenience
def find(address: BigInt) = managers.find(_.address.exists(_.contains(address)))
def findById(id: Int) = managers.find(_.sinkId.contains(id))
// Synthesizable lookup methods
def find(address: UInt) = Vec(managers.map(_.address.map(_.contains(address)).reduce(_ || _)))
def findById(id: UInt) = Vec(managers.map(_.sinkId.contains(id)))
// Does this Port manage this ID/address?
def contains(address: UInt) = find(address).reduce(_ || _)
def containsById(id: UInt) = findById(id).reduce(_ || _)
private def safety_helper(member: TLManagerParameters => TransferSizes)(address: UInt, lgSize: UInt) = {
val allSame = managers.map(member(_) == member(managers(0))).reduce(_ && _)
if (allSame) member(managers(0)).containsLg(lgSize) else {
Mux1H(find(address), managers.map(member(_).containsLg(lgSize)))
}
}
// Check for support of a given operation at a specific address
val supportsAcquire = safety_helper(_.supportsAcquire) _
val supportsArithmetic = safety_helper(_.supportsArithmetic) _
val supportsLogical = safety_helper(_.supportsLogical) _
val supportsGet = safety_helper(_.supportsGet) _
val supportsPutFull = safety_helper(_.supportsPutFull) _
val supportsPutPartial = safety_helper(_.supportsPutPartial) _
def supportsHint(address: UInt) = {
if (allSupportHint) Bool(true) else {
Mux1H(find(address), managers.map(m => Bool(m.supportsHint)))
}
}
}
case class TLClientParameters(
sourceId: IdRange = IdRange(0,1),
// Supports both Probe+Grant of these sizes
supportsProbe: TransferSizes = TransferSizes.none,
supportsArithmetic: TransferSizes = TransferSizes.none,
supportsLogical: TransferSizes = TransferSizes.none,
supportsGet: TransferSizes = TransferSizes.none,
supportsPutFull: TransferSizes = TransferSizes.none,
supportsPutPartial: TransferSizes = TransferSizes.none,
supportsHint: Boolean = false)
{
val maxTransfer = List(
supportsProbe.max,
supportsArithmetic.max,
supportsLogical.max,
supportsGet.max,
supportsPutFull.max,
supportsPutPartial.max).max
}
case class TLClientPortParameters(clients: Seq[TLClientParameters]) {
require (!clients.isEmpty)
// Require disjoint ranges for Ids
clients.combinations(2).foreach({ case Seq(x,y) =>
require (!x.sourceId.overlaps(y.sourceId))
})
// Bounds on required sizes
def endSourceId = clients.map(_.sourceId.end).max
def maxTransfer = clients.map(_.maxTransfer).max
// Operation sizes supported by all inward Clients
val allSupportProbe = clients.map(_.supportsProbe) .reduce(_ intersect _)
val allSupportArithmetic = clients.map(_.supportsArithmetic).reduce(_ intersect _)
val allSupportLogical = clients.map(_.supportsLogical) .reduce(_ intersect _)
val allSupportGet = clients.map(_.supportsGet) .reduce(_ intersect _)
val allSupportPutFull = clients.map(_.supportsPutFull) .reduce(_ intersect _)
val allSupportPutPartial = clients.map(_.supportsPutPartial).reduce(_ intersect _)
val allSupportHint = clients.map(_.supportsHint) .reduce(_ && _)
// Operation is supported by at least one client
val anySupportProbe = clients.map(!_.supportsProbe.none) .reduce(_ || _)
val anySupportArithmetic = clients.map(!_.supportsArithmetic.none).reduce(_ || _)
val anySupportLogical = clients.map(!_.supportsLogical.none) .reduce(_ || _)
val anySupportGet = clients.map(!_.supportsGet.none) .reduce(_ || _)
val anySupportPutFull = clients.map(!_.supportsPutFull.none) .reduce(_ || _)
val anySupportPutPartial = clients.map(!_.supportsPutPartial.none).reduce(_ || _)
val anySupportHint = clients.map( _.supportsHint) .reduce(_ || _)
// These return Option[TLClientParameters] for your convenience
def find(id: Int) = clients.find(_.sourceId.contains(id))
// Synthesizable lookup methods
def find(id: UInt) = Vec(clients.map(_.sourceId.contains(id)))
def contains(id: UInt) = find(id).reduce(_ || _)
private def safety_helper(member: TLClientParameters => TransferSizes)(id: UInt, lgSize: UInt) = {
val allSame = clients.map(member(_) == member(clients(0))).reduce(_ && _)
if (allSame) member(clients(0)).containsLg(lgSize) else {
Mux1H(find(id), clients.map(member(_).containsLg(lgSize)))
}
}
// Check for support of a given operation at a specific id
val supportsProbe = safety_helper(_.supportsProbe) _
val supportsArithmetic = safety_helper(_.supportsArithmetic) _
val supportsLogical = safety_helper(_.supportsLogical) _
val supportsGet = safety_helper(_.supportsGet) _
val supportsPutFull = safety_helper(_.supportsPutFull) _
val supportsPutPartial = safety_helper(_.supportsPutPartial) _
def supportsHint(id: UInt) = {
if (allSupportHint) Bool(true) else {
Mux1H(find(id), clients.map(c => Bool(c.supportsHint)))
}
}
}
case class TLBundleParameters(
addressBits: Int,
dataBits: Int,
sourceBits: Int,
sinkBits: Int,
sizeBits: Int)
{
// Chisel has issues with 0-width wires
require (addressBits >= 1)
require (dataBits >= 1)
require (sourceBits >= 1)
require (sinkBits >= 1)
require (sizeBits >= 1)
require (isPow2(dataBits))
def union(x: TLBundleParameters) =
TLBundleParameters(
max(addressBits, x.addressBits),
max(dataBits, x.dataBits),
max(sourceBits, x.sourceBits),
max(sinkBits, x.sinkBits),
max(sizeBits, x.sizeBits))
}
case class TLEdgeParameters(
client: TLClientPortParameters,
manager: TLManagerPortParameters)
{
val maxTransfer = max(client.maxTransfer, manager.maxTransfer)
val maxLgSize = log2Up(maxTransfer)
// Sanity check the link...
require (maxTransfer >= manager.beatBytes)
val bundle = TLBundleParameters(
addressBits = log2Up(manager.maxAddress + 1),
dataBits = manager.beatBytes * 8,
sourceBits = log2Up(client.endSourceId),
sinkBits = log2Up(manager.endSinkId),
sizeBits = log2Up(maxLgSize+1))
}