1
0

reorganize moving non-submodule packages into src/main/scala

This commit is contained in:
Howard Mao
2016-08-19 10:58:56 -07:00
parent f78da0b0ea
commit 7b20609d4d
110 changed files with 3 additions and 381 deletions

View File

@ -0,0 +1,117 @@
package uncore
import Chisel._
import cde.{Config, Parameters, ParameterDump, Knob, Dump}
import junctions.PAddrBits
import uncore.tilelink._
import uncore.agents._
import uncore.coherence._
object UncoreBuilder extends App {
val topModuleName = args(0)
val configClassName = args(1)
val config = try {
Class.forName(s"uncore.$configClassName").newInstance.asInstanceOf[Config]
} catch {
case e: java.lang.ClassNotFoundException =>
throwException("Unable to find configClassName \"" + configClassName +
"\", did you misspell it?", e)
}
val world = config.toInstance
val paramsFromConfig: Parameters = Parameters.root(world)
val gen = () =>
Class.forName(s"uncore.$topModuleName")
.getConstructor(classOf[cde.Parameters])
.newInstance(paramsFromConfig)
.asInstanceOf[Module]
chiselMain.run(args.drop(2), gen)
val pdFile = new java.io.FileWriter(s"${Driver.targetDir}/$topModuleName.prm")
pdFile.write(ParameterDump.getDump)
pdFile.close
}
class DefaultL2Config extends Config (
topDefinitions = { (pname,site,here) =>
pname match {
case PAddrBits => 32
case CacheId => 0
case CacheName => "L2Bank"
case TLId => "L1toL2"
case InnerTLId => "L1toL2"
case OuterTLId => "L2toMC"
case "N_CACHED" => Dump("N_CACHED",here[Int]("CACHED_CLIENTS_PER_PORT"))
case "N_UNCACHED" => Dump("N_UNCACHED",here[Int]("MAX_CLIENTS_PER_PORT") - here[Int]("N_CACHED"))
case "MAX_CLIENT_XACTS" => 4
case "MAX_CLIENTS_PER_PORT" => Knob("NTILES")
case "CACHED_CLIENTS_PER_PORT" => Knob("N_CACHED_TILES")
case TLKey("L1toL2") =>
TileLinkParameters(
coherencePolicy = new MESICoherence(site(L2DirectoryRepresentation)),
nManagers = 1,
nCachingClients = here[Int]("N_CACHED"),
nCachelessClients = here[Int]("N_UNCACHED"),
maxClientXacts = here[Int]("MAX_CLIENT_XACTS"),
maxClientsPerPort = here[Int]("MAX_CLIENTS_PER_PORT"),
maxManagerXacts = site(NAcquireTransactors) + 2,
dataBits = site(CacheBlockBytes)*8,
dataBeats = 2)
case TLKey("L2toMC") =>
TileLinkParameters(
coherencePolicy = new MEICoherence(new NullRepresentation(1)),
nManagers = 1,
nCachingClients = 1,
nCachelessClients = 0,
maxClientXacts = 1,
maxClientsPerPort = site(NAcquireTransactors) + 2,
maxManagerXacts = 1,
dataBits = site(CacheBlockBytes)*8,
dataBeats = 2)
case CacheBlockBytes => 64
case CacheBlockOffsetBits => log2Up(here(CacheBlockBytes))
case "L2_SETS" => Knob("L2_SETS")
case NSets => Dump("L2_SETS",here[Int]("L2_SETS"))
case NWays => Knob("L2_WAYS")
case RowBits => site(TLKey(site(TLId))).dataBitsPerBeat
case CacheIdBits => Dump("CACHE_ID_BITS",1)
case L2StoreDataQueueDepth => 1
case NAcquireTransactors => Dump("N_ACQUIRE_TRANSACTORS",2)
case NSecondaryMisses => 4
case L2DirectoryRepresentation => new FullRepresentation(here[Int]("N_CACHED"))
case L2Replacer => () => new SeqRandom(site(NWays))
case ECCCode => None
case AmoAluOperandBits => 64
case SplitMetadata => false
// case XLen => 128
}},
knobValues = {
case "L2_WAYS" => 1
case "L2_SETS" => 1024
case "NTILES" => 2
case "N_CACHED_TILES" => 2
case "L2_CAPACITY_IN_KB" => 256
}
)
class WithPLRU extends Config(
(pname, site, here) => pname match {
case L2Replacer => () => new SeqPLRU(site(NSets), site(NWays))
})
class PLRUL2Config extends Config(new WithPLRU ++ new DefaultL2Config)
class With1L2Ways extends Config(knobValues = { case "L2_WAYS" => 1 })
class With2L2Ways extends Config(knobValues = { case "L2_WAYS" => 2 })
class With4L2Ways extends Config(knobValues = { case "L2_WAYS" => 4 })
class With1Cached extends Config(knobValues = { case "N_CACHED_TILES" => 1 })
class With2Cached extends Config(knobValues = { case "N_CACHED_TILES" => 2 })
class W1Cached1WaysConfig extends Config(new With1L2Ways ++ new With1Cached ++ new DefaultL2Config)
class W1Cached2WaysConfig extends Config(new With2L2Ways ++ new With1Cached ++ new DefaultL2Config)
class W2Cached1WaysConfig extends Config(new With1L2Ways ++ new With2Cached ++ new DefaultL2Config)
class W2Cached2WaysConfig extends Config(new With2L2Ways ++ new With2Cached ++ new DefaultL2Config)

View File

@ -0,0 +1,39 @@
// See LICENSE for license details.
package uncore
package constants
import Chisel._
object MemoryOpConstants extends MemoryOpConstants
trait MemoryOpConstants {
val NUM_XA_OPS = 9
val M_SZ = 5
val M_X = BitPat("b?????");
val M_XRD = UInt("b00000"); // int load
val M_XWR = UInt("b00001"); // int store
val M_PFR = UInt("b00010"); // prefetch with intent to read
val M_PFW = UInt("b00011"); // prefetch with intent to write
val M_XA_SWAP = UInt("b00100");
val M_FLUSH_ALL = UInt("b00101") // flush all lines
val M_XLR = UInt("b00110");
val M_XSC = UInt("b00111");
val M_XA_ADD = UInt("b01000");
val M_XA_XOR = UInt("b01001");
val M_XA_OR = UInt("b01010");
val M_XA_AND = UInt("b01011");
val M_XA_MIN = UInt("b01100");
val M_XA_MAX = UInt("b01101");
val M_XA_MINU = UInt("b01110");
val M_XA_MAXU = UInt("b01111");
val M_FLUSH = UInt("b10000") // write back dirty data and cede R/W permissions
val M_PRODUCE = UInt("b10001") // write back dirty data and cede W permissions
val M_CLEAN = UInt("b10011") // write back dirty data and retain R/W permissions
def isAMO(cmd: UInt) = cmd(3) || cmd === M_XA_SWAP
def isPrefetch(cmd: UInt) = cmd === M_PFR || cmd === M_PFW
def isRead(cmd: UInt) = cmd === M_XRD || cmd === M_XLR || cmd === M_XSC || isAMO(cmd)
def isWrite(cmd: UInt) = cmd === M_XWR || cmd === M_XSC || isAMO(cmd)
def isWriteIntent(cmd: UInt) = isWrite(cmd) || cmd === M_PFW || cmd === M_XLR
}

View File

@ -0,0 +1,4 @@
// See LICENSE for license details.
package uncore
package object constants extends uncore.constants.MemoryOpConstants

View File

@ -0,0 +1,162 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import cde.{Parameters, Field}
import junctions._
import uncore.tilelink._
import uncore.converters._
import uncore.coherence._
import uncore.util._
case object NReleaseTransactors extends Field[Int]
case object NProbeTransactors extends Field[Int]
case object NAcquireTransactors extends Field[Int]
trait HasCoherenceAgentParameters {
implicit val p: Parameters
val nReleaseTransactors = 1
val nAcquireTransactors = p(NAcquireTransactors)
val nTransactors = nReleaseTransactors + nAcquireTransactors
val blockAddrBits = p(PAddrBits) - p(CacheBlockOffsetBits)
val outerTLId = p(OuterTLId)
val outerTLParams = p(TLKey(outerTLId))
val outerDataBeats = outerTLParams.dataBeats
val outerDataBits = outerTLParams.dataBitsPerBeat
val outerBeatAddrBits = log2Up(outerDataBeats)
val outerByteAddrBits = log2Up(outerDataBits/8)
val outerWriteMaskBits = outerTLParams.writeMaskBits
val innerTLId = p(InnerTLId)
val innerTLParams = p(TLKey(innerTLId))
val innerDataBeats = innerTLParams.dataBeats
val innerDataBits = innerTLParams.dataBitsPerBeat
val innerWriteMaskBits = innerTLParams.writeMaskBits
val innerBeatAddrBits = log2Up(innerDataBeats)
val innerByteAddrBits = log2Up(innerDataBits/8)
val innerNCachingClients = innerTLParams.nCachingClients
val maxManagerXacts = innerTLParams.maxManagerXacts
require(outerDataBeats == innerDataBeats) //TODO: fix all xact_data Vecs to remove this requirement
}
abstract class CoherenceAgentModule(implicit val p: Parameters) extends Module
with HasCoherenceAgentParameters
abstract class CoherenceAgentBundle(implicit val p: Parameters) extends junctions.ParameterizedBundle()(p)
with HasCoherenceAgentParameters
trait HasCoherenceAgentWiringHelpers {
def doOutputArbitration[T <: TileLinkChannel](
out: DecoupledIO[T],
ins: Seq[DecoupledIO[T]]) {
def lock(o: T) = o.hasMultibeatData()
val arb = Module(new LockingRRArbiter(out.bits, ins.size, out.bits.tlDataBeats, Some(lock _)))
out <> arb.io.out
arb.io.in <> ins
}
def doInputRouting[T <: Bundle with HasManagerTransactionId](
in: DecoupledIO[T],
outs: Seq[DecoupledIO[T]]) {
val idx = in.bits.manager_xact_id
outs.map(_.bits := in.bits)
outs.zipWithIndex.map { case (o,i) => o.valid := in.valid && idx === UInt(i) }
in.ready := outs.map(_.ready).apply(idx)
}
/** Broadcasts valid messages on this channel to all trackers,
* but includes logic to allocate a new tracker in the case where
* no previously allocated tracker matches the new req's addr.
*
* When a match is reported, if ready is high the new transaction
* is merged; when ready is low the transaction is being blocked.
* When no match is reported, any high idles are presumed to be
* from trackers that are available for allocation, and one is
* assigned via alloc based on priority; if no idles are high then
* all trackers are busy with other transactions. If idle is high
* but ready is low, the tracker will be allocated but does not
* have sufficient buffering for the data.
*/
def doInputRoutingWithAllocation[T <: TileLinkChannel with HasTileLinkData](
in: DecoupledIO[T],
outs: Seq[DecoupledIO[T]],
allocs: Seq[TrackerAllocation],
dataOverrides: Option[Seq[UInt]] = None,
allocOverride: Option[Bool] = None,
matchOverride: Option[Bool] = None) {
val ready_bits = outs.map(_.ready).asUInt
val can_alloc_bits = allocs.map(_.can).asUInt
val should_alloc_bits = PriorityEncoderOH(can_alloc_bits)
val match_bits = allocs.map(_.matches).asUInt
val no_matches = !match_bits.orR
val alloc_ok = allocOverride.getOrElse(Bool(true))
val match_ok = matchOverride.getOrElse(Bool(true))
in.ready := (Mux(no_matches, can_alloc_bits, match_bits) & ready_bits).orR && alloc_ok && match_ok
outs.zip(allocs).zipWithIndex.foreach { case((out, alloc), i) =>
out.valid := in.valid && match_ok && alloc_ok
out.bits := in.bits
dataOverrides foreach { d => out.bits.data := d(i) }
alloc.should := should_alloc_bits(i) && no_matches && alloc_ok
}
}
}
trait HasInnerTLIO extends HasCoherenceAgentParameters {
val inner = new ManagerTileLinkIO()(p.alterPartial({case TLId => p(InnerTLId)}))
val incoherent = Vec(inner.tlNCachingClients, Bool()).asInput
def iacq(dummy: Int = 0) = inner.acquire.bits
def iprb(dummy: Int = 0) = inner.probe.bits
def irel(dummy: Int = 0) = inner.release.bits
def ignt(dummy: Int = 0) = inner.grant.bits
def ifin(dummy: Int = 0) = inner.finish.bits
}
trait HasUncachedOuterTLIO extends HasCoherenceAgentParameters {
val outer = new ClientUncachedTileLinkIO()(p.alterPartial({case TLId => p(OuterTLId)}))
def oacq(dummy: Int = 0) = outer.acquire.bits
def ognt(dummy: Int = 0) = outer.grant.bits
}
trait HasCachedOuterTLIO extends HasCoherenceAgentParameters {
val outer = new ClientTileLinkIO()(p.alterPartial({case TLId => p(OuterTLId)}))
def oacq(dummy: Int = 0) = outer.acquire.bits
def oprb(dummy: Int = 0) = outer.probe.bits
def orel(dummy: Int = 0) = outer.release.bits
def ognt(dummy: Int = 0) = outer.grant.bits
}
class ManagerTLIO(implicit p: Parameters) extends CoherenceAgentBundle()(p)
with HasInnerTLIO
with HasUncachedOuterTLIO
abstract class CoherenceAgent(implicit p: Parameters) extends CoherenceAgentModule()(p) {
def innerTL: ManagerTileLinkIO
def outerTL: ClientTileLinkIO
def incoherent: Vec[Bool]
}
abstract class ManagerCoherenceAgent(implicit p: Parameters) extends CoherenceAgent()(p)
with HasCoherenceAgentWiringHelpers {
val io = new ManagerTLIO
def innerTL = io.inner
def outerTL = TileLinkIOWrapper(io.outer)(p.alterPartial({case TLId => p(OuterTLId)}))
def incoherent = io.incoherent
}
class HierarchicalTLIO(implicit p: Parameters) extends CoherenceAgentBundle()(p)
with HasInnerTLIO
with HasCachedOuterTLIO
abstract class HierarchicalCoherenceAgent(implicit p: Parameters) extends CoherenceAgent()(p)
with HasCoherenceAgentWiringHelpers {
val io = new HierarchicalTLIO
def innerTL = io.inner
def outerTL = io.outer
def incoherent = io.incoherent
// TODO: Remove this function (and all its calls) when we support probing the L2
def disconnectOuterProbeAndFinish() {
io.outer.probe.ready := Bool(false)
io.outer.finish.valid := Bool(false)
assert(!io.outer.probe.valid, "L2 agent got illegal probe")
}
}

View File

@ -0,0 +1,204 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.coherence._
import uncore.tilelink._
import uncore.constants._
import uncore.util._
import cde.Parameters
class L2BroadcastHub(implicit p: Parameters) extends HierarchicalCoherenceAgent()(p) {
// Create TSHRs for outstanding transactions
val irelTrackerList =
(0 until nReleaseTransactors).map(id =>
Module(new BufferedBroadcastVoluntaryReleaseTracker(id)))
val iacqTrackerList =
(nReleaseTransactors until nTransactors).map(id =>
Module(new BufferedBroadcastAcquireTracker(id)))
val trackerList = irelTrackerList ++ iacqTrackerList
// Propagate incoherence flags
trackerList.map(_.io.incoherent) foreach { _ := io.incoherent }
// Create an arbiter for the one memory port
val outerList = trackerList.map(_.io.outer)
val outer_arb = Module(new ClientTileLinkIOArbiter(outerList.size)
(p.alterPartial({ case TLId => p(OuterTLId) })))
outer_arb.io.in <> outerList
io.outer <> outer_arb.io.out
// Handle acquire transaction initiation
val irel_vs_iacq_conflict =
io.inner.acquire.valid &&
io.inner.release.valid &&
io.irel().conflicts(io.iacq())
doInputRoutingWithAllocation(
in = io.inner.acquire,
outs = trackerList.map(_.io.inner.acquire),
allocs = trackerList.map(_.io.alloc.iacq),
allocOverride = Some(!irel_vs_iacq_conflict))
// Handle releases, which might be voluntary and might have data
doInputRoutingWithAllocation(
in = io.inner.release,
outs = trackerList.map(_.io.inner.release),
allocs = trackerList.map(_.io.alloc.irel))
// Wire probe requests and grant reply to clients, finish acks from clients
doOutputArbitration(io.inner.probe, trackerList.map(_.io.inner.probe))
doOutputArbitration(io.inner.grant, trackerList.map(_.io.inner.grant))
doInputRouting(io.inner.finish, trackerList.map(_.io.inner.finish))
disconnectOuterProbeAndFinish()
}
class BroadcastXactTracker(implicit p: Parameters) extends XactTracker()(p) {
val io = new HierarchicalXactTrackerIO
pinAllReadyValidLow(io)
}
trait BroadcastsToAllClients extends HasCoherenceAgentParameters {
val coh = HierarchicalMetadata.onReset
val inner_coh = coh.inner
val outer_coh = coh.outer
def full_representation = ~UInt(0, width = innerNCachingClients)
}
abstract class BroadcastVoluntaryReleaseTracker(trackerId: Int)(implicit p: Parameters)
extends VoluntaryReleaseTracker(trackerId)(p)
with EmitsVoluntaryReleases
with BroadcastsToAllClients {
val io = new HierarchicalXactTrackerIO
pinAllReadyValidLow(io)
// Checks for illegal behavior
assert(!(state === s_idle && io.inner.release.fire() && io.alloc.irel.should && !io.irel().isVoluntary()),
"VoluntaryReleaseTracker accepted Release that wasn't voluntary!")
}
abstract class BroadcastAcquireTracker(trackerId: Int)(implicit p: Parameters)
extends AcquireTracker(trackerId)(p)
with EmitsVoluntaryReleases
with BroadcastsToAllClients {
val io = new HierarchicalXactTrackerIO
pinAllReadyValidLow(io)
val alwaysWriteFullBeat = false
val nSecondaryMisses = 1
def iacq_can_merge = Bool(false)
// Checks for illegal behavior
// TODO: this could be allowed, but is a useful check against allocation gone wild
assert(!(state === s_idle && io.inner.acquire.fire() && io.alloc.iacq.should &&
io.iacq().hasMultibeatData() && !io.iacq().first()),
"AcquireTracker initialized with a tail data beat.")
assert(!(state =/= s_idle && pending_ignt && xact_iacq.isPrefetch()),
"Broadcast Hub does not support Prefetches.")
assert(!(state =/= s_idle && pending_ignt && xact_iacq.isAtomic()),
"Broadcast Hub does not support PutAtomics.")
}
class BufferedBroadcastVoluntaryReleaseTracker(trackerId: Int)(implicit p: Parameters)
extends BroadcastVoluntaryReleaseTracker(trackerId)(p)
with HasDataBuffer {
// Tell the parent if any incoming messages conflict with the ongoing transaction
routeInParent(irelCanAlloc = Bool(true))
// Start transaction by accepting inner release
innerRelease(block_vol_ignt = pending_orel || vol_ognt_counter.pending)
// A release beat can be accepted if we are idle, if its a mergeable transaction, or if its a tail beat
io.inner.release.ready := state === s_idle || irel_can_merge || irel_same_xact
when(io.inner.release.fire()) { data_buffer(io.irel().addr_beat) := io.irel().data }
// Dispatch outer release
outerRelease(
coh = outer_coh.onHit(M_XWR),
data = data_buffer(vol_ognt_counter.up.idx),
add_pending_send_bit = irel_is_allocating)
quiesce() {}
}
class BufferedBroadcastAcquireTracker(trackerId: Int)(implicit p: Parameters)
extends BroadcastAcquireTracker(trackerId)(p)
with HasByteWriteMaskBuffer {
// Setup IOs used for routing in the parent
routeInParent(iacqCanAlloc = Bool(true))
// First, take care of accpeting new acquires or secondary misses
// Handling of primary and secondary misses' data and write mask merging
innerAcquire(
can_alloc = Bool(false),
next = s_inner_probe)
io.inner.acquire.ready := state === s_idle || iacq_can_merge || iacq_same_xact_multibeat
// Track which clients yet need to be probed and make Probe message
// If a writeback occurs, we can forward its data via the buffer,
// and skip having to go outwards
val skip_outer_acquire = pending_ignt_data.andR
innerProbe(
inner_coh.makeProbe(curr_probe_dst, xact_iacq, xact_addr_block),
Mux(!skip_outer_acquire, s_outer_acquire, s_busy))
// Handle incoming releases from clients, which may reduce sharer counts
// and/or write back dirty data, and may be unexpected voluntary releases
def irel_can_merge = io.irel().conflicts(xact_addr_block) &&
io.irel().isVoluntary() &&
!state.isOneOf(s_idle, s_meta_write) &&
!all_pending_done &&
!io.outer.grant.fire() &&
!io.inner.grant.fire() &&
!vol_ignt_counter.pending &&
!blockInnerRelease()
innerRelease(block_vol_ignt = vol_ognt_counter.pending)
//TODO: accept vol irels when state === s_idle, operate like the VolRelTracker
io.inner.release.ready := irel_can_merge || irel_same_xact
mergeDataInner(io.inner.release)
// If there was a writeback, forward it outwards
outerRelease(
coh = outer_coh.onHit(M_XWR),
data = data_buffer(vol_ognt_counter.up.idx))
// Send outer request for miss
outerAcquire(
caching = !xact_iacq.isBuiltInType(),
coh = outer_coh,
data = data_buffer(ognt_counter.up.idx),
wmask = wmask_buffer(ognt_counter.up.idx),
next = s_busy)
// Handle the response from outer memory
mergeDataOuter(io.outer.grant)
// Acknowledge or respond with data
innerGrant(
data = data_buffer(ignt_data_idx),
external_pending = pending_orel || ognt_counter.pending || vol_ognt_counter.pending)
when(iacq_is_allocating) {
initializeProbes()
}
initDataInner(io.inner.acquire, iacq_is_allocating || iacq_is_merging)
// Wait for everything to quiesce
quiesce() { clearWmaskBuffer() }
}

View File

@ -0,0 +1,162 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.coherence._
import uncore.tilelink._
import uncore.constants._
import cde.Parameters
class BufferlessBroadcastHub(implicit p: Parameters) extends HierarchicalCoherenceAgent()(p) {
// Create TSHRs for outstanding transactions
val irelTrackerList =
(0 until nReleaseTransactors).map(id =>
Module(new BufferlessBroadcastVoluntaryReleaseTracker(id)))
val iacqTrackerList =
(nReleaseTransactors until nTransactors).map(id =>
Module(new BufferlessBroadcastAcquireTracker(id)))
val trackerList = irelTrackerList ++ iacqTrackerList
// Propagate incoherence flags
trackerList.map(_.io.incoherent) foreach { _ := io.incoherent }
// Create an arbiter for the one memory port
val outerList = trackerList.map(_.io.outer)
val outer_arb = Module(new ClientTileLinkIOArbiter(outerList.size)
(p.alterPartial({ case TLId => p(OuterTLId) })))
outer_arb.io.in <> outerList
io.outer <> outer_arb.io.out
val iacq = Queue(io.inner.acquire, 1, pipe=true)
val irel = Queue(io.inner.release, 1, pipe=true)
// Handle acquire transaction initiation
val irel_vs_iacq_conflict =
iacq.valid &&
irel.valid &&
irel.bits.conflicts(iacq.bits)
doInputRoutingWithAllocation(
in = iacq,
outs = trackerList.map(_.io.inner.acquire),
allocs = trackerList.map(_.io.alloc.iacq),
allocOverride = Some(!irel_vs_iacq_conflict))
io.outer.acquire.bits.data := iacq.bits.data
when (io.oacq().hasData()) {
io.outer.acquire.bits.addr_beat := iacq.bits.addr_beat
}
// Handle releases, which might be voluntary and might have data
doInputRoutingWithAllocation(
in = irel,
outs = trackerList.map(_.io.inner.release),
allocs = trackerList.map(_.io.alloc.irel))
io.outer.release.bits.data := irel.bits.data
when (io.orel().hasData()) {
io.outer.release.bits.addr_beat := irel.bits.addr_beat
}
// Wire probe requests and grant reply to clients, finish acks from clients
doOutputArbitration(io.inner.probe, trackerList.map(_.io.inner.probe))
doOutputArbitration(io.inner.grant, trackerList.map(_.io.inner.grant))
io.inner.grant.bits.data := io.outer.grant.bits.data
io.inner.grant.bits.addr_beat := io.outer.grant.bits.addr_beat
doInputRouting(io.inner.finish, trackerList.map(_.io.inner.finish))
disconnectOuterProbeAndFinish()
}
class BufferlessBroadcastVoluntaryReleaseTracker(trackerId: Int)(implicit p: Parameters)
extends BroadcastVoluntaryReleaseTracker(trackerId)(p) {
// Tell the parent if any incoming messages conflict with the ongoing transaction
routeInParent(irelCanAlloc = Bool(true))
// Start transaction by accepting inner release
innerRelease(block_vol_ignt = pending_orel || vol_ognt_counter.pending)
// A release beat can be accepted if we are idle, if its a mergeable transaction, or if its a tail beat
// and if the outer relase path is clear
io.inner.release.ready := Mux(io.irel().hasData(),
(state =/= s_idle) && (irel_can_merge || irel_same_xact) && io.outer.release.ready,
(state === s_idle) || irel_can_merge || irel_same_xact)
// Dispatch outer release
outerRelease(coh = outer_coh.onHit(M_XWR), buffering = Bool(false))
quiesce() {}
}
class BufferlessBroadcastAcquireTracker(trackerId: Int)(implicit p: Parameters)
extends BroadcastAcquireTracker(trackerId)(p) {
// Setup IOs used for routing in the parent
routeInParent(iacqCanAlloc = Bool(true))
// First, take care of accpeting new acquires or secondary misses
// Handling of primary and secondary misses' data and write mask merging
innerAcquire(
can_alloc = Bool(false),
next = s_inner_probe)
// We are never going to merge anything in the bufferless hub
// Therefore, we only need to concern ourselves with the allocated
// transaction and (in case of PutBlock) subsequent tail beats
val iacq_can_forward = iacq_same_xact && !vol_ognt_counter.pending
io.inner.acquire.ready := Mux(io.iacq().hasData(),
state === s_outer_acquire && iacq_can_forward && io.outer.acquire.ready,
state === s_idle && io.alloc.iacq.should)
// Track which clients yet need to be probed and make Probe message
innerProbe(
inner_coh.makeProbe(curr_probe_dst, xact_iacq, xact_addr_block),
s_outer_acquire)
// Handle incoming releases from clients, which may reduce sharer counts
// and/or write back dirty data, and may be unexpected voluntary releases
def irel_can_merge = io.irel().conflicts(xact_addr_block) &&
io.irel().isVoluntary() &&
!vol_ignt_counter.pending &&
!(io.irel().hasData() && ognt_counter.pending) &&
(state =/= s_idle)
innerRelease(block_vol_ignt = vol_ognt_counter.pending)
val irel_could_accept = irel_can_merge || irel_same_xact
io.inner.release.ready := irel_could_accept &&
(!io.irel().hasData() || io.outer.release.ready)
// If there was a writeback, forward it outwards
outerRelease(
coh = outer_coh.onHit(M_XWR),
buffering = Bool(false),
block_orel = !irel_could_accept)
// Send outer request for miss
outerAcquire(
caching = !xact_iacq.isBuiltInType(),
block_outer_acquire = vol_ognt_counter.pending,
buffering = Bool(false),
coh = outer_coh,
next = s_busy)
// Handle the response from outer memory
when (ognt_counter.pending && io.ognt().hasData()) {
io.outer.grant.ready := io.inner.grant.ready // bypass data
}
// Acknowledge or respond with data
innerGrant(
external_pending = pending_orel || vol_ognt_counter.pending,
buffering = Bool(false))
when(iacq_is_allocating) { initializeProbes() }
// Wait for everything to quiesce
quiesce() {}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.util._
abstract class Decoding
{
def uncorrected: UInt
def corrected: UInt
def correctable: Bool
def uncorrectable: Bool
def error = correctable || uncorrectable
}
abstract class Code
{
def width(w0: Int): Int
def encode(x: UInt): UInt
def decode(x: UInt): Decoding
}
class IdentityCode extends Code
{
def width(w0: Int) = w0
def encode(x: UInt) = x
def decode(y: UInt) = new Decoding {
def uncorrected = y
def corrected = y
def correctable = Bool(false)
def uncorrectable = Bool(false)
}
}
class ParityCode extends Code
{
def width(w0: Int) = w0+1
def encode(x: UInt) = Cat(x.xorR, x)
def decode(y: UInt) = new Decoding {
def uncorrected = y(y.getWidth-2,0)
def corrected = uncorrected
def correctable = Bool(false)
def uncorrectable = y.xorR
}
}
class SECCode extends Code
{
def width(k: Int) = {
val m = log2Floor(k) + 1
k + m + (if((1 << m) < m+k+1) 1 else 0)
}
def encode(x: UInt) = {
val k = x.getWidth
require(k > 0)
val n = width(k)
val y = for (i <- 1 to n) yield {
if (isPow2(i)) {
val r = for (j <- 1 to n; if j != i && (j & i) != 0)
yield x(mapping(j))
r reduce (_^_)
} else
x(mapping(i))
}
y.asUInt
}
def decode(y: UInt) = new Decoding {
val n = y.getWidth
require(n > 0 && !isPow2(n))
val p2 = for (i <- 0 until log2Up(n)) yield 1 << i
val syndrome = (p2 map { i =>
val r = for (j <- 1 to n; if (j & i) != 0)
yield y(j-1)
r reduce (_^_)
}).asUInt
private def swizzle(z: UInt) = (1 to n).filter(i => !isPow2(i)).map(i => z(i-1)).asUInt
def uncorrected = swizzle(y)
def corrected = swizzle(((y << 1) ^ UIntToOH(syndrome)) >> 1)
def correctable = syndrome.orR
def uncorrectable = Bool(false)
}
private def mapping(i: Int) = i-1-log2Up(i)
}
class SECDEDCode extends Code
{
private val sec = new SECCode
private val par = new ParityCode
def width(k: Int) = sec.width(k)+1
def encode(x: UInt) = par.encode(sec.encode(x))
def decode(x: UInt) = new Decoding {
val secdec = sec.decode(x(x.getWidth-2,0))
val pardec = par.decode(x)
def uncorrected = secdec.uncorrected
def corrected = secdec.corrected
def correctable = pardec.uncorrectable
def uncorrectable = !pardec.uncorrectable && secdec.correctable
}
}
object ErrGen
{
// generate a 1-bit error with approximate probability 2^-f
def apply(width: Int, f: Int): UInt = {
require(width > 0 && f >= 0 && log2Up(width) + f <= 16)
UIntToOH(LFSR16()(log2Up(width)+f-1,0))(width-1,0)
}
def apply(x: UInt, f: Int): UInt = x ^ apply(x.getWidth, f)
}
class SECDEDTest extends Module
{
val code = new SECDEDCode
val k = 4
val n = code.width(k)
val io = new Bundle {
val original = Bits(OUTPUT, k)
val encoded = Bits(OUTPUT, n)
val injected = Bits(OUTPUT, n)
val uncorrected = Bits(OUTPUT, k)
val corrected = Bits(OUTPUT, k)
val correctable = Bool(OUTPUT)
val uncorrectable = Bool(OUTPUT)
}
val c = Counter(Bool(true), 1 << k)
val numErrors = Counter(c._2, 3)._1
val e = code.encode(c._1)
val i = e ^ Mux(numErrors < UInt(1), UInt(0), ErrGen(n, 1)) ^ Mux(numErrors < UInt(2), UInt(0), ErrGen(n, 1))
val d = code.decode(i)
io.original := c._1
io.encoded := e
io.injected := i
io.uncorrected := d.uncorrected
io.corrected := d.corrected
io.correctable := d.correctable
io.uncorrectable := d.uncorrectable
}

View File

@ -0,0 +1,73 @@
package uncore.agents
import Chisel._
import uncore.tilelink._
import cde.Parameters
class MMIOTileLinkManagerData(implicit p: Parameters)
extends TLBundle()(p)
with HasClientId
with HasClientTransactionId
class MMIOTileLinkManager(implicit p: Parameters)
extends CoherenceAgentModule()(p) {
val io = new ManagerTLIO
// MMIO requests should never need probe or release
io.inner.probe.valid := Bool(false)
io.inner.release.ready := Bool(false)
val multibeat_fire = io.outer.acquire.fire() && io.oacq().hasMultibeatData()
val multibeat_start = multibeat_fire && io.oacq().addr_beat === UInt(0)
val multibeat_end = multibeat_fire && io.oacq().addr_beat === UInt(outerDataBeats - 1)
// Acquire and Grant are basically passthru,
// except client_id and client_xact_id need to be converted.
// Associate the inner client_id and client_xact_id
// with the outer client_xact_id.
val xact_pending = Reg(init = UInt(0, maxManagerXacts))
val xact_id_sel = PriorityEncoder(~xact_pending)
val xact_id_reg = RegEnable(xact_id_sel, multibeat_start)
val xact_multibeat = Reg(init = Bool(false))
val outer_xact_id = Mux(xact_multibeat, xact_id_reg, xact_id_sel)
val xact_free = !xact_pending.andR
val xact_buffer = Reg(Vec(maxManagerXacts, new MMIOTileLinkManagerData))
io.inner.acquire.ready := io.outer.acquire.ready && xact_free
io.outer.acquire.valid := io.inner.acquire.valid && xact_free
io.outer.acquire.bits := io.inner.acquire.bits
io.outer.acquire.bits.client_xact_id := outer_xact_id
def isLastBeat[T <: TileLinkChannel with HasTileLinkBeatId](in: T): Bool =
!in.hasMultibeatData() || in.addr_beat === UInt(outerDataBeats - 1)
def addPendingBitOnAcq[T <: AcquireMetadata](in: DecoupledIO[T]): UInt =
Mux(in.fire() && isLastBeat(in.bits), UIntToOH(in.bits.client_xact_id), UInt(0))
def clearPendingBitOnGnt[T <: GrantMetadata](in: DecoupledIO[T]): UInt =
~Mux(in.fire() && isLastBeat(in.bits) && !in.bits.requiresAck(),
UIntToOH(in.bits.manager_xact_id), UInt(0))
def clearPendingBitOnFin(in: DecoupledIO[Finish]): UInt =
~Mux(in.fire(), UIntToOH(in.bits.manager_xact_id), UInt(0))
xact_pending := (xact_pending | addPendingBitOnAcq(io.outer.acquire)) &
clearPendingBitOnFin(io.inner.finish) &
clearPendingBitOnGnt(io.inner.grant)
when (io.outer.acquire.fire() && isLastBeat(io.outer.acquire.bits)) {
xact_buffer(outer_xact_id) := io.iacq()
}
when (multibeat_start) { xact_multibeat := Bool(true) }
when (multibeat_end) { xact_multibeat := Bool(false) }
val gnt_xact = xact_buffer(io.ognt().client_xact_id)
io.outer.grant.ready := io.inner.grant.ready
io.inner.grant.valid := io.outer.grant.valid
io.inner.grant.bits := io.outer.grant.bits
io.inner.grant.bits.client_id := gnt_xact.client_id
io.inner.grant.bits.client_xact_id := gnt_xact.client_xact_id
io.inner.grant.bits.manager_xact_id := io.ognt().client_xact_id
io.inner.finish.ready := Bool(true)
}

View File

@ -0,0 +1,69 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.coherence._
import uncore.tilelink._
import uncore.constants._
import uncore.devices._
import cde.{Parameters, Field, Config}
/** The ManagerToClientStateless Bridge does not maintain any state for the messages
* which pass through it. It simply passes the messages back and forth without any
* tracking or translation.
*
* This can reduce area and timing in very constrained situations:
* - The Manager and Client implement the same coherence protocol
* - There are no probe or finish messages.
* - The outer transaction ID is large enough to handle all possible inner
* transaction IDs, such that no remapping state must be maintained.
*
* This bridge DOES NOT keep the uncached channel coherent with the cached
* channel. Uncached requests to blocks cached by the L1 will not probe the L1.
* As a result, uncached reads to cached blocks will get stale data until
* the L1 performs a voluntary writeback, and uncached writes to cached blocks
* will get lost, as the voluntary writeback from the L1 will overwrite the
* changes. If your tile relies on probing the L1 data cache in order to
* share data between the instruction cache and data cache (e.g. you are using
* a non-blocking L1 D$) or if the tile has uncached channels capable of
* writes (e.g. Hwacha and other RoCC accelerators), DO NOT USE THIS BRIDGE.
*/
class ManagerToClientStatelessBridge(implicit p: Parameters) extends HierarchicalCoherenceAgent()(p) {
val icid = io.inner.tlClientIdBits
val ixid = io.inner.tlClientXactIdBits
val oxid = io.outer.tlClientXactIdBits
val innerCoh = io.inner.tlCoh.getClass
val outerCoh = io.outer.tlCoh.getClass
// Stateless Bridge is only usable in certain constrained situations.
// Sanity check its usage here.
require(io.inner.tlNCachingClients <= 1)
require(icid + ixid <= oxid)
require(innerCoh eq outerCoh,
s"Coherence policies do not match: inner is ${innerCoh.getSimpleName}, outer is ${outerCoh.getSimpleName}")
io.outer.acquire.valid := io.inner.acquire.valid
io.inner.acquire.ready := io.outer.acquire.ready
io.outer.acquire.bits := io.inner.acquire.bits
io.outer.acquire.bits.client_xact_id := Cat(io.inner.acquire.bits.client_id, io.inner.acquire.bits.client_xact_id)
io.outer.release.valid := io.inner.release.valid
io.inner.release.ready := io.outer.release.ready
io.outer.release.bits := io.inner.release.bits
io.outer.release.bits.client_xact_id := Cat(io.inner.release.bits.client_id, io.inner.release.bits.client_xact_id)
io.inner.grant.valid := io.outer.grant.valid
io.outer.grant.ready := io.inner.grant.ready
io.inner.grant.bits := io.outer.grant.bits
io.inner.grant.bits.client_xact_id := io.outer.grant.bits.client_xact_id(ixid-1, 0)
io.inner.grant.bits.client_id := io.outer.grant.bits.client_xact_id(icid+ixid-1, ixid)
io.inner.probe.valid := Bool(false)
io.inner.finish.ready := Bool(true)
disconnectOuterProbeAndFinish()
}

View File

@ -0,0 +1,119 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.tilelink._
import cde.{Parameters, Field}
case object L2StoreDataQueueDepth extends Field[Int]
trait HasStoreDataQueueParameters extends HasCoherenceAgentParameters {
val sdqDepth = p(L2StoreDataQueueDepth)*innerDataBeats
val dqIdxBits = math.max(log2Up(nReleaseTransactors) + 1, log2Up(sdqDepth))
val nDataQueueLocations = 3 //Stores, VoluntaryWBs, Releases
}
class DataQueueLocation(implicit p: Parameters) extends CoherenceAgentBundle()(p)
with HasStoreDataQueueParameters {
val idx = UInt(width = dqIdxBits)
val loc = UInt(width = log2Ceil(nDataQueueLocations))
}
object DataQueueLocation {
def apply(idx: UInt, loc: UInt)(implicit p: Parameters) = {
val d = Wire(new DataQueueLocation)
d.idx := idx
d.loc := loc
d
}
}
trait HasStoreDataQueue extends HasStoreDataQueueParameters {
val io: HierarchicalTLIO
val trackerIOsList: Seq[HierarchicalXactTrackerIO]
val internalDataBits = new DataQueueLocation().getWidth
val inStoreQueue :: inVolWBQueue :: inClientReleaseQueue :: Nil = Enum(UInt(), nDataQueueLocations)
val usingStoreDataQueue = p.alterPartial({
case TLKey(`innerTLId`) => innerTLParams.copy(overrideDataBitsPerBeat = Some(internalDataBits))
case TLKey(`outerTLId`) => outerTLParams.copy(overrideDataBitsPerBeat = Some(internalDataBits))
})
// Queue to store impending Put data
lazy val sdq = Reg(Vec(sdqDepth, io.iacq().data))
lazy val sdq_val = Reg(init=Bits(0, sdqDepth))
lazy val sdq_alloc_id = PriorityEncoder(~sdq_val)
lazy val sdq_rdy = !sdq_val.andR
lazy val sdq_enq = trackerIOsList.map( t =>
(t.alloc.iacq.should || t.alloc.iacq.matches) &&
t.inner.acquire.fire() &&
t.iacq().hasData()
).reduce(_||_)
lazy val sdqLoc = List.fill(nTransactors) {
DataQueueLocation(sdq_alloc_id, inStoreQueue).asUInt
}
/*
doInputRoutingWithAllocation(
in = io.inner.acquire,
outs = trackerList.map(_.io.inner.acquire),
allocs = trackerList.map(_.io.alloc._iacq),
dataOverride = Some(sdqLoc),
allocOverride = Some(sdq_rdy && !irel_vs_iacq_conflict))
*/
// Queue to store impending Voluntary Release data
lazy val voluntary = io.irel().isVoluntary()
lazy val vwbdq_enq = io.inner.release.fire() && voluntary && io.irel().hasData()
lazy val (rel_data_cnt, rel_data_done) = Counter(vwbdq_enq, innerDataBeats) //TODO Zero width
lazy val vwbdq = Reg(Vec(innerDataBeats, io.irel().data)) //TODO Assumes nReleaseTransactors == 1
lazy val vwbqLoc = (0 until nTransactors).map(i =>
(DataQueueLocation(rel_data_cnt,
(if(i < nReleaseTransactors) inVolWBQueue
else inClientReleaseQueue)).asUInt))
/*
doInputRoutingWithAllocation(
io.inner.release,
trackerList.map(_.io.inner.release),
trackerList.map(_.io.matches.irel),
trackerList.map(_.io.alloc.irel),
Some(vwbqLoc))
*/
val outer_arb: ClientTileLinkIOArbiter
lazy val outer_data_ptr = new DataQueueLocation().fromBits(outer_arb.io.out.acquire.bits.data)
/*
val outer_arb = Module(new ClientTileLinkIOArbiter(trackerList.size)
(usingStoreDataQueue.alterPartial({ case TLId => p(OuterTLId) })))
outer_arb.io.in <> trackerList
*/
// Get the pending data out of the store data queue
lazy val is_in_sdq = outer_data_ptr.loc === inStoreQueue
lazy val free_sdq = io.outer.acquire.fire() &&
io.outer.acquire.bits.hasData() &&
outer_data_ptr.loc === inStoreQueue
/*
io.outer <> outer_arb.io.out
io.outer.acquire.bits.data := MuxLookup(outer_data_ptr.loc, io.irel().data, Array(
inStoreQueue -> sdq(outer_data_ptr.idx),
inVolWBQueue -> vwbdq(outer_data_ptr.idx)))
*/
// Enqueue SDQ data
def sdqEnqueue() {
when (sdq_enq) { sdq(sdq_alloc_id) := io.iacq().data }
when(vwbdq_enq) { vwbdq(rel_data_cnt) := io.irel().data }
}
// Update SDQ valid bits
def sdqUpdate() {
when (io.outer.acquire.valid || sdq_enq) {
sdq_val := sdq_val & ~(UIntToOH(outer_data_ptr.idx) & Fill(sdqDepth, free_sdq)) |
PriorityEncoderOH(~sdq_val(sdqDepth-1,0)) & Fill(sdqDepth, sdq_enq)
}
}
}

View File

@ -0,0 +1,654 @@
// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.coherence._
import uncore.tilelink._
import uncore.util._
import uncore.util._
import junctions._
import cde.{Field, Parameters}
import scala.math.max
case object EnableL2Logging extends Field[Boolean]
class TrackerAllocation extends Bundle {
val matches = Bool(OUTPUT)
val can = Bool(OUTPUT)
val should = Bool(INPUT)
}
class TrackerAllocationIO(implicit val p: Parameters)
extends ParameterizedBundle()(p)
with HasCacheBlockAddress {
val iacq = new TrackerAllocation
val irel = new TrackerAllocation
val oprb = new TrackerAllocation
val idle = Bool(OUTPUT)
override val addr_block = UInt(OUTPUT, tlBlockAddrBits)
}
trait HasTrackerAllocationIO extends Bundle {
implicit val p: Parameters
val alloc = new TrackerAllocationIO
}
class ManagerXactTrackerIO(implicit p: Parameters) extends ManagerTLIO()(p)
with HasTrackerAllocationIO
class HierarchicalXactTrackerIO(implicit p: Parameters) extends HierarchicalTLIO()(p)
with HasTrackerAllocationIO
abstract class XactTracker(implicit p: Parameters) extends CoherenceAgentModule()(p)
with HasXactTrackerStates
with HasPendingBitHelpers {
override val s_idle :: s_meta_read :: s_meta_resp :: s_wb_req :: s_wb_resp :: s_inner_probe :: s_outer_acquire :: s_busy :: s_meta_write :: Nil = Enum(UInt(), 9)
val state = Reg(init=s_idle)
def quiesce(next: UInt = s_idle)(restore: => Unit) {
all_pending_done := !scoreboard.foldLeft(Bool(false))(_||_)
when(state === s_busy && all_pending_done) {
state := next
restore
}
}
def pinAllReadyValidLow[T <: Data](b: Bundle) {
b.elements.foreach {
_._2 match {
case d: DecoupledIO[_] =>
if(d.ready.dir == OUTPUT) d.ready := Bool(false)
else if(d.valid.dir == OUTPUT) d.valid := Bool(false)
case v: ValidIO[_] => if(v.valid.dir == OUTPUT) v.valid := Bool(false)
case b: Bundle => pinAllReadyValidLow(b)
case _ =>
}
}
}
}
trait HasXactTrackerStates {
def state: UInt
def s_idle: UInt = UInt(0)
def s_meta_read: UInt
def s_meta_resp: UInt
def s_wb_req: UInt
def s_wb_resp: UInt
def s_inner_probe: UInt
def s_outer_acquire: UInt
def s_busy: UInt
def s_meta_write: UInt
}
trait HasPendingBitHelpers extends HasDataBeatCounters {
val scoreboard = scala.collection.mutable.ListBuffer.empty[Bool]
val all_pending_done = Wire(Bool())
def addPendingBitWhenBeat[T <: HasBeat](inc: Bool, in: T): UInt =
Fill(in.tlDataBeats, inc) & UIntToOH(in.addr_beat)
def dropPendingBitWhenBeat[T <: HasBeat](dec: Bool, in: T): UInt =
~Fill(in.tlDataBeats, dec) | ~UIntToOH(in.addr_beat)
def addPendingBitWhenId[T <: HasClientId](inc: Bool, in: T): UInt =
Fill(in.tlNCachingClients, inc) & UIntToOH(in.client_id)
def dropPendingBitWhenId[T <: HasClientId](dec: Bool, in: T): UInt =
~Fill(in.tlNCachingClients, dec) | ~UIntToOH(in.client_id)
def addPendingBitWhenBeatHasData[T <: HasBeat](in: DecoupledIO[T], inc: Bool = Bool(true)): UInt =
addPendingBitWhenBeat(in.fire() && in.bits.hasData() && inc, in.bits)
def addPendingBitWhenBeatHasDataAndAllocs(
in: DecoupledIO[AcquireFromSrc],
alloc_override: Bool = Bool(false)): UInt =
addPendingBitWhenBeatHasData(in, in.bits.allocate() || alloc_override)
def addPendingBitWhenBeatNeedsRead(in: DecoupledIO[AcquireFromSrc],
always: Bool = Bool(true), unless: Bool = Bool(false)): UInt = {
val a = in.bits
val needs_read = !unless && (a.isGet() || a.isAtomic() || a.hasPartialWritemask()) || always
addPendingBitWhenBeat(in.fire() && needs_read, a)
}
def addPendingBitWhenBeatHasPartialWritemask(in: DecoupledIO[AcquireFromSrc]): UInt =
addPendingBitWhenBeat(in.fire() && in.bits.hasPartialWritemask(), in.bits)
def addPendingBitsFromAcquire(a: SecondaryMissInfo): UInt =
Mux(a.hasMultibeatData(), Fill(a.tlDataBeats, UInt(1, 1)), UIntToOH(a.addr_beat))
def dropPendingBitWhenBeatHasData[T <: HasBeat](in: DecoupledIO[T]): UInt =
dropPendingBitWhenBeat(in.fire() && in.bits.hasData(), in.bits)
def dropPendingBitAtDest[T <: HasId](in: DecoupledIO[T]): UInt =
dropPendingBitWhenId(in.fire(), in.bits)
def dropPendingBitAtDestWhenVoluntary[T <: HasId with MightBeVoluntary](in: DecoupledIO[T]): UInt =
dropPendingBitWhenId(in.fire() && in.bits.isVoluntary(), in.bits)
def addPendingBitAtSrc[T <: HasId](in: DecoupledIO[T]): UInt =
addPendingBitWhenId(in.fire(), in.bits)
def addPendingBitAtSrcWhenVoluntary[T <: HasId with MightBeVoluntary](in: DecoupledIO[T]): UInt =
addPendingBitWhenId(in.fire() && in.bits.isVoluntary(), in.bits)
def addOtherBits(en: Bool, nBits: Int): UInt =
Mux(en, Cat(Fill(nBits - 1, UInt(1, 1)), UInt(0, 1)), UInt(0, nBits))
def addPendingBitsOnFirstBeat(in: DecoupledIO[Acquire]): UInt =
addOtherBits(in.fire() &&
in.bits.hasMultibeatData() &&
in.bits.addr_beat === UInt(0),
in.bits.tlDataBeats)
def dropPendingBitsOnFirstBeat(in: DecoupledIO[Acquire]): UInt =
~addPendingBitsOnFirstBeat(in)
}
trait HasDataBuffer extends HasCoherenceAgentParameters {
val data_buffer = Reg(init=Vec.fill(innerDataBeats)(UInt(0, width = innerDataBits)))
type TLDataBundle = TLBundle with HasTileLinkData with HasTileLinkBeatId
def initDataInner[T <: Acquire](in: DecoupledIO[T], alloc: Bool) {
when(in.fire() && in.bits.hasData() && alloc) {
data_buffer(in.bits.addr_beat) := in.bits.data
}
}
// TODO: provide func for accessing when innerDataBeats =/= outerDataBeats or internalDataBeats
def mergeData(dataBits: Int)(beat: UInt, incoming: UInt) {
data_buffer(beat) := incoming
}
def mergeDataInner[T <: TLDataBundle](in: DecoupledIO[T]) {
when(in.fire() && in.bits.hasData()) {
mergeData(innerDataBits)(in.bits.addr_beat, in.bits.data)
}
}
def mergeDataOuter[T <: TLDataBundle](in: DecoupledIO[T]) {
when(in.fire() && in.bits.hasData()) {
mergeData(outerDataBits)(in.bits.addr_beat, in.bits.data)
}
}
}
trait HasByteWriteMaskBuffer extends HasDataBuffer {
val wmask_buffer = Reg(init=Vec.fill(innerDataBeats)(UInt(0, width = innerWriteMaskBits)))
val data_valid = Vec(wmask_buffer.map(wmask => wmask.andR))
override def initDataInner[T <: Acquire](in: DecoupledIO[T], alloc: Bool) {
when(in.fire() && in.bits.hasData() && alloc) {
val beat = in.bits.addr_beat
val full = FillInterleaved(8, in.bits.wmask())
data_buffer(beat) := (~full & data_buffer(beat)) | (full & in.bits.data)
wmask_buffer(beat) := in.bits.wmask() | wmask_buffer(beat) // assumes wmask_buffer is zeroed
}
}
override def mergeData(dataBits: Int)(beat: UInt, incoming: UInt) {
val old_data = incoming // Refilled, written back, or de-cached data
val new_data = data_buffer(beat) // Newly Put data is already in the buffer
val wmask = FillInterleaved(8, wmask_buffer(beat))
data_buffer(beat) := (~wmask & old_data) | (wmask & new_data)
wmask_buffer(beat) := ~UInt(0, innerWriteMaskBits)
}
def clearWmaskBuffer() {
wmask_buffer.foreach { w => w := UInt(0) }
}
}
trait HasBlockAddressBuffer extends HasCoherenceAgentParameters {
val xact_addr_block = Reg(init = UInt(0, width = blockAddrBits))
}
trait HasAcquireMetadataBuffer extends HasBlockAddressBuffer {
val xact_allocate = Reg{ Bool() }
val xact_amo_shift_bytes = Reg{ UInt() }
val xact_op_code = Reg{ UInt() }
val xact_addr_byte = Reg{ UInt() }
val xact_op_size = Reg{ UInt() }
val xact_addr_beat = Wire(UInt())
val xact_iacq = Wire(new SecondaryMissInfo)
}
trait HasVoluntaryReleaseMetadataBuffer extends HasBlockAddressBuffer
with HasPendingBitHelpers
with HasXactTrackerStates {
def io: HierarchicalXactTrackerIO
val xact_vol_ir_r_type = Reg{ UInt() }
val xact_vol_ir_src = Reg{ UInt() }
val xact_vol_ir_client_xact_id = Reg{ UInt() }
def xact_vol_irel = Release(
src = xact_vol_ir_src,
voluntary = Bool(true),
r_type = xact_vol_ir_r_type,
client_xact_id = xact_vol_ir_client_xact_id,
addr_block = xact_addr_block)
(p.alterPartial({ case TLId => p(InnerTLId) }))
}
trait AcceptsVoluntaryReleases extends HasVoluntaryReleaseMetadataBuffer {
def inner_coh: ManagerMetadata
val pending_irel_data = Reg(init=Bits(0, width = innerDataBeats))
val vol_ignt_counter = Wire(new TwoWayBeatCounterStatus)
def irel_can_merge: Bool
def irel_same_xact: Bool
def irel_is_allocating: Bool = state === s_idle && io.alloc.irel.should && io.inner.release.valid
def irel_is_merging: Bool = (irel_can_merge || irel_same_xact) && io.inner.release.valid
def innerRelease(block_vol_ignt: Bool = Bool(false), next: UInt = s_busy) {
connectTwoWayBeatCounters(
status = vol_ignt_counter,
up = io.inner.release,
down = io.inner.grant,
trackUp = (r: Release) => {
Mux(state === s_idle, io.alloc.irel.should, io.alloc.irel.matches) && r.isVoluntary() && r.requiresAck()
},
trackDown = (g: Grant) => (state =/= s_idle) && g.isVoluntary())
when(irel_is_allocating) {
xact_addr_block := io.irel().addr_block
// Set all of them to pending in the beginning as a precaution
// If it turns out we don't need some or all of the beats, they will
// be overridden below
pending_irel_data := ~UInt(0, innerDataBeats)
state := next
}
val irel_fire = (irel_is_allocating || irel_is_merging) && io.inner.release.ready
when (irel_fire) {
when (io.irel().first()) {
when (io.irel().isVoluntary()) {
xact_vol_ir_r_type := io.irel().r_type
xact_vol_ir_src := io.irel().client_id
xact_vol_ir_client_xact_id := io.irel().client_xact_id
}
// If this release has data, set all the pending bits except the first.
// Otherwise, clear all the pending bits
pending_irel_data := Mux(io.irel().hasMultibeatData(),
dropPendingBitWhenBeatHasData(io.inner.release),
UInt(0))
} .otherwise {
pending_irel_data := (pending_irel_data & dropPendingBitWhenBeatHasData(io.inner.release))
}
if (p(EnableL2Logging)) {
when (io.irel().hasData()) {
printf("[release] addr_block=%x addr_beat=%d data=%x\n",
io.irel().addr_block, io.irel().addr_beat, io.irel().data)
}
}
}
io.inner.grant.valid := state.isOneOf(s_wb_req, s_wb_resp, s_inner_probe, s_busy) &&
vol_ignt_counter.pending &&
!(pending_irel_data.orR || block_vol_ignt)
io.inner.grant.bits := inner_coh.makeGrant(xact_vol_irel)
scoreboard += (pending_irel_data.orR, vol_ignt_counter.pending)
}
}
trait EmitsVoluntaryReleases extends HasVoluntaryReleaseMetadataBuffer {
val pending_orel_send = Reg(init=Bool(false))
val pending_orel_data = Reg(init=Bits(0, width = innerDataBeats))
val vol_ognt_counter = Wire(new TwoWayBeatCounterStatus)
val pending_orel = pending_orel_send || pending_orel_data.orR || vol_ognt_counter.pending
val sending_orel = Reg(init = Bool(false))
// Block acceptance of inner releases if we have already started sending
// outer releases, but have not yet sent out the beat corresponding to the
// inner release. This function must be included in io.inner.release.ready
// if it is possible to start accepting a new inner release as the previous
// outer release is still being sent. DO NOT include this in the
// io.inner.release.ready if the releases are not buffered
// (i.e. io.inner.release and io.outer.release combinationally linked).
def blockInnerRelease(rel: ReleaseMetadata = io.irel()): Bool = {
val waiting_to_send = sending_orel && pending_orel_data(rel.addr_beat)
val sending_now = io.outer.release.fire() && rel.addr_beat === io.orel().addr_beat
rel.hasData() && (waiting_to_send || sending_now)
}
def outerRelease(
coh: ClientMetadata,
buffering: Bool = Bool(true),
data: UInt = io.irel().data,
add_pending_data_bits: UInt = UInt(0),
add_pending_send_bit: Bool = Bool(false),
block_orel: Bool = Bool(false)) {
when (state =/= s_idle || io.alloc.irel.should) {
pending_orel_data := (pending_orel_data |
addPendingBitWhenBeatHasData(io.inner.release) |
add_pending_data_bits) &
dropPendingBitWhenBeatHasData(io.outer.release)
}
when (add_pending_send_bit) { pending_orel_send := Bool(true) }
when (io.outer.release.fire()) {
when (io.outer.release.bits.first()) { sending_orel := Bool(true) }
when (io.outer.release.bits.last()) { sending_orel := Bool(false) }
pending_orel_send := Bool(false)
}
connectTwoWayBeatCounters(
status = vol_ognt_counter,
up = io.outer.release,
down = io.outer.grant,
trackUp = (r: Release) => r.isVoluntary() && r.requiresAck(),
trackDown = (g: Grant) => g.isVoluntary())
io.outer.release.valid := !block_orel && Mux(buffering,
(state === s_busy) && Mux(io.orel().hasData(),
pending_orel_data(vol_ognt_counter.up.idx),
pending_orel_send),
// only writebacks need to be forwarded to the outer interface
state =/= s_idle && io.alloc.irel.matches &&
io.irel().hasData() && io.inner.release.valid)
io.outer.release.bits := coh.makeVoluntaryWriteback(
client_xact_id = UInt(0), // TODO was tracker id, but not needed?
addr_block = xact_addr_block,
addr_beat = vol_ognt_counter.up.idx,
data = data)
when (vol_ognt_counter.pending) { io.outer.grant.ready := Bool(true) }
scoreboard += (pending_orel, vol_ognt_counter.pending)
}
}
trait EmitsInnerProbes extends HasBlockAddressBuffer
with HasXactTrackerStates
with HasPendingBitHelpers {
def io: HierarchicalXactTrackerIO
val needs_probes = (innerNCachingClients > 0)
val pending_iprbs = Reg(UInt(width = max(innerNCachingClients, 1)))
val curr_probe_dst = PriorityEncoder(pending_iprbs)
def full_representation: UInt
def initializeProbes() {
if (needs_probes)
pending_iprbs := full_representation & ~io.incoherent.asUInt
else
pending_iprbs := UInt(0)
}
def irel_same_xact = io.irel().conflicts(xact_addr_block) &&
!io.irel().isVoluntary() &&
state === s_inner_probe
def innerProbe(prb: Probe, next: UInt) {
if (needs_probes) {
val irel_counter = Wire(new TwoWayBeatCounterStatus)
pending_iprbs := pending_iprbs & dropPendingBitAtDest(io.inner.probe)
io.inner.probe.valid := state === s_inner_probe && pending_iprbs.orR
io.inner.probe.bits := prb
connectTwoWayBeatCounters(
status = irel_counter,
up = io.inner.probe,
down = io.inner.release,
max = innerNCachingClients,
trackDown = (r: Release) => (state =/= s_idle) && !r.isVoluntary())
when(state === s_inner_probe && !(pending_iprbs.orR || irel_counter.pending)) {
state := next
}
} else {
when (state === s_inner_probe) { state := next }
}
//N.B. no pending bits added to scoreboard because all handled in s_inner_probe
}
}
trait RoutesInParent extends HasBlockAddressBuffer
with HasXactTrackerStates {
def io: HierarchicalXactTrackerIO
type AddrComparison = HasCacheBlockAddress => Bool
def exactAddrMatch(a: HasCacheBlockAddress): Bool = a.conflicts(xact_addr_block)
def routeInParent(iacqMatches: AddrComparison = exactAddrMatch,
irelMatches: AddrComparison = exactAddrMatch,
oprbMatches: AddrComparison = exactAddrMatch,
iacqCanAlloc: Bool = Bool(false),
irelCanAlloc: Bool = Bool(false),
oprbCanAlloc: Bool = Bool(false)) {
io.alloc.iacq.matches := (state =/= s_idle) && iacqMatches(io.iacq())
io.alloc.irel.matches := (state =/= s_idle) && irelMatches(io.irel())
io.alloc.oprb.matches := (state =/= s_idle) && oprbMatches(io.oprb())
io.alloc.iacq.can := state === s_idle && iacqCanAlloc
io.alloc.irel.can := state === s_idle && irelCanAlloc
io.alloc.oprb.can := state === s_idle && oprbCanAlloc
io.alloc.addr_block := xact_addr_block
io.alloc.idle := state === s_idle
}
}
trait AcceptsInnerAcquires extends HasAcquireMetadataBuffer
with AcceptsVoluntaryReleases
with HasXactTrackerStates
with HasPendingBitHelpers {
def io: HierarchicalXactTrackerIO
def nSecondaryMisses: Int
def alwaysWriteFullBeat: Boolean
def inner_coh: ManagerMetadata
def trackerId: Int
// Secondary miss queue holds transaction metadata used to make grants
lazy val ignt_q = Module(new Queue(
new SecondaryMissInfo()(p.alterPartial({ case TLId => p(InnerTLId) })),
1 + nSecondaryMisses))
val pending_ignt = Wire(Bool())
val ignt_data_idx = Wire(UInt())
val ignt_data_done = Wire(Bool())
val ifin_counter = Wire(new TwoWayBeatCounterStatus)
val pending_put_data = Reg(init=Bits(0, width = innerDataBeats))
val pending_ignt_data = Reg(init=Bits(0, width = innerDataBeats))
def iacq_same_xact: Bool =
(xact_iacq.client_xact_id === io.iacq().client_xact_id) &&
(xact_iacq.client_id === io.iacq().client_id) &&
pending_ignt
def iacq_same_xact_multibeat = iacq_same_xact && io.iacq().hasMultibeatData()
def iacq_can_merge: Bool
def iacq_is_allocating: Bool = state === s_idle && io.alloc.iacq.should && io.inner.acquire.valid
def iacq_is_merging: Bool = (iacq_can_merge || iacq_same_xact) && io.inner.acquire.valid
def innerAcquire(can_alloc: Bool, next: UInt) {
val iacq_matches_head = iacq_same_xact && xact_iacq.addr_beat === io.iacq().addr_beat
// Enqueue some metadata information that we'll use to make coherence updates with later
ignt_q.io.enq.valid := iacq_is_allocating ||
(!iacq_matches_head && pending_ignt &&
io.inner.acquire.fire() && io.iacq().first())
ignt_q.io.enq.bits := io.iacq()
// Use the outputs of the queue to make further messages
xact_iacq := Mux(ignt_q.io.deq.valid, ignt_q.io.deq.bits, ignt_q.io.enq.bits)
xact_addr_beat := xact_iacq.addr_beat
pending_ignt := ignt_q.io.count > UInt(0)
// Track whether any beats are missing from a PutBlock
when (state =/= s_idle || io.alloc.iacq.should) {
pending_put_data := (pending_put_data &
dropPendingBitWhenBeatHasData(io.inner.acquire)) |
addPendingBitsOnFirstBeat(io.inner.acquire)
}
// Intialize transaction metadata for accepted Acquire
when(iacq_is_allocating) {
xact_addr_block := io.iacq().addr_block
xact_allocate := io.iacq().allocate() && can_alloc
xact_amo_shift_bytes := io.iacq().amo_shift_bytes()
xact_op_code := io.iacq().op_code()
xact_addr_byte := io.iacq().addr_byte()
xact_op_size := io.iacq().op_size()
// Make sure to collect all data from a PutBlock
pending_put_data := Mux(
io.iacq().isBuiltInType(Acquire.putBlockType),
dropPendingBitWhenBeatHasData(io.inner.acquire),
UInt(0))
pending_ignt_data := UInt(0)
state := next
}
scoreboard += (pending_put_data.orR)
}
def innerGrant(
data: UInt = io.ognt().data,
external_pending: Bool = Bool(false),
buffering: Bool = Bool(true),
add_pending_bits: UInt = UInt(0)) {
// Track the number of outstanding inner.finishes
connectTwoWayBeatCounters(
status = ifin_counter,
up = io.inner.grant,
down = io.inner.finish,
max = nSecondaryMisses,
trackUp = (g: Grant) => g.requiresAck())
// Track which beats are ready for response
when(!iacq_is_allocating) {
pending_ignt_data := pending_ignt_data |
addPendingBitWhenBeatHasData(io.inner.release) |
addPendingBitWhenBeatHasData(io.outer.grant) |
add_pending_bits
}
if (p(EnableL2Logging)) {
when (io.inner.grant.fire() && io.ignt().hasData()) {
printf("[get] addr_block=%x addr_beat=%d data=%x\n",
xact_addr_block, io.ignt().addr_beat, io.ignt().data)
}
}
// Have we finished receiving the complete inner acquire transaction?
val iacq_finished = !(state === s_idle ||
state === s_meta_read ||
pending_put_data.orR)
val ignt_from_iacq = inner_coh.makeGrant(
sec = ignt_q.io.deq.bits,
manager_xact_id = UInt(trackerId),
data = data)
// Make the Grant message using the data stored in the secondary miss queue
val (cnt, done) = connectOutgoingDataBeatCounter(io.inner.grant, ignt_q.io.deq.bits.addr_beat)
ignt_data_idx := cnt
ignt_data_done := done
ignt_q.io.deq.ready := Bool(false)
when(!vol_ignt_counter.pending) {
ignt_q.io.deq.ready := ignt_data_done
io.inner.grant.bits := ignt_from_iacq
io.inner.grant.bits.addr_beat := ignt_data_idx // override based on outgoing counter
when (state === s_busy && pending_ignt) {
io.inner.grant.valid := !external_pending &&
Mux(io.ignt().hasData(),
Mux(buffering,
pending_ignt_data(ignt_data_idx),
io.outer.grant.valid),
iacq_finished)
}
}
// We must wait for as many Finishes as we sent Grants
io.inner.finish.ready := state === s_busy
scoreboard += (pending_ignt, ifin_counter.pending)
}
}
trait EmitsOuterAcquires extends AcceptsInnerAcquires {
val ognt_counter = Wire(new TwoWayBeatCounterStatus)
// Handle misses or coherence permission upgrades by initiating a new transaction in the outer memory:
//
// If we're allocating in this cache, we can use the current metadata
// to make an appropriate custom Acquire, otherwise we copy over the
// built-in Acquire from the inner TL to the outer TL
def outerAcquire(
caching: Bool,
coh: ClientMetadata,
block_outer_acquire: Bool = Bool(false),
buffering: Bool = Bool(true),
data: UInt = io.iacq().data,
wmask: UInt = io.iacq().wmask(),
next: UInt = s_busy) {
// Tracks outstanding Acquires, waiting for their matching Grant.
connectTwoWayBeatCounters(
status = ognt_counter,
up = io.outer.acquire,
down = io.outer.grant,
beat = xact_addr_beat,
trackDown = (g: Grant) => !g.isVoluntary())
io.outer.acquire.valid :=
state === s_outer_acquire && !block_outer_acquire &&
(xact_allocate ||
Mux(buffering,
!pending_put_data(ognt_counter.up.idx),
// If not buffering, we should only send an outer acquire if
// the ignt_q is not empty (pending_ignt) and the enqueued
// transaction does not have data or we are receiving the
// inner acquire and it is the same transaction as the one enqueued.
pending_ignt && (!xact_iacq.hasData() ||
(io.inner.acquire.valid && iacq_same_xact))))
io.outer.acquire.bits :=
Mux(caching,
coh.makeAcquire(
op_code = xact_op_code,
client_xact_id = UInt(0),
addr_block = xact_addr_block),
BuiltInAcquireBuilder(
a_type = xact_iacq.a_type,
client_xact_id = UInt(0),
addr_block = xact_addr_block,
addr_beat = ognt_counter.up.idx,
data = data,
addr_byte = xact_addr_byte,
operand_size = xact_op_size,
opcode = xact_op_code,
wmask = wmask,
alloc = Bool(false))
(p.alterPartial({ case TLId => p(OuterTLId)})))
when(state === s_outer_acquire && ognt_counter.up.done) { state := next }
when (ognt_counter.pending) { io.outer.grant.ready := Bool(true) }
scoreboard += ognt_counter.pending
}
}
abstract class VoluntaryReleaseTracker(val trackerId: Int)(implicit p: Parameters) extends XactTracker()(p)
with AcceptsVoluntaryReleases
with RoutesInParent {
def irel_can_merge = Bool(false)
def irel_same_xact = io.irel().conflicts(xact_addr_block) &&
io.irel().isVoluntary() &&
pending_irel_data.orR
}
abstract class AcquireTracker(val trackerId: Int)(implicit p: Parameters) extends XactTracker()(p)
with AcceptsInnerAcquires
with EmitsOuterAcquires
with EmitsInnerProbes
with RoutesInParent {
}

View File

@ -0,0 +1,43 @@
// See LICENSE for license details.
package uncore.coherence
import Chisel._
// This class encapsulates transformations on different directory information
// storage formats
abstract class DirectoryRepresentation(val width: Int) {
def pop(prev: UInt, id: UInt): UInt
def push(prev: UInt, id: UInt): UInt
def flush: UInt
def none(s: UInt): Bool
def one(s: UInt): Bool
def count(s: UInt): UInt
def next(s: UInt): UInt
def full(s: UInt): UInt
}
abstract trait HasDirectoryRepresentation {
val dir: DirectoryRepresentation
}
class NullRepresentation(nClients: Int) extends DirectoryRepresentation(1) {
def pop(prev: UInt, id: UInt) = UInt(0)
def push(prev: UInt, id: UInt) = UInt(0)
def flush = UInt(0)
def none(s: UInt) = Bool(false)
def one(s: UInt) = Bool(false)
def count(s: UInt) = UInt(nClients)
def next(s: UInt) = UInt(0)
def full(s: UInt) = SInt(-1, width = nClients).asUInt
}
class FullRepresentation(nClients: Int) extends DirectoryRepresentation(nClients) {
def pop(prev: UInt, id: UInt) = prev & ~UIntToOH(id)
def push(prev: UInt, id: UInt) = prev | UIntToOH(id)
def flush = UInt(0, width = width)
def none(s: UInt) = s === UInt(0)
def one(s: UInt) = PopCount(s) === UInt(1)
def count(s: UInt) = PopCount(s)
def next(s: UInt) = PriorityEncoder(s)
def full(s: UInt) = s
}

View File

@ -0,0 +1,344 @@
// See LICENSE for license details.
package uncore.coherence
import Chisel._
import uncore.tilelink._
import uncore.constants._
import cde.{Parameters, Field}
/** Identifies the TLId of the inner network in a hierarchical cache controller */
case object InnerTLId extends Field[String]
/** Identifies the TLId of the outer network in a hierarchical cache controller */
case object OuterTLId extends Field[String]
/** Base class to represent coherence information in clients and managers */
abstract class CoherenceMetadata(implicit p: Parameters) extends TLBundle()(p) {
val co = tlCoh
}
/** Stores the client-side coherence information,
* such as permissions on the data and whether the data is dirty.
* Its API can be used to make TileLink messages in response to
* memory operations or [[uncore.Probe]] messages.
*/
class ClientMetadata(implicit p: Parameters) extends CoherenceMetadata()(p) {
/** Actual state information stored in this bundle */
val state = UInt(width = co.clientStateWidth)
/** Metadata equality */
def ===(rhs: ClientMetadata): Bool = this.state === rhs.state
def =/=(rhs: ClientMetadata): Bool = !this.===(rhs)
/** Is the block's data present in this cache */
def isValid(dummy: Int = 0): Bool = co.isValid(this)
/** Does this cache have permissions on this block sufficient to perform op */
def isHit(op_code: UInt): Bool = co.isHit(op_code, this)
/** Does this cache lack permissions on this block sufficient to perform op */
def isMiss(op_code: UInt): Bool = !co.isHit(op_code, this)
/** Does a secondary miss on the block require another Acquire message */
def requiresAcquireOnSecondaryMiss(first_op: UInt, second_op: UInt): Bool =
co.requiresAcquireOnSecondaryMiss(first_op, second_op, this)
/** Does op require a Release to be made to outer memory */
def requiresReleaseOnCacheControl(op_code: UInt): Bool =
co.requiresReleaseOnCacheControl(op_code: UInt, this)
/** Does an eviction require a Release to be made to outer memory */
def requiresVoluntaryWriteback(dummy: Int = 0): Bool =
co.requiresReleaseOnCacheControl(M_FLUSH, this)
/** Constructs an Acquire message based on this metdata and a memory operation
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param op_code a memory operation from [[uncore.constants.MemoryOpConstants]]
*/
def makeAcquire(
op_code: UInt,
client_xact_id: UInt,
addr_block: UInt): Acquire = {
Acquire(
is_builtin_type = Bool(false),
a_type = co.getAcquireType(op_code, this),
client_xact_id = client_xact_id,
addr_block = addr_block,
union = Cat(op_code, Bool(true)))(p)
}
/** Constructs a Release message based on this metadata on cache control op
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (which beat)
* @param data data being written back
*/
def makeVoluntaryRelease(
op_code: UInt,
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0)): Release =
Release(
voluntary = Bool(true),
r_type = co.getReleaseType(op_code, this),
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
data = data)(p)
/** Constructs a Release message based on this metadata on an eviction
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (which beat)
* @param data data being written back
*/
def makeVoluntaryWriteback(
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0)): Release =
makeVoluntaryRelease(
op_code = M_FLUSH,
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
data = data)
/** Constructs a Release message based on this metadata and a [[uncore.Probe]]
*
* @param the incoming [[uncore.Probe]]
* @param addr_beat sub-block address (which beat)
* @param data data being released
*/
def makeRelease(
prb: Probe,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0)): Release =
Release(
voluntary = Bool(false),
r_type = co.getReleaseType(prb, this),
client_xact_id = UInt(0),
addr_block = prb.addr_block,
addr_beat = addr_beat,
data = data)(p)
/** New metadata after receiving a [[uncore.Grant]]
*
* @param incoming the incoming [[uncore.Grant]]
* @param pending the mem op that triggered this transaction
*/
def onGrant(incoming: Grant, pending: UInt): ClientMetadata =
co.clientMetadataOnGrant(incoming, pending, this)
/** New metadata after receiving a [[uncore.Probe]]
*
* @param incoming the incoming [[uncore.Probe]]
*/
def onProbe(incoming: Probe): ClientMetadata =
co.clientMetadataOnProbe(incoming, this)
/** New metadata after a op_code hits this block
*
* @param op_code a memory operation from [[uncore.constants.MemoryOpConstants]]
*/
def onHit(op_code: UInt): ClientMetadata =
co.clientMetadataOnHit(op_code, this)
/** New metadata after op_code releases permissions on this block
*
* @param op_code a memory operation from [[uncore.constants.MemoryOpConstants]]
*/
def onCacheControl(op_code: UInt): ClientMetadata =
co.clientMetadataOnCacheControl(op_code, this)
}
/** Factories for ClientMetadata, including on reset */
object ClientMetadata {
def apply(state: UInt)(implicit p: Parameters) = {
val meta = Wire(new ClientMetadata)
meta.state := state
meta
}
def onReset(implicit p: Parameters) = ClientMetadata(UInt(0))(p) // TODO: assumes clientInvalid === 0
}
/** Stores manager-side information about the status
* of a cache block, including whether it has any known sharers.
*
* Its API can be used to create [[uncore.Probe]] and [[uncore.Grant]] messages.
*/
class ManagerMetadata(implicit p: Parameters) extends CoherenceMetadata()(p) {
// Currently no coherence policies assume manager-side state information
// val state = UInt(width = co.masterStateWidth) TODO: Fix 0-width wires in Chisel
/** The directory information for this block */
val sharers = UInt(width = co.dir.width)
/** Metadata equality */
def ===(rhs: ManagerMetadata): Bool = //this.state === rhs.state && TODO: Fix 0-width wires in Chisel
this.sharers === rhs.sharers
def =/=(rhs: ManagerMetadata): Bool = !this.===(rhs)
/** Converts the directory info into an N-hot sharer bitvector (i.e. full representation) */
def full(dummy: Int = 0): UInt = co.dir.full(this.sharers)
/** Does this [[uncore.Acquire]] require [[uncore.Probe Probes]] to be sent */
def requiresProbes(acq: HasAcquireType): Bool = co.requiresProbes(acq, this)
/** Does this memory op require [[uncore.Probe Probes]] to be sent */
def requiresProbes(op_code: UInt): Bool = co.requiresProbes(op_code, this)
/** Does an eviction require [[uncore.Probe Probes]] to be sent */
def requiresProbesOnVoluntaryWriteback(dummy: Int = 0): Bool =
co.requiresProbes(M_FLUSH, this)
/** Construct an appropriate [[uncore.ProbeToDst]] for a given [[uncore.Acquire]]
*
* @param dst Destination client id for this Probe
* @param acq Acquire message triggering this Probe
* @param addr_block address of the cache block being probed
*/
def makeProbe(dst: UInt, acq: HasAcquireType, addr_block: UInt): ProbeToDst =
Probe(dst, co.getProbeType(acq, this), addr_block)(p)
/** Construct an appropriate [[uncore.ProbeToDst]] for a given [[uncore.Acquire]]
*
* @param dst Destination client id for this Probe
* @param acq Acquire message triggering this Probe
*/
def makeProbe(dst: UInt, acq: AcquireMetadata): ProbeToDst =
Probe(dst, co.getProbeType(acq, this), acq.addr_block)(p)
/** Construct an appropriate [[uncore.ProbeToDst]] for a given mem op
*
* @param dst Destination client id for this Probe
* @param op_code memory operation triggering this Probe
* @param addr_block address of the cache block being probed
*/
def makeProbe(dst: UInt, op_code: UInt, addr_block: UInt): ProbeToDst =
Probe(dst, co.getProbeType(op_code, this), addr_block)(p)
/** Construct an appropriate [[uncore.ProbeToDst]] for an eviction
*
* @param dst Destination client id for this Probe
* @param addr_block address of the cache block being probed prior to eviction
*/
def makeProbeForVoluntaryWriteback(dst: UInt, addr_block: UInt): ProbeToDst =
makeProbe(dst, M_FLUSH, addr_block)
/** Construct an appropriate [[uncore.GrantToDst]] to acknowledge an [[uncore.Release]]
*
* @param rel Release message being acknowledged by this Grant
*/
def makeGrant(rel: ReleaseMetadata with HasClientId): GrantToDst =
Grant(
dst = rel.client_id,
is_builtin_type = Bool(true),
g_type = Grant.voluntaryAckType,
client_xact_id = rel.client_xact_id,
manager_xact_id = UInt(0))(p)
/** Construct an appropriate [[uncore.GrantToDst]] to respond to an [[uncore.Acquire]]
*
* May contain single or multiple beats of data, or just be a permissions upgrade.
*
* @param acq Acquire message being responded to by this Grant
* @param manager_xact_id manager's transaction id
* @param addr_beat beat id of the data
* @param data data being refilled to the original requestor
*/
def makeGrant(
acq: AcquireMetadata with HasClientId,
manager_xact_id: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0)): GrantToDst =
Grant(
dst = acq.client_id,
is_builtin_type = acq.isBuiltInType(),
g_type = co.getGrantType(acq, this),
client_xact_id = acq.client_xact_id,
manager_xact_id = manager_xact_id,
addr_beat = addr_beat,
data = data)(p)
/** Construct an [[uncore.GrantToDst]] to respond to an [[uncore.Acquire]] with some overrides
*
* Used to respond to secondary misses merged into this transaction.
* May contain single or multiple beats of data.
*
* @param sec Secondary miss info
* @param manager_xact_id manager's transaction id
* @param data data being refilled to the original requestor
*/
def makeGrant(
sec: SecondaryMissInfo,
manager_xact_id: UInt,
data: UInt): GrantToDst = {
Grant(
dst = sec.client_id,
is_builtin_type = sec.isBuiltInType(),
g_type = co.getGrantType(sec, this),
client_xact_id = sec.client_xact_id,
manager_xact_id = manager_xact_id,
addr_beat = sec.addr_beat,
data = data)(p)
}
/** New metadata after receiving a [[uncore.ReleaseFromSrc]]
*
* @param incoming the incoming [[uncore.ReleaseFromSrc]]
*/
def onRelease(incoming: ReleaseMetadata with HasClientId): ManagerMetadata =
co.managerMetadataOnRelease(incoming, incoming.client_id, this)
/** New metadata after sending a [[uncore.GrantToDst]]
*
* @param outgoing the outgoing [[uncore.GrantToDst]]
*/
def onGrant(outgoing: GrantMetadata with HasClientId): ManagerMetadata =
co.managerMetadataOnGrant(outgoing, outgoing.client_id, this)
}
/** Factories for ManagerMetadata, including on reset */
object ManagerMetadata {
def apply(sharers: UInt, state: UInt = UInt(width = 0))(implicit p: Parameters) = {
val meta = Wire(new ManagerMetadata)
//meta.state := state TODO: Fix 0-width wires in Chisel
meta.sharers := sharers
meta
}
def apply(implicit p: Parameters) = {
val meta = Wire(new ManagerMetadata)
//meta.state := UInt(width = 0) TODO: Fix 0-width wires in Chisel
meta.sharers := meta.co.dir.flush
meta
}
def onReset(implicit p: Parameters) = ManagerMetadata(p)
}
/** HierarchicalMetadata is used in a cache in a multi-level memory hierarchy
* that is a manager with respect to some inner caches and a client with
* respect to some outer cache.
*
* This class makes use of two different sets of TileLink parameters, which are
* applied by contextually mapping [[uncore.TLId]] to one of
* [[uncore.InnerTLId]] or [[uncore.OuterTLId]].
*/
class HierarchicalMetadata(implicit p: Parameters) extends CoherenceMetadata()(p) {
val inner: ManagerMetadata = new ManagerMetadata()(p.alterPartial({case TLId => p(InnerTLId)}))
val outer: ClientMetadata = new ClientMetadata()(p.alterPartial({case TLId => p(OuterTLId)}))
def ===(rhs: HierarchicalMetadata): Bool =
this.inner === rhs.inner && this.outer === rhs.outer
def =/=(rhs: HierarchicalMetadata): Bool = !this.===(rhs)
}
/** Factories for HierarchicalMetadata, including on reset */
object HierarchicalMetadata {
def apply(inner: ManagerMetadata, outer: ClientMetadata)
(implicit p: Parameters): HierarchicalMetadata = {
val m = Wire(new HierarchicalMetadata)
m.inner := inner
m.outer := outer
m
}
def onReset(implicit p: Parameters): HierarchicalMetadata =
apply(ManagerMetadata.onReset, ClientMetadata.onReset)
}

View File

@ -0,0 +1,696 @@
// See LICENSE for license details.
package uncore.coherence
import Chisel._
import uncore.tilelink._
import uncore.constants._
import uncore.util._
/** The entire CoherencePolicy API consists of the following three traits:
* HasCustomTileLinkMessageTypes, used to define custom messages
* HasClientSideCoherencePolicy, for client coherence agents
* HasManagerSideCoherencePolicy, for manager coherence agents
*/
abstract class CoherencePolicy(val dir: DirectoryRepresentation)
extends HasCustomTileLinkMessageTypes
with HasClientSideCoherencePolicy
with HasManagerSideCoherencePolicy
/** This API defines the custom, coherence-policy-defined message types,
* as opposed to the built-in ones found in tilelink.scala.
* Policies must enumerate the custom messages to be sent over each
* channel, as well as which of them have associated data.
*/
trait HasCustomTileLinkMessageTypes {
val nAcquireTypes: Int
def acquireTypeWidth = log2Up(nAcquireTypes)
val nProbeTypes: Int
def probeTypeWidth = log2Up(nProbeTypes)
val nReleaseTypes: Int
def releaseTypeWidth = log2Up(nReleaseTypes)
val nGrantTypes: Int
def grantTypeWidth = log2Up(nGrantTypes)
val acquireTypesWithData = Nil // Only built-in Acquire types have data for now
def releaseTypesWithData: Seq[UInt]
def grantTypesWithData: Seq[UInt]
}
/** This API contains all functions required for client coherence agents.
* Policies must enumerate the number of client states and define their
* permissions with respect to memory operations. Policies must fill in functions
* to control which messages are sent and how metadata is updated in response
* to coherence events. These funtions are generally called from within the
* ClientMetadata class in metadata.scala
*/
trait HasClientSideCoherencePolicy {
// Client coherence states and their permissions
val nClientStates: Int
def clientStateWidth = log2Ceil(nClientStates)
def clientStatesWithReadPermission: Seq[UInt]
def clientStatesWithWritePermission: Seq[UInt]
def clientStatesWithDirtyData: Seq[UInt]
// Transaction initiation logic
def isValid(meta: ClientMetadata): Bool
def isHit(cmd: UInt, meta: ClientMetadata): Bool = {
Mux(isWriteIntent(cmd),
meta.state isOneOf clientStatesWithWritePermission,
meta.state isOneOf clientStatesWithReadPermission)
}
//TODO: Assumes all states with write permissions also have read permissions
def requiresAcquireOnSecondaryMiss(
first_cmd: UInt,
second_cmd: UInt,
meta: ClientMetadata): Bool = {
isWriteIntent(second_cmd) && !isWriteIntent(first_cmd)
}
//TODO: Assumes all cache ctrl ops writeback dirty data, and
// doesn't issue transaction when e.g. downgrading Exclusive to Shared:
def requiresReleaseOnCacheControl(cmd: UInt, meta: ClientMetadata): Bool =
meta.state isOneOf clientStatesWithDirtyData
// Determine which custom message type to use
def getAcquireType(cmd: UInt, meta: ClientMetadata): UInt
def getReleaseType(cmd: UInt, meta: ClientMetadata): UInt
def getReleaseType(p: HasProbeType, meta: ClientMetadata): UInt
// Mutate ClientMetadata based on messages or cmds
def clientMetadataOnHit(cmd: UInt, meta: ClientMetadata): ClientMetadata
def clientMetadataOnCacheControl(cmd: UInt, meta: ClientMetadata): ClientMetadata
def clientMetadataOnGrant(incoming: HasGrantType, cmd: UInt, meta: ClientMetadata): ClientMetadata
def clientMetadataOnProbe(incoming: HasProbeType, meta: ClientMetadata): ClientMetadata
}
/** This API contains all functions required for manager coherence agents.
* Policies must enumerate the number of manager states. Policies must fill
* in functions to control which Probe and Grant messages are sent and how
* metadata should be updated in response to coherence events. These funtions
* are generally called from within the ManagerMetadata class in metadata.scala
*/
trait HasManagerSideCoherencePolicy extends HasDirectoryRepresentation {
val nManagerStates: Int
def masterStateWidth = log2Ceil(nManagerStates)
// Transaction probing logic
def requiresProbes(acq: HasAcquireType, meta: ManagerMetadata): Bool
def requiresProbes(cmd: UInt, meta: ManagerMetadata): Bool
// Determine which custom message type to use in response
def getProbeType(cmd: UInt, meta: ManagerMetadata): UInt
def getProbeType(acq: HasAcquireType, meta: ManagerMetadata): UInt
def getGrantType(acq: HasAcquireType, meta: ManagerMetadata): UInt
def getExclusiveGrantType(): UInt
// Mutate ManagerMetadata based on messages or cmds
def managerMetadataOnRelease(incoming: HasReleaseType, src: UInt, meta: ManagerMetadata): ManagerMetadata
def managerMetadataOnGrant(outgoing: HasGrantType, dst: UInt, meta: ManagerMetadata) =
ManagerMetadata(sharers=Mux(outgoing.isBuiltInType(), // Assumes all built-ins are uncached
meta.sharers,
dir.push(meta.sharers, dst)))(meta.p)
//state = meta.state) TODO: Fix 0-width wires in Chisel
}
/** The following concrete implementations of CoherencePolicy each provide the
* functionality of one particular protocol.
*/
/** A simple protocol with only two Client states.
* Data is always assumed to be dirty.
* Only a single client may ever have a copy of a block at a time.
*/
class MICoherence(dir: DirectoryRepresentation) extends CoherencePolicy(dir) {
// Message types
val nAcquireTypes = 1
val nProbeTypes = 2
val nReleaseTypes = 4
val nGrantTypes = 1
val acquireExclusive :: Nil = Enum(UInt(), nAcquireTypes)
val probeInvalidate :: probeCopy :: Nil = Enum(UInt(), nProbeTypes)
val releaseInvalidateData :: releaseCopyData :: releaseInvalidateAck :: releaseCopyAck :: Nil = Enum(UInt(), nReleaseTypes)
val grantExclusive :: Nil = Enum(UInt(), nGrantTypes)
def releaseTypesWithData = Seq(releaseInvalidateData, releaseCopyData)
def grantTypesWithData = Seq(grantExclusive)
// Client states and functions
val nClientStates = 2
val clientInvalid :: clientValid :: Nil = Enum(UInt(), nClientStates)
def clientStatesWithReadPermission = Seq(clientValid)
def clientStatesWithWritePermission = Seq(clientValid)
def clientStatesWithDirtyData = Seq(clientValid)
def isValid (meta: ClientMetadata): Bool = meta.state =/= clientInvalid
def getAcquireType(cmd: UInt, meta: ClientMetadata): UInt = acquireExclusive
def getReleaseType(cmd: UInt, meta: ClientMetadata): UInt = {
val dirty = meta.state isOneOf clientStatesWithDirtyData
MuxLookup(cmd, releaseCopyAck, Array(
M_FLUSH -> Mux(dirty, releaseInvalidateData, releaseInvalidateAck),
M_PRODUCE -> Mux(dirty, releaseCopyData, releaseCopyAck),
M_CLEAN -> Mux(dirty, releaseCopyData, releaseCopyAck)))
}
def getReleaseType(incoming: HasProbeType, meta: ClientMetadata): UInt =
MuxLookup(incoming.p_type, releaseInvalidateAck, Array(
probeInvalidate -> getReleaseType(M_FLUSH, meta),
probeCopy -> getReleaseType(M_FLUSH, meta)))
def clientMetadataOnHit(cmd: UInt, meta: ClientMetadata) = meta
def clientMetadataOnCacheControl(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(Mux(cmd === M_FLUSH, clientInvalid, meta.state))(meta.p)
def clientMetadataOnGrant(incoming: HasGrantType, cmd: UInt, meta: ClientMetadata) =
ClientMetadata(Mux(incoming.isBuiltInType(), clientInvalid, clientValid))(meta.p)
def clientMetadataOnProbe(incoming: HasProbeType, meta: ClientMetadata) =
ClientMetadata(Mux(incoming.p_type === probeInvalidate,
clientInvalid, meta.state))(meta.p)
// Manager states and functions:
val nManagerStates = 0 // We don't actually need any states for this protocol
def requiresProbes(a: HasAcquireType, meta: ManagerMetadata) = !dir.none(meta.sharers)
def requiresProbes(cmd: UInt, meta: ManagerMetadata) = !dir.none(meta.sharers)
def getProbeType(cmd: UInt, meta: ManagerMetadata): UInt =
MuxLookup(cmd, probeCopy, Array(
M_FLUSH -> probeInvalidate))
def getProbeType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(),
MuxLookup(a.a_type, probeCopy, Array(
Acquire.getBlockType -> probeCopy,
Acquire.putBlockType -> probeInvalidate,
Acquire.getType -> probeCopy,
Acquire.putType -> probeInvalidate,
Acquire.getPrefetchType -> probeCopy,
Acquire.putPrefetchType -> probeInvalidate,
Acquire.putAtomicType -> probeInvalidate)),
probeInvalidate)
def getGrantType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(), Acquire.getBuiltInGrantType(a.a_type), grantExclusive)
def getExclusiveGrantType(): UInt = grantExclusive
def managerMetadataOnRelease(incoming: HasReleaseType, src: UInt, meta: ManagerMetadata) = {
val popped = ManagerMetadata(sharers=dir.pop(meta.sharers, src))(meta.p)
MuxCase(meta, Array(
incoming.is(releaseInvalidateData) -> popped,
incoming.is(releaseInvalidateAck) -> popped))
}
}
/** A simple protocol with only three Client states.
* Data is marked as dirty when written.
* Only a single client may ever have a copy of a block at a time.
*/
class MEICoherence(dir: DirectoryRepresentation) extends CoherencePolicy(dir) {
// Message types
val nAcquireTypes = 2
val nProbeTypes = 3
val nReleaseTypes = 6
val nGrantTypes = 1
val acquireExclusiveClean :: acquireExclusiveDirty :: Nil = Enum(UInt(), nAcquireTypes)
val probeInvalidate :: probeDowngrade :: probeCopy :: Nil = Enum(UInt(), nProbeTypes)
val releaseInvalidateData :: releaseDowngradeData :: releaseCopyData :: releaseInvalidateAck :: releaseDowngradeAck :: releaseCopyAck :: Nil = Enum(UInt(), nReleaseTypes)
val grantExclusive :: Nil = Enum(UInt(), nGrantTypes)
def releaseTypesWithData = Seq(releaseInvalidateData, releaseDowngradeData, releaseCopyData)
def grantTypesWithData = Seq(grantExclusive)
// Client states and functions
val nClientStates = 3
val clientInvalid :: clientExclusiveClean :: clientExclusiveDirty :: Nil = Enum(UInt(), nClientStates)
def clientStatesWithReadPermission = Seq(clientExclusiveClean, clientExclusiveDirty)
def clientStatesWithWritePermission = Seq(clientExclusiveClean, clientExclusiveDirty)
def clientStatesWithDirtyData = Seq(clientExclusiveDirty)
def isValid (meta: ClientMetadata) = meta.state =/= clientInvalid
def getAcquireType(cmd: UInt, meta: ClientMetadata): UInt =
Mux(isWriteIntent(cmd), acquireExclusiveDirty, acquireExclusiveClean)
def getReleaseType(cmd: UInt, meta: ClientMetadata): UInt = {
val dirty = meta.state isOneOf clientStatesWithDirtyData
MuxLookup(cmd, releaseCopyAck, Array(
M_FLUSH -> Mux(dirty, releaseInvalidateData, releaseInvalidateAck),
M_PRODUCE -> Mux(dirty, releaseDowngradeData, releaseDowngradeAck),
M_CLEAN -> Mux(dirty, releaseCopyData, releaseCopyAck)))
}
def getReleaseType(incoming: HasProbeType, meta: ClientMetadata): UInt =
MuxLookup(incoming.p_type, releaseInvalidateAck, Array(
probeInvalidate -> getReleaseType(M_FLUSH, meta),
probeDowngrade -> getReleaseType(M_FLUSH, meta),
probeCopy -> getReleaseType(M_FLUSH, meta)))
def clientMetadataOnHit(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(Mux(isWrite(cmd), clientExclusiveDirty, meta.state))(meta.p)
def clientMetadataOnCacheControl(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(cmd, meta.state, Array(
M_FLUSH -> clientInvalid,
M_CLEAN -> Mux(meta.state === clientExclusiveDirty, clientExclusiveClean, meta.state))))(meta.p)
def clientMetadataOnGrant(incoming: HasGrantType, cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
Mux(incoming.isBuiltInType(), clientInvalid,
Mux(isWrite(cmd), clientExclusiveDirty, clientExclusiveClean)))(meta.p)
def clientMetadataOnProbe(incoming: HasProbeType, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(incoming.p_type, meta.state, Array(
probeInvalidate -> clientInvalid,
probeDowngrade -> clientInvalid,
probeCopy -> clientInvalid)))(meta.p)
// Manager states and functions:
val nManagerStates = 0 // We don't actually need any states for this protocol
def requiresProbes(a: HasAcquireType, meta: ManagerMetadata) = !dir.none(meta.sharers)
def requiresProbes(cmd: UInt, meta: ManagerMetadata) = !dir.none(meta.sharers)
def getProbeType(cmd: UInt, meta: ManagerMetadata): UInt =
MuxLookup(cmd, probeCopy, Array(
M_FLUSH -> probeInvalidate,
M_PRODUCE -> probeDowngrade))
def getProbeType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(),
MuxLookup(a.a_type, probeCopy, Array(
Acquire.getBlockType -> probeCopy,
Acquire.putBlockType -> probeInvalidate,
Acquire.getType -> probeCopy,
Acquire.putType -> probeInvalidate,
Acquire.getPrefetchType -> probeCopy,
Acquire.putPrefetchType -> probeInvalidate,
Acquire.putAtomicType -> probeInvalidate)),
probeInvalidate)
def getGrantType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(), Acquire.getBuiltInGrantType(a.a_type), grantExclusive)
def getExclusiveGrantType(): UInt = grantExclusive
def managerMetadataOnRelease(incoming: HasReleaseType, src: UInt, meta: ManagerMetadata) = {
val popped = ManagerMetadata(sharers=dir.pop(meta.sharers, src))(meta.p)
MuxCase(meta, Array(
incoming.is(releaseInvalidateData) -> popped,
incoming.is(releaseInvalidateAck) -> popped))
}
}
/** A protocol with only three Client states.
* Data is always assumed to be dirty.
* Multiple clients may share read permissions on a block at the same time.
*/
class MSICoherence(dir: DirectoryRepresentation) extends CoherencePolicy(dir) {
// Message types
val nAcquireTypes = 2
val nProbeTypes = 3
val nReleaseTypes = 6
val nGrantTypes = 3
val acquireShared :: acquireExclusive :: Nil = Enum(UInt(), nAcquireTypes)
val probeInvalidate :: probeDowngrade :: probeCopy :: Nil = Enum(UInt(), nProbeTypes)
val releaseInvalidateData :: releaseDowngradeData :: releaseCopyData :: releaseInvalidateAck :: releaseDowngradeAck :: releaseCopyAck :: Nil = Enum(UInt(), nReleaseTypes)
val grantShared :: grantExclusive :: grantExclusiveAck :: Nil = Enum(UInt(), nGrantTypes)
def releaseTypesWithData = Seq(releaseInvalidateData, releaseDowngradeData, releaseCopyData)
def grantTypesWithData = Seq(grantShared, grantExclusive)
// Client states and functions
val nClientStates = 3
val clientInvalid :: clientShared :: clientExclusiveDirty :: Nil = Enum(UInt(), nClientStates)
def clientStatesWithReadPermission = Seq(clientShared, clientExclusiveDirty)
def clientStatesWithWritePermission = Seq(clientExclusiveDirty)
def clientStatesWithDirtyData = Seq(clientExclusiveDirty)
def isValid(meta: ClientMetadata): Bool = meta.state =/= clientInvalid
def getAcquireType(cmd: UInt, meta: ClientMetadata): UInt =
Mux(isWriteIntent(cmd), acquireExclusive, acquireShared)
def getReleaseType(cmd: UInt, meta: ClientMetadata): UInt = {
val dirty = meta.state isOneOf clientStatesWithDirtyData
MuxLookup(cmd, releaseCopyAck, Array(
M_FLUSH -> Mux(dirty, releaseInvalidateData, releaseInvalidateAck),
M_PRODUCE -> Mux(dirty, releaseDowngradeData, releaseDowngradeAck),
M_CLEAN -> Mux(dirty, releaseCopyData, releaseCopyAck)))
}
def getReleaseType(incoming: HasProbeType, meta: ClientMetadata): UInt =
MuxLookup(incoming.p_type, releaseInvalidateAck, Array(
probeInvalidate -> getReleaseType(M_FLUSH, meta),
probeDowngrade -> getReleaseType(M_PRODUCE, meta),
probeCopy -> getReleaseType(M_PRODUCE, meta)))
def clientMetadataOnHit(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(Mux(isWrite(cmd), clientExclusiveDirty, meta.state))(meta.p)
def clientMetadataOnCacheControl(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(cmd, meta.state, Array(
M_FLUSH -> clientInvalid,
M_PRODUCE -> Mux(meta.state isOneOf clientStatesWithWritePermission,
clientShared, meta.state))))(meta.p)
def clientMetadataOnGrant(incoming: HasGrantType, cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
Mux(incoming.isBuiltInType(), clientInvalid,
MuxLookup(incoming.g_type, clientInvalid, Array(
grantShared -> clientShared,
grantExclusive -> clientExclusiveDirty,
grantExclusiveAck -> clientExclusiveDirty))))(meta.p)
def clientMetadataOnProbe(incoming: HasProbeType, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(incoming.p_type, meta.state, Array(
probeInvalidate -> clientInvalid,
probeDowngrade -> clientShared,
probeCopy -> clientShared)))(meta.p)
// Manager states and functions:
val nManagerStates = 0 // TODO: We could add a Shared state to avoid probing
// only a single sharer (also would need
// notification msg to track clean drops)
// Also could avoid probes on outer WBs.
def requiresProbes(a: HasAcquireType, meta: ManagerMetadata) =
Mux(dir.none(meta.sharers), Bool(false),
Mux(dir.one(meta.sharers), Bool(true), //TODO: for now we assume it's Exclusive
Mux(a.isBuiltInType(), a.hasData(), a.a_type =/= acquireShared)))
def requiresProbes(cmd: UInt, meta: ManagerMetadata) = !dir.none(meta.sharers)
def getProbeType(cmd: UInt, meta: ManagerMetadata): UInt =
MuxLookup(cmd, probeCopy, Array(
M_FLUSH -> probeInvalidate,
M_PRODUCE -> probeDowngrade))
def getProbeType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(),
MuxLookup(a.a_type, probeCopy, Array(
Acquire.getBlockType -> probeCopy,
Acquire.putBlockType -> probeInvalidate,
Acquire.getType -> probeCopy,
Acquire.putType -> probeInvalidate,
Acquire.getPrefetchType -> probeCopy,
Acquire.putPrefetchType -> probeInvalidate,
Acquire.putAtomicType -> probeInvalidate)),
MuxLookup(a.a_type, probeCopy, Array(
acquireShared -> probeDowngrade,
acquireExclusive -> probeInvalidate)))
def getGrantType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(), Acquire.getBuiltInGrantType(a.a_type),
Mux(a.a_type === acquireShared,
Mux(!dir.none(meta.sharers), grantShared, grantExclusive),
grantExclusive))
def getExclusiveGrantType(): UInt = grantExclusive
def managerMetadataOnRelease(incoming: HasReleaseType, src: UInt, meta: ManagerMetadata) = {
val popped = ManagerMetadata(sharers=dir.pop(meta.sharers, src))(meta.p)
MuxCase(meta, Array(
incoming.is(releaseInvalidateData) -> popped,
incoming.is(releaseInvalidateAck) -> popped))
}
}
/** A protocol with four Client states.
* Data is marked as dirty when written.
* Multiple clients may share read permissions on a block at the same time.
*/
class MESICoherence(dir: DirectoryRepresentation) extends CoherencePolicy(dir) {
// Message types
val nAcquireTypes = 2
val nProbeTypes = 3
val nReleaseTypes = 6
val nGrantTypes = 3
val acquireShared :: acquireExclusive :: Nil = Enum(UInt(), nAcquireTypes)
val probeInvalidate :: probeDowngrade :: probeCopy :: Nil = Enum(UInt(), nProbeTypes)
val releaseInvalidateData :: releaseDowngradeData :: releaseCopyData :: releaseInvalidateAck :: releaseDowngradeAck :: releaseCopyAck :: Nil = Enum(UInt(), nReleaseTypes)
val grantShared :: grantExclusive :: grantExclusiveAck :: Nil = Enum(UInt(), nGrantTypes)
def releaseTypesWithData = Seq(releaseInvalidateData, releaseDowngradeData, releaseCopyData)
def grantTypesWithData = Seq(grantShared, grantExclusive)
// Client states and functions
val nClientStates = 4
val clientInvalid :: clientShared :: clientExclusiveClean :: clientExclusiveDirty :: Nil = Enum(UInt(), nClientStates)
def clientStatesWithReadPermission = Seq(clientShared, clientExclusiveClean, clientExclusiveDirty)
def clientStatesWithWritePermission = Seq(clientExclusiveClean, clientExclusiveDirty)
def clientStatesWithDirtyData = Seq(clientExclusiveDirty)
def isValid(meta: ClientMetadata): Bool = meta.state =/= clientInvalid
def getAcquireType(cmd: UInt, meta: ClientMetadata): UInt =
Mux(isWriteIntent(cmd), acquireExclusive, acquireShared)
def getReleaseType(cmd: UInt, meta: ClientMetadata): UInt = {
val dirty = meta.state isOneOf clientStatesWithDirtyData
MuxLookup(cmd, releaseCopyAck, Array(
M_FLUSH -> Mux(dirty, releaseInvalidateData, releaseInvalidateAck),
M_PRODUCE -> Mux(dirty, releaseDowngradeData, releaseDowngradeAck),
M_CLEAN -> Mux(dirty, releaseCopyData, releaseCopyAck)))
}
def getReleaseType(incoming: HasProbeType, meta: ClientMetadata): UInt =
MuxLookup(incoming.p_type, releaseInvalidateAck, Array(
probeInvalidate -> getReleaseType(M_FLUSH, meta),
probeDowngrade -> getReleaseType(M_PRODUCE, meta),
probeCopy -> getReleaseType(M_PRODUCE, meta)))
def clientMetadataOnHit(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(Mux(isWrite(cmd), clientExclusiveDirty, meta.state))(meta.p)
def clientMetadataOnCacheControl(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(cmd, meta.state, Array(
M_FLUSH -> clientInvalid,
M_PRODUCE -> Mux(meta.state isOneOf clientStatesWithWritePermission,
clientShared, meta.state),
M_CLEAN -> Mux(meta.state === clientExclusiveDirty,
clientExclusiveClean, meta.state))))(meta.p)
def clientMetadataOnGrant(incoming: HasGrantType, cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
Mux(incoming.isBuiltInType(), clientInvalid,
MuxLookup(incoming.g_type, clientInvalid, Array(
grantShared -> clientShared,
grantExclusive -> Mux(isWrite(cmd), clientExclusiveDirty, clientExclusiveClean),
grantExclusiveAck -> clientExclusiveDirty))))(meta.p)
def clientMetadataOnProbe(incoming: HasProbeType, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(incoming.p_type, meta.state, Array(
probeInvalidate -> clientInvalid,
probeDowngrade -> clientShared,
probeCopy -> clientShared)))(meta.p)
// Manager states and functions:
val nManagerStates = 0 // TODO: We could add a Shared state to avoid probing
// only a single sharer (also would need
// notification msg to track clean drops)
// Also could avoid probes on outer WBs.
def requiresProbes(a: HasAcquireType, meta: ManagerMetadata) =
Mux(dir.none(meta.sharers), Bool(false),
Mux(dir.one(meta.sharers), Bool(true), //TODO: for now we assume it's Exclusive
Mux(a.isBuiltInType(), a.hasData(), a.a_type =/= acquireShared)))
def requiresProbes(cmd: UInt, meta: ManagerMetadata) = !dir.none(meta.sharers)
def getProbeType(cmd: UInt, meta: ManagerMetadata): UInt =
MuxLookup(cmd, probeCopy, Array(
M_FLUSH -> probeInvalidate,
M_PRODUCE -> probeDowngrade))
def getProbeType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(),
MuxLookup(a.a_type, probeCopy, Array(
Acquire.getBlockType -> probeCopy,
Acquire.putBlockType -> probeInvalidate,
Acquire.getType -> probeCopy,
Acquire.putType -> probeInvalidate,
Acquire.getPrefetchType -> probeCopy,
Acquire.putPrefetchType -> probeInvalidate,
Acquire.putAtomicType -> probeInvalidate)),
MuxLookup(a.a_type, probeCopy, Array(
acquireShared -> probeDowngrade,
acquireExclusive -> probeInvalidate)))
def getGrantType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(), Acquire.getBuiltInGrantType(a.a_type),
Mux(a.a_type === acquireShared,
Mux(!dir.none(meta.sharers), grantShared, grantExclusive),
grantExclusive))
def getExclusiveGrantType(): UInt = grantExclusive
def managerMetadataOnRelease(incoming: HasReleaseType, src: UInt, meta: ManagerMetadata) = {
val popped = ManagerMetadata(sharers=dir.pop(meta.sharers, src))(meta.p)
MuxCase(meta, Array(
incoming.is(releaseInvalidateData) -> popped,
incoming.is(releaseInvalidateAck) -> popped))
}
}
class MigratoryCoherence(dir: DirectoryRepresentation) extends CoherencePolicy(dir) {
// Message types
val nAcquireTypes = 3
val nProbeTypes = 4
val nReleaseTypes = 10
val nGrantTypes = 4
val acquireShared :: acquireExclusive :: acquireInvalidateOthers :: Nil = Enum(UInt(), nAcquireTypes)
val probeInvalidate :: probeDowngrade :: probeCopy :: probeInvalidateOthers :: Nil = Enum(UInt(), nProbeTypes)
val releaseInvalidateData :: releaseDowngradeData :: releaseCopyData :: releaseInvalidateAck :: releaseDowngradeAck :: releaseCopyAck :: releaseDowngradeDataMigratory :: releaseDowngradeAckHasCopy :: releaseInvalidateDataMigratory :: releaseInvalidateAckMigratory :: Nil = Enum(UInt(), nReleaseTypes)
val grantShared :: grantExclusive :: grantExclusiveAck :: grantReadMigratory :: Nil = Enum(UInt(), nGrantTypes)
def releaseTypesWithData = Seq(releaseInvalidateData, releaseDowngradeData, releaseCopyData, releaseInvalidateDataMigratory, releaseDowngradeDataMigratory)
def grantTypesWithData = Seq(grantShared, grantExclusive, grantReadMigratory)
// Client states and functions
val nClientStates = 7
val clientInvalid :: clientShared :: clientExclusiveClean :: clientExclusiveDirty :: clientSharedByTwo :: clientMigratoryClean :: clientMigratoryDirty :: Nil = Enum(UInt(), nClientStates)
def clientStatesWithReadPermission = Seq(clientShared, clientExclusiveClean, clientExclusiveDirty, clientSharedByTwo, clientMigratoryClean, clientMigratoryDirty)
def clientStatesWithWritePermission = Seq(clientExclusiveClean, clientExclusiveDirty, clientMigratoryClean, clientMigratoryDirty)
def clientStatesWithDirtyData = Seq(clientExclusiveDirty, clientMigratoryDirty)
def isValid (meta: ClientMetadata): Bool = meta.state =/= clientInvalid
def getAcquireType(cmd: UInt, meta: ClientMetadata): UInt =
Mux(isWriteIntent(cmd),
Mux(meta.state === clientInvalid, acquireExclusive, acquireInvalidateOthers),
acquireShared)
def getReleaseType(cmd: UInt, meta: ClientMetadata): UInt = {
val dirty = meta.state isOneOf clientStatesWithDirtyData
MuxLookup(cmd, releaseCopyAck, Array(
M_FLUSH -> Mux(dirty, releaseInvalidateData, releaseInvalidateAck),
M_PRODUCE -> Mux(dirty, releaseDowngradeData, releaseDowngradeAck),
M_CLEAN -> Mux(dirty, releaseCopyData, releaseCopyAck)))
}
def getReleaseType(incoming: HasProbeType, meta: ClientMetadata): UInt = {
val dirty = meta.state isOneOf clientStatesWithDirtyData
val with_data = MuxLookup(incoming.p_type, releaseInvalidateData, Array(
probeInvalidate -> Mux(meta.state isOneOf (clientExclusiveDirty, clientMigratoryDirty),
releaseInvalidateDataMigratory, releaseInvalidateData),
probeDowngrade -> Mux(meta.state === clientMigratoryDirty,
releaseDowngradeDataMigratory, releaseDowngradeData),
probeCopy -> releaseCopyData))
val without_data = MuxLookup(incoming.p_type, releaseInvalidateAck, Array(
probeInvalidate -> Mux(clientExclusiveClean === meta.state,
releaseInvalidateAckMigratory, releaseInvalidateAck),
probeInvalidateOthers -> Mux(clientSharedByTwo === meta.state,
releaseInvalidateAckMigratory, releaseInvalidateAck),
probeDowngrade -> Mux(meta.state =/= clientInvalid,
releaseDowngradeAckHasCopy, releaseDowngradeAck),
probeCopy -> Mux(meta.state =/= clientInvalid,
releaseDowngradeAckHasCopy, releaseDowngradeAck)))
Mux(dirty, with_data, without_data)
}
def clientMetadataOnHit(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
Mux(isWrite(cmd), MuxLookup(meta.state, clientExclusiveDirty, Array(
clientExclusiveClean -> clientExclusiveDirty,
clientMigratoryClean -> clientMigratoryDirty)),
meta.state))(meta.p)
def clientMetadataOnCacheControl(cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
MuxLookup(cmd, meta.state, Array(
M_FLUSH -> clientInvalid,
M_PRODUCE -> Mux(meta.state isOneOf clientStatesWithWritePermission,
clientShared, meta.state),
M_CLEAN -> MuxLookup(meta.state, meta.state, Array(
clientExclusiveDirty -> clientExclusiveClean,
clientMigratoryDirty -> clientMigratoryClean)))))(meta.p)
def clientMetadataOnGrant(incoming: HasGrantType, cmd: UInt, meta: ClientMetadata) =
ClientMetadata(
Mux(incoming.isBuiltInType(), clientInvalid,
MuxLookup(incoming.g_type, clientInvalid, Array(
grantShared -> clientShared,
grantExclusive -> Mux(isWrite(cmd), clientExclusiveDirty, clientExclusiveClean),
grantExclusiveAck -> clientExclusiveDirty,
grantReadMigratory -> Mux(isWrite(cmd),
clientMigratoryDirty, clientMigratoryClean)))))(meta.p)
def clientMetadataOnProbe(incoming: HasProbeType, meta: ClientMetadata) = {
val downgradeState = MuxLookup(meta.state, clientShared, Array(
clientExclusiveClean -> clientSharedByTwo,
clientExclusiveDirty -> clientSharedByTwo,
clientSharedByTwo -> clientShared,
clientMigratoryClean -> clientSharedByTwo,
clientMigratoryDirty -> clientInvalid))
ClientMetadata(
MuxLookup(incoming.p_type, meta.state, Array(
probeInvalidate -> clientInvalid,
probeInvalidateOthers -> clientInvalid,
probeDowngrade -> downgradeState,
probeCopy -> downgradeState)))(meta.p)
}
// Manager states and functions:
val nManagerStates = 0 // TODO: we could add some states to reduce the number of message types
def requiresProbes(a: HasAcquireType, meta: ManagerMetadata) =
Mux(dir.none(meta.sharers), Bool(false),
Mux(dir.one(meta.sharers), Bool(true), //TODO: for now we assume it's Exclusive
Mux(a.isBuiltInType(), a.hasData(), a.a_type =/= acquireShared)))
def requiresProbes(cmd: UInt, meta: ManagerMetadata) = !dir.none(meta.sharers)
def getProbeType(cmd: UInt, meta: ManagerMetadata): UInt =
MuxLookup(cmd, probeCopy, Array(
M_FLUSH -> probeInvalidate,
M_PRODUCE -> probeDowngrade))
def getProbeType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(),
MuxLookup(a.a_type, probeCopy, Array(
Acquire.getBlockType -> probeCopy,
Acquire.putBlockType -> probeInvalidate,
Acquire.getType -> probeCopy,
Acquire.putType -> probeInvalidate,
Acquire.getPrefetchType -> probeCopy,
Acquire.putPrefetchType -> probeInvalidate,
Acquire.putAtomicType -> probeInvalidate)),
MuxLookup(a.a_type, probeCopy, Array(
acquireShared -> probeDowngrade,
acquireExclusive -> probeInvalidate,
acquireInvalidateOthers -> probeInvalidateOthers)))
def getGrantType(a: HasAcquireType, meta: ManagerMetadata): UInt =
Mux(a.isBuiltInType(), Acquire.getBuiltInGrantType(a.a_type),
MuxLookup(a.a_type, grantShared, Array(
acquireShared -> Mux(!dir.none(meta.sharers), grantShared, grantExclusive),
acquireExclusive -> grantExclusive,
acquireInvalidateOthers -> grantExclusiveAck))) //TODO: add this to MESI for broadcast?
def getExclusiveGrantType(): UInt = grantExclusive
def managerMetadataOnRelease(incoming: HasReleaseType, src: UInt, meta: ManagerMetadata) = {
val popped = ManagerMetadata(sharers=dir.pop(meta.sharers, src))(meta.p)
MuxCase(meta, Array(
incoming.is(releaseInvalidateData) -> popped,
incoming.is(releaseInvalidateAck) -> popped,
incoming.is(releaseInvalidateDataMigratory) -> popped,
incoming.is(releaseInvalidateAckMigratory) -> popped))
}
}

View File

@ -0,0 +1,424 @@
package uncore.converters
import Chisel._
import junctions._
import uncore.tilelink._
import uncore.util._
import uncore.constants._
import cde.{Parameters, Field}
import HastiConstants._
/* We need to translate TileLink requests into operations we can actually execute on AHB.
* The general plan of attack is:
* get => one AHB=>TL read
* put => [multiple AHB write fragments=>nill], one AHB write=>TL
* getBlock => AHB burst reads =>TL
* putBlock => AHB burst writes=>TL
* getPrefetch => noop=>TL
* putPrefetch => noop=>TL
* putAtomic => one AHB=>TL read, one idle, one AHB atom_write=>nill, one idle
*
* This requires that we support a pipeline of optional AHB requests with optional TL responses
*/
class AHBRequestIO(implicit p: Parameters) extends HastiMasterIO
with HasGrantType
with HasClientTransactionId
with HasTileLinkBeatId {
val executeAHB = Bool()
val respondTL = Bool()
val latchAtom = Bool()
val firstBurst = Bool()
val finalBurst = Bool()
val cmd = Bits(width = M_SZ) // atomic op
}
// AHB stage1: translate TileLink Acquires into AHBRequests
class AHBTileLinkIn(supportAtomics: Boolean = false)(implicit val p: Parameters) extends Module
with HasHastiParameters
with HasTileLinkParameters
with HasAddrMapParameters {
val io = new Bundle {
val acquire = new DecoupledIO(new Acquire).flip // NOTE: acquire must be either a Queue or a Pipe
val request = new DecoupledIO(new AHBRequestIO)
}
// Match the AHB burst with a TileLink {Put,Get}Block
val burstSize = tlDataBeats match {
case 1 => HBURST_SINGLE
// case 2 not supported by AHB
case 4 => HBURST_WRAP4
case 8 => HBURST_WRAP8
case 16 => HBURST_WRAP16
case _ => throw new java.lang.AssertionError("TileLink beats unsupported by AHB")
}
// Bursts start at 0 and wrap-around back to 0
val finalBurst = UInt(tlDataBeats-1, width = log2Up(tlDataBeats)).asUInt
val firstBurst = UInt(0, width = log2Up(tlDataBeats))
val next_wmask = Wire(UInt(width = tlDataBytes)) // calculated below
// State variables for processing more complicated TileLink Acquires
val s_atom_r :: s_atom_idle1 :: s_atom_w :: s_atom_idle2 :: Nil = Enum(UInt(), 4)
val atom_state = Reg(init = s_atom_r) // never changes if !supportAtomics
val done_wmask = Reg(init = UInt(0, width = tlDataBytes))
val burst = Reg(init = firstBurst)
// Grab some view of the TileLink acquire
val acq_wmask = io.acquire.bits.wmask()
val isReadBurst = io.acquire.bits.is(Acquire.getBlockType)
val isWriteBurst = io.acquire.bits.is(Acquire.putBlockType)
val isBurst = isWriteBurst || isReadBurst
val isAtomic = io.acquire.bits.is(Acquire.putAtomicType) && Bool(supportAtomics)
val isPut = io.acquire.bits.is(Acquire.putType)
// Final states?
val last_wmask = next_wmask === acq_wmask
val last_atom = atom_state === s_atom_idle2
val last_burst = burst === finalBurst
// Block the incoming request until we've fully consumed it
// NOTE: the outgoing grant.valid may happen while acquire.ready is still false;
// for this reason it is essential to have a Queue or a Pipe infront of acquire
io.acquire.ready := io.request.ready && MuxLookup(io.acquire.bits.a_type, Bool(true), Array(
Acquire.getType -> Bool(true),
Acquire.getBlockType -> last_burst, // hold it until the last beat is burst
Acquire.putType -> last_wmask, // only accept the put if we can fully consume its wmask
Acquire.putBlockType -> Bool(true),
Acquire.putAtomicType -> last_atom, // atomic operation stages complete
Acquire.getPrefetchType -> Bool(true),
Acquire.putPrefetchType -> Bool(true)))
// Advance the fragment state
when (io.request.ready && io.acquire.valid && isPut) {
when (last_wmask) { // if this was the last fragment, restart FSM
done_wmask := UInt(0)
} .otherwise {
done_wmask := next_wmask
}
}
// Advance the burst state
// We assume here that TileLink gives us all putBlock beats with nothing between them
when (io.request.ready && io.acquire.valid && isBurst) {
when (last_burst) {
burst := UInt(0)
} .otherwise {
burst := burst + UInt(1)
}
}
// Advance the atomic state machine
when (io.request.ready && io.acquire.valid && isAtomic) {
switch (atom_state) {
is (s_atom_r) { atom_state := s_atom_idle1 }
is (s_atom_idle1) { atom_state := s_atom_w } // idle1 => AMOALU runs on a different clock than AHB slave read
is (s_atom_w) { atom_state := s_atom_idle2 }
is (s_atom_idle2) { atom_state := s_atom_r } // idle2 state is required by AHB after hmastlock is lowered
}
}
// Returns (range=0, range=-1, aligned_wmask, size)
def mask_helper(in_0 : Bool, range : UInt): (Bool, Bool, UInt, UInt) = {
val len = range.getWidth
if (len == 1) {
(range === UInt(0), range === UInt(1), in_0.asUInt() & range, UInt(0))
} else {
val mid = len / 2
val lo = range(mid-1, 0)
val hi = range(len-1, mid)
val (lo_0, lo_1, lo_m, lo_s) = mask_helper(in_0, lo)
val (hi_0, hi_1, hi_m, hi_s) = mask_helper(in_0 && lo_0, hi)
val out_0 = lo_0 && hi_0
val out_1 = lo_1 && hi_1
val out_m = Cat(hi_m, lo_m) | Fill(len, (in_0 && out_1).asUInt())
val out_s = Mux(out_1, UInt(log2Up(len)), Mux(lo_0, hi_s, lo_s))
(out_0, out_1, out_m, out_s)
}
}
val pending_wmask = acq_wmask & ~done_wmask
val put_addr = PriorityEncoder(pending_wmask)
val (wmask_0, _, exec_wmask, put_size) = mask_helper(Bool(true), pending_wmask)
next_wmask := done_wmask | exec_wmask
// Calculate the address, with consideration to put fragments and bursts
val addr_block = io.acquire.bits.addr_block
val addr_beatin= io.acquire.bits.addr_beat
val addr_burst = Mux(isReadBurst, addr_beatin + burst, addr_beatin)
val addr_byte = Mux(isPut, put_addr, io.acquire.bits.addr_byte())
val addr_beat = Mux(isWriteBurst, UInt(0), addr_burst)
val ahbAddr = Cat(addr_block, addr_burst, addr_byte)
val ahbSize = Mux(isPut, put_size, Mux(isBurst, UInt(log2Ceil(tlDataBytes)), io.acquire.bits.op_size()))
val ahbBurst = MuxLookup(io.acquire.bits.a_type, HBURST_SINGLE, Array(
Acquire.getType -> HBURST_SINGLE,
Acquire.getBlockType -> burstSize,
Acquire.putType -> HBURST_SINGLE,
Acquire.putBlockType -> burstSize,
Acquire.putAtomicType -> HBURST_SINGLE,
Acquire.getPrefetchType -> HBURST_SINGLE,
Acquire.putPrefetchType -> HBURST_SINGLE))
val ahbWrite = MuxLookup(io.acquire.bits.a_type, Bool(false), Array(
Acquire.getType -> Bool(false),
Acquire.getBlockType -> Bool(false),
Acquire.putType -> Bool(true),
Acquire.putBlockType -> Bool(true),
Acquire.putAtomicType -> MuxLookup(atom_state, Bool(false), Array(
s_atom_r -> Bool(false),
s_atom_idle1 -> Bool(false), // don't care
s_atom_w -> Bool(true),
s_atom_idle2 -> Bool(true))), // don't care
Acquire.getPrefetchType -> Bool(false), // don't care
Acquire.putPrefetchType -> Bool(true))) // don't care
val ahbExecute = MuxLookup(io.acquire.bits.a_type, Bool(false), Array(
Acquire.getType -> Bool(true),
Acquire.getBlockType -> Bool(true),
Acquire.putType -> !wmask_0, // handle the case of a Put with no bytes!
Acquire.putBlockType -> Bool(true),
Acquire.putAtomicType -> MuxLookup(atom_state, Bool(false), Array(
s_atom_r -> Bool(true),
s_atom_idle1 -> Bool(false),
s_atom_w -> Bool(true),
s_atom_idle2 -> Bool(false))),
Acquire.getPrefetchType -> Bool(false),
Acquire.putPrefetchType -> Bool(false)))
val respondTL = MuxLookup(io.acquire.bits.a_type, Bool(false), Array(
Acquire.getType -> Bool(true),
Acquire.getBlockType -> Bool(true),
Acquire.putType -> last_wmask,
Acquire.putBlockType -> last_burst,
Acquire.putAtomicType -> MuxLookup(atom_state, Bool(false), Array(
s_atom_r -> Bool(true), // they want the old data
s_atom_idle1 -> Bool(false),
s_atom_w -> Bool(false),
s_atom_idle2 -> Bool(false))),
Acquire.getPrefetchType -> Bool(true),
Acquire.putPrefetchType -> Bool(true)))
io.request.valid := io.acquire.valid
io.request.bits.htrans := HTRANS_IDLE // unused/ignored
io.request.bits.haddr := ahbAddr
io.request.bits.hmastlock := isAtomic && atom_state =/= s_atom_idle2
io.request.bits.hwrite := ahbWrite
io.request.bits.hburst := ahbBurst
io.request.bits.hsize := ahbSize
io.request.bits.hprot := HPROT_DATA | HPROT_PRIVILEGED
io.request.bits.hwdata := io.acquire.bits.data
io.request.bits.executeAHB := ahbExecute
io.request.bits.respondTL := respondTL
io.request.bits.latchAtom := isAtomic && atom_state === s_atom_r
io.request.bits.firstBurst := burst === firstBurst
io.request.bits.finalBurst := burst === finalBurst || !isBurst
io.request.bits.cmd := io.acquire.bits.op_code()
io.request.bits.is_builtin_type := Bool(true)
io.request.bits.g_type := io.acquire.bits.getBuiltInGrantType()
io.request.bits.client_xact_id := io.acquire.bits.client_xact_id
io.request.bits.addr_beat := addr_beat
val debugBurst = Reg(UInt())
when (io.request.valid) {
debugBurst := addr_burst - burst
}
// We only support built-in TileLink requests
assert(!io.acquire.valid || io.acquire.bits.is_builtin_type, "AHB bridge only supports builtin TileLink types")
// Ensure alignment of address to size
assert(!io.acquire.valid || (ahbAddr & ((UInt(1) << ahbSize) - UInt(1))) === UInt(0), "TileLink operation misaligned")
// If this is a putBlock, make sure it moves properly
assert(!io.acquire.valid || !isBurst || burst === firstBurst || debugBurst === addr_burst - burst, "TileLink putBlock beats not sequential")
// We better not get an incomplete TileLink acquire
assert(!io.acquire.valid || isBurst || burst === firstBurst, "TileLink never completed a putBlock")
// If we disabled atomic support, we better not see a request
assert(!io.acquire.bits.is(Acquire.putAtomicType) || Bool(supportAtomics))
}
// AHB stage2: execute AHBRequests
class AHBBusMaster(supportAtomics: Boolean = false)(implicit val p: Parameters) extends Module
with HasHastiParameters
with HasTileLinkParameters
with HasAddrMapParameters {
val io = new Bundle {
val request = new DecoupledIO(new AHBRequestIO).flip
val grant = new DecoupledIO(new Grant)
val ahb = new HastiMasterIO()
}
// All AHB outputs are registered (they might be IOs)
val midBurst = Reg(init = Bool(false))
val htrans = Reg(init = HTRANS_IDLE)
val haddr = Reg(UInt())
val hmastlock = Reg(init = Bool(false))
val hwrite = Reg(Bool())
val hburst = Reg(UInt())
val hsize = Reg(init = UInt(0, width = SZ_HSIZE))
val hprot = Reg(UInt())
val hwdata0 = Reg(Bits())
val hwdata1 = Reg(Bits())
val hrdata = Reg(Bits())
io.ahb.htrans := htrans
io.ahb.haddr := haddr
io.ahb.hmastlock := hmastlock
io.ahb.hwrite := hwrite
io.ahb.hburst := hburst
io.ahb.hsize := hsize
io.ahb.hprot := hprot
io.ahb.hwdata := hwdata1 // one cycle after the address phase
// TileLink response data needed in data phase
val respondTL0 = Reg(init = Bool(false))
val respondTL1 = Reg(init = Bool(false))
val latchAtom0 = Reg(init = Bool(false))
val latchAtom1 = Reg(init = Bool(false))
val executeAHB0 = Reg(init = Bool(false))
val executeAHB1 = Reg(init = Bool(false))
val bubble = Reg(init = Bool(true)) // nothing useful in address phase
val cmd = Reg(Bits())
val g_type0 = Reg(UInt())
val g_type1 = Reg(UInt())
val client_xact_id0 = Reg(Bits())
val client_xact_id1 = Reg(Bits())
val addr_beat0 = Reg(UInt())
val addr_beat1 = Reg(UInt())
val grant1 = Reg(new Grant)
// It is allowed to progress from Idle/Busy during a wait state
val addrReady = io.ahb.hready || bubble || (!executeAHB1 && !executeAHB0)
val dataReady = io.ahb.hready || !executeAHB1
// Only accept a new AHBRequest if we have enough buffer space in the pad
// to accomodate a persistent drop in TileLink's grant.ready
io.request.ready := addrReady && io.grant.ready
// htrans must be updated even if no request is valid
when (addrReady) {
when (io.request.fire() && io.request.bits.executeAHB) {
midBurst := !io.request.bits.finalBurst
when (io.request.bits.firstBurst) {
htrans := HTRANS_NONSEQ
} .otherwise {
htrans := HTRANS_SEQ
}
} .otherwise {
when (midBurst) {
htrans := HTRANS_BUSY
} .otherwise {
htrans := HTRANS_IDLE
}
}
}
// Address phase, clear repondTL when we have nothing to do
when (addrReady) {
when (io.request.fire()) {
respondTL0 := io.request.bits.respondTL
latchAtom0 := io.request.bits.latchAtom
executeAHB0:= io.request.bits.executeAHB
bubble := Bool(false)
} .otherwise {
respondTL0 := Bool(false)
latchAtom0 := Bool(false)
executeAHB0:= Bool(false)
bubble := Bool(true) // an atom-injected Idle is not a bubble!
}
}
// Transfer bulk address phase
when (io.request.fire()) {
haddr := io.request.bits.haddr
hmastlock := io.request.bits.hmastlock
hwrite := io.request.bits.hwrite
hburst := io.request.bits.hburst
hsize := io.request.bits.hsize
hprot := io.request.bits.hprot
hwdata0 := io.request.bits.hwdata
cmd := io.request.bits.cmd
g_type0 := io.request.bits.g_type
client_xact_id0 := io.request.bits.client_xact_id
addr_beat0 := io.request.bits.addr_beat
}
// Execute Atomic ops; unused and optimized away if !supportAtomics
val amo_p = p.alterPartial({
case CacheBlockOffsetBits => hastiAddrBits
})
val alu = Module(new AMOALU(hastiDataBits, rhsIsAligned = true)(amo_p))
alu.io.addr := haddr
alu.io.cmd := cmd
alu.io.typ := hsize
alu.io.rhs := hwdata0
alu.io.lhs := hrdata
// Transfer bulk data phase
when (dataReady) {
when (addrReady) {
respondTL1 := respondTL0
latchAtom1 := latchAtom0
executeAHB1 := executeAHB0
} .otherwise {
respondTL1 := Bool(false)
latchAtom1 := Bool(false)
executeAHB1 := Bool(false)
}
hwdata1 := Mux(Bool(supportAtomics), alu.io.out, hwdata0)
g_type1 := g_type0
client_xact_id1 := client_xact_id0
addr_beat1 := addr_beat0
}
// Latch the read result for an atomic operation
when (dataReady && latchAtom1) {
hrdata := io.ahb.hrdata
}
// Only issue TL grant when the slave has provided data
io.grant.valid := dataReady && respondTL1
io.grant.bits := Grant(
is_builtin_type = Bool(true),
g_type = g_type1,
client_xact_id = client_xact_id1,
manager_xact_id = UInt(0),
addr_beat = addr_beat1,
data = io.ahb.hrdata)
// We cannot support errors from AHB to TileLink
assert(!io.ahb.hresp, "AHB hresp error detected and cannot be reported via TileLink")
}
class AHBBridge(supportAtomics: Boolean = true)(implicit val p: Parameters) extends Module
with HasHastiParameters
with HasTileLinkParameters
with HasAddrMapParameters {
val io = new Bundle {
val tl = new ClientUncachedTileLinkIO().flip
val ahb = new HastiMasterIO()
}
// Hasti and TileLink widths must agree at this point in the topology
require (tlDataBits == hastiDataBits)
require (p(PAddrBits) == hastiAddrBits)
// AHB does not permit bursts to cross a 1KB boundary
require (tlDataBits * tlDataBeats <= 1024*8)
// tlDataBytes must be a power of 2
require (1 << log2Ceil(tlDataBytes) == tlDataBytes)
// Create the sub-blocks
val fsm = Module(new AHBTileLinkIn(supportAtomics))
val bus = Module(new AHBBusMaster(supportAtomics))
val pad = Module(new Queue(new Grant, 4))
fsm.io.acquire <> Queue(io.tl.acquire, 2) // Pipe is also acceptable
bus.io.request <> fsm.io.request
io.ahb <> bus.io.ahb
io.tl.grant <> pad.io.deq
// The pad is needed to absorb AHB progress while !grant.ready
// We are only 'ready' if the pad has at least 3 cycles of space
bus.io.grant.ready := pad.io.count <= UInt(1)
pad.io.enq.bits := bus.io.grant.bits
pad.io.enq.valid := bus.io.grant.valid
}

View File

@ -0,0 +1,383 @@
package uncore.converters
import Chisel._
import junctions._
import uncore.tilelink._
import uncore.constants._
import cde.Parameters
import scala.math.min
class IdMapper(val inIdBits: Int, val outIdBits: Int,
val forceMapping: Boolean = false)
(implicit val p: Parameters) extends Module {
val io = new Bundle {
val req = new Bundle {
val valid = Bool(INPUT)
val ready = Bool(OUTPUT)
val in_id = UInt(INPUT, inIdBits)
val out_id = UInt(OUTPUT, outIdBits)
}
val resp = new Bundle {
val valid = Bool(INPUT)
val matches = Bool(OUTPUT)
val out_id = UInt(INPUT, outIdBits)
val in_id = UInt(OUTPUT, inIdBits)
}
}
val maxInXacts = 1 << inIdBits
if (inIdBits <= outIdBits && !forceMapping) {
io.req.ready := Bool(true)
io.req.out_id := io.req.in_id
io.resp.matches := Bool(true)
io.resp.in_id := io.resp.out_id
} else {
val nInXacts = 1 << inIdBits
// No point in allowing more out xacts than in xacts
val nOutXacts = min(1 << outIdBits, nInXacts)
val out_id_free = Reg(init = Vec.fill(nOutXacts){Bool(true)})
val in_id_free = Reg(init = Vec.fill(nInXacts){Bool(true)})
val next_out_id = PriorityEncoder(out_id_free)
val id_mapping = Reg(Vec(nOutXacts, UInt(0, inIdBits)))
val req_fire = io.req.valid && io.req.ready
when (req_fire) {
out_id_free(io.req.out_id) := Bool(false)
in_id_free(io.req.in_id) := Bool(false)
id_mapping(io.req.out_id) := io.req.in_id
}
when (io.resp.valid) {
out_id_free(io.resp.out_id) := Bool(true)
in_id_free(io.resp.in_id) := Bool(true)
}
io.req.ready := out_id_free.reduce(_ || _) && in_id_free(io.req.in_id)
io.req.out_id := next_out_id
io.resp.in_id := id_mapping(io.resp.out_id)
io.resp.matches := !out_id_free(io.resp.out_id)
}
}
class NastiIOTileLinkIOConverterInfo(implicit p: Parameters) extends TLBundle()(p) {
val addr_beat = UInt(width = tlBeatAddrBits)
val subblock = Bool()
}
class NastiIOTileLinkIOConverter(implicit p: Parameters) extends TLModule()(p)
with HasNastiParameters {
val io = new Bundle {
val tl = new ClientUncachedTileLinkIO().flip
val nasti = new NastiIO
}
val dataBits = tlDataBits*tlDataBeats
require(tlDataBits == nastiXDataBits, "Data sizes between LLC and MC don't agree") // TODO: remove this restriction
require(tlDataBeats < (1 << nastiXLenBits), "Can't have that many beats")
val has_data = io.tl.acquire.bits.hasData()
val is_subblock = io.tl.acquire.bits.isSubBlockType()
val is_multibeat = io.tl.acquire.bits.hasMultibeatData()
val (tl_cnt_out, tl_wrap_out) = Counter(
io.tl.acquire.fire() && is_multibeat, tlDataBeats)
val get_valid = io.tl.acquire.valid && !has_data
val put_valid = io.tl.acquire.valid && has_data
// Reorder queue saves extra information needed to send correct
// grant back to TL client
val roqIdBits = min(tlClientXactIdBits, nastiXIdBits)
val roq = Module(new ReorderQueue(
new NastiIOTileLinkIOConverterInfo, roqIdBits))
val get_id_mapper = Module(new IdMapper(tlClientXactIdBits, nastiXIdBits))
val put_id_mapper = Module(new IdMapper(tlClientXactIdBits, nastiXIdBits))
val get_id_ready = get_id_mapper.io.req.ready
val put_id_mask = is_subblock || io.tl.acquire.bits.addr_beat === UInt(0)
val put_id_ready = put_id_mapper.io.req.ready || !put_id_mask
// For Get/GetBlock, make sure Reorder queue can accept new entry
val get_helper = DecoupledHelper(
get_valid,
roq.io.enq.ready,
io.nasti.ar.ready,
get_id_ready)
val w_inflight = Reg(init = Bool(false))
val w_id_reg = Reg(init = UInt(0, nastiXIdBits))
val w_id = Mux(w_inflight, w_id_reg, put_id_mapper.io.req.out_id)
// For Put/PutBlock, make sure aw and w channel are both ready before
// we send the first beat
val aw_ready = w_inflight || io.nasti.aw.ready
val put_helper = DecoupledHelper(
put_valid,
aw_ready,
io.nasti.w.ready,
put_id_ready)
val (nasti_cnt_out, nasti_wrap_out) = Counter(
io.nasti.r.fire() && !roq.io.deq.data.subblock, tlDataBeats)
roq.io.enq.valid := get_helper.fire(roq.io.enq.ready)
roq.io.enq.bits.tag := io.nasti.ar.bits.id
roq.io.enq.bits.data.addr_beat := io.tl.acquire.bits.addr_beat
roq.io.enq.bits.data.subblock := is_subblock
roq.io.deq.valid := io.nasti.r.fire() && (nasti_wrap_out || roq.io.deq.data.subblock)
roq.io.deq.tag := io.nasti.r.bits.id
get_id_mapper.io.req.valid := get_helper.fire(get_id_ready)
get_id_mapper.io.req.in_id := io.tl.acquire.bits.client_xact_id
get_id_mapper.io.resp.valid := io.nasti.r.fire() && io.nasti.r.bits.last
get_id_mapper.io.resp.out_id := io.nasti.r.bits.id
put_id_mapper.io.req.valid := put_helper.fire(put_id_ready, put_id_mask)
put_id_mapper.io.req.in_id := io.tl.acquire.bits.client_xact_id
put_id_mapper.io.resp.valid := io.nasti.b.fire()
put_id_mapper.io.resp.out_id := io.nasti.b.bits.id
// Decompose outgoing TL Acquires into Nasti address and data channels
io.nasti.ar.valid := get_helper.fire(io.nasti.ar.ready)
io.nasti.ar.bits := NastiReadAddressChannel(
id = get_id_mapper.io.req.out_id,
addr = io.tl.acquire.bits.full_addr(),
size = Mux(is_subblock,
io.tl.acquire.bits.op_size(),
UInt(log2Ceil(tlDataBytes))),
len = Mux(is_subblock, UInt(0), UInt(tlDataBeats - 1)))
def mask_helper(all_inside_0: Seq[Bool], defsize: Int): (Seq[Bool], UInt, UInt) = {
val len = all_inside_0.size
if (len == 1) {
(Seq(Bool(true)), UInt(0), UInt(defsize))
} else {
val sub_inside_0 = Seq.tabulate (len/2) { i => all_inside_0(2*i) && all_inside_0(2*i+1) }
val (sub_outside_0, sub_offset, sub_size) = mask_helper(sub_inside_0, defsize+1)
val all_outside_0 = Seq.tabulate (len) { i => sub_outside_0(i/2) && all_inside_0(i^1) }
val odd_outside_0 = Seq.tabulate (len/2) { i => all_outside_0(2*i+1) }
val odd_outside = odd_outside_0.reduce (_ || _)
val all_outside = all_outside_0.reduce (_ || _)
val offset = Cat(sub_offset, odd_outside)
val size = Mux(all_outside, UInt(defsize), sub_size)
(all_outside_0, offset, size)
}
}
val all_inside_0 = (~io.tl.acquire.bits.wmask()).toBools
val (_, put_offset, put_size) = mask_helper(all_inside_0, 0)
io.nasti.aw.valid := put_helper.fire(aw_ready, !w_inflight)
io.nasti.aw.bits := NastiWriteAddressChannel(
id = put_id_mapper.io.req.out_id,
addr = io.tl.acquire.bits.full_addr() |
Mux(is_multibeat, UInt(0), put_offset),
size = Mux(is_multibeat, UInt(log2Ceil(tlDataBytes)), put_size),
len = Mux(is_multibeat, UInt(tlDataBeats - 1), UInt(0)))
io.nasti.w.valid := put_helper.fire(io.nasti.w.ready)
io.nasti.w.bits := NastiWriteDataChannel(
id = w_id,
data = io.tl.acquire.bits.data,
strb = Some(io.tl.acquire.bits.wmask()),
last = Mux(w_inflight,
tl_cnt_out === UInt(tlDataBeats - 1), !is_multibeat))
io.tl.acquire.ready := Mux(has_data,
put_helper.fire(put_valid),
get_helper.fire(get_valid))
when (!w_inflight && io.tl.acquire.fire() && is_multibeat) {
w_inflight := Bool(true)
w_id_reg := w_id
}
when (w_inflight) {
when (tl_wrap_out) { w_inflight := Bool(false) }
}
// Aggregate incoming NASTI responses into TL Grants
val (tl_cnt_in, tl_wrap_in) = Counter(
io.tl.grant.fire() && io.tl.grant.bits.hasMultibeatData(), tlDataBeats)
val gnt_arb = Module(new LockingArbiter(new GrantToDst, 2,
tlDataBeats, Some((gnt: GrantToDst) => gnt.hasMultibeatData())))
io.tl.grant <> gnt_arb.io.out
gnt_arb.io.in(0).valid := io.nasti.r.valid
io.nasti.r.ready := gnt_arb.io.in(0).ready
gnt_arb.io.in(0).bits := Grant(
is_builtin_type = Bool(true),
g_type = Mux(roq.io.deq.data.subblock,
Grant.getDataBeatType, Grant.getDataBlockType),
client_xact_id = get_id_mapper.io.resp.in_id,
manager_xact_id = UInt(0),
addr_beat = Mux(roq.io.deq.data.subblock, roq.io.deq.data.addr_beat, tl_cnt_in),
data = io.nasti.r.bits.data)
assert(!roq.io.deq.valid || roq.io.deq.matches,
"TL -> NASTI converter ReorderQueue: NASTI tag error")
assert(!gnt_arb.io.in(0).valid || get_id_mapper.io.resp.matches,
"TL -> NASTI ID Mapper: NASTI tag error")
gnt_arb.io.in(1).valid := io.nasti.b.valid
io.nasti.b.ready := gnt_arb.io.in(1).ready
gnt_arb.io.in(1).bits := Grant(
is_builtin_type = Bool(true),
g_type = Grant.putAckType,
client_xact_id = put_id_mapper.io.resp.in_id,
manager_xact_id = UInt(0),
addr_beat = UInt(0),
data = Bits(0))
assert(!gnt_arb.io.in(1).valid || put_id_mapper.io.resp.matches, "NASTI tag error")
assert(!io.nasti.r.valid || io.nasti.r.bits.resp === UInt(0), "NASTI read error")
assert(!io.nasti.b.valid || io.nasti.b.bits.resp === UInt(0), "NASTI write error")
}
class TileLinkIONastiIOConverter(implicit p: Parameters) extends TLModule()(p)
with HasNastiParameters {
val io = new Bundle {
val nasti = (new NastiIO).flip
val tl = new ClientUncachedTileLinkIO
}
val (s_idle :: s_put :: Nil) = Enum(Bits(), 2)
val state = Reg(init = s_idle)
private val blockOffset = tlByteAddrBits + tlBeatAddrBits
val aw_req = Reg(new NastiWriteAddressChannel)
val w_tl_id = Reg(io.tl.acquire.bits.client_xact_id)
def is_singlebeat(chan: NastiAddressChannel): Bool =
chan.len === UInt(0)
def is_multibeat(chan: NastiAddressChannel): Bool =
chan.len === UInt(tlDataBeats - 1) && chan.size === UInt(log2Up(tlDataBytes))
def nasti_addr_block(chan: NastiAddressChannel): UInt =
chan.addr(nastiXAddrBits - 1, blockOffset)
def nasti_addr_beat(chan: NastiAddressChannel): UInt =
chan.addr(blockOffset - 1, tlByteAddrBits)
def nasti_addr_byte(chan: NastiAddressChannel): UInt =
chan.addr(tlByteAddrBits - 1, 0)
def size_mask(size: UInt): UInt =
(UInt(1) << (UInt(1) << size)) - UInt(1)
def nasti_wmask(aw: NastiWriteAddressChannel, w: NastiWriteDataChannel): UInt = {
val base = w.strb & size_mask(aw.size)
val addr_byte = nasti_addr_byte(aw)
w.strb & (size_mask(aw.size) << addr_byte)
}
def tl_last(gnt: GrantMetadata): Bool =
!gnt.hasMultibeatData() || gnt.addr_beat === UInt(tlDataBeats - 1)
def tl_b_grant(gnt: GrantMetadata): Bool =
gnt.g_type === Grant.putAckType
assert(!io.nasti.ar.valid ||
is_singlebeat(io.nasti.ar.bits) || is_multibeat(io.nasti.ar.bits),
"NASTI read transaction cannot convert to TileLInk")
assert(!io.nasti.aw.valid ||
is_singlebeat(io.nasti.aw.bits) || is_multibeat(io.nasti.aw.bits),
"NASTI write transaction cannot convert to TileLInk")
val put_count = Reg(init = UInt(0, tlBeatAddrBits))
val get_id_mapper = Module(new IdMapper(nastiXIdBits, tlClientXactIdBits, true))
val put_id_mapper = Module(new IdMapper(nastiXIdBits, tlClientXactIdBits, true))
when (io.nasti.aw.fire()) {
aw_req := io.nasti.aw.bits
w_tl_id := put_id_mapper.io.req.out_id
state := s_put
}
when (io.nasti.w.fire()) {
put_count := put_count + UInt(1)
when (io.nasti.w.bits.last) {
put_count := UInt(0)
state := s_idle
}
}
val get_acquire = Mux(is_multibeat(io.nasti.ar.bits),
GetBlock(
client_xact_id = get_id_mapper.io.req.out_id,
addr_block = nasti_addr_block(io.nasti.ar.bits)),
Get(
client_xact_id = get_id_mapper.io.req.out_id,
addr_block = nasti_addr_block(io.nasti.ar.bits),
addr_beat = nasti_addr_beat(io.nasti.ar.bits),
addr_byte = nasti_addr_byte(io.nasti.ar.bits),
operand_size = io.nasti.ar.bits.size,
alloc = Bool(false)))
val put_acquire = Mux(is_multibeat(aw_req),
PutBlock(
client_xact_id = w_tl_id,
addr_block = nasti_addr_block(aw_req),
addr_beat = put_count,
data = io.nasti.w.bits.data,
wmask = Some(io.nasti.w.bits.strb)),
Put(
client_xact_id = w_tl_id,
addr_block = nasti_addr_block(aw_req),
addr_beat = nasti_addr_beat(aw_req),
data = io.nasti.w.bits.data,
wmask = Some(nasti_wmask(aw_req, io.nasti.w.bits))))
val get_helper = DecoupledHelper(
io.nasti.ar.valid,
get_id_mapper.io.req.ready,
io.tl.acquire.ready)
get_id_mapper.io.req.valid := get_helper.fire(
get_id_mapper.io.req.ready, state === s_idle)
get_id_mapper.io.req.in_id := io.nasti.ar.bits.id
get_id_mapper.io.resp.out_id := io.tl.grant.bits.client_xact_id
get_id_mapper.io.resp.valid := io.nasti.r.fire() && io.nasti.r.bits.last
val aw_ok = (state === s_idle && !io.nasti.ar.valid)
put_id_mapper.io.req.valid := aw_ok && io.nasti.aw.valid
put_id_mapper.io.req.in_id := io.nasti.aw.bits.id
put_id_mapper.io.resp.out_id := io.tl.grant.bits.client_xact_id
put_id_mapper.io.resp.valid := io.nasti.b.fire()
io.tl.acquire.bits := Mux(state === s_put, put_acquire, get_acquire)
io.tl.acquire.valid := get_helper.fire(io.tl.acquire.ready, state === s_idle) ||
(state === s_put && io.nasti.w.valid)
io.nasti.ar.ready := get_helper.fire(io.nasti.ar.valid, state === s_idle)
io.nasti.aw.ready := aw_ok && put_id_mapper.io.req.ready
io.nasti.w.ready := (state === s_put && io.tl.acquire.ready)
val nXacts = tlMaxClientXacts * tlMaxClientsPerPort
io.nasti.b.valid := io.tl.grant.valid && tl_b_grant(io.tl.grant.bits)
io.nasti.b.bits := NastiWriteResponseChannel(
id = put_id_mapper.io.resp.in_id)
assert(!io.nasti.b.valid || put_id_mapper.io.resp.matches,
"Put ID does not match")
io.nasti.r.valid := io.tl.grant.valid && !tl_b_grant(io.tl.grant.bits)
io.nasti.r.bits := NastiReadDataChannel(
id = get_id_mapper.io.resp.in_id,
data = io.tl.grant.bits.data,
last = tl_last(io.tl.grant.bits))
assert(!io.nasti.r.valid || get_id_mapper.io.resp.matches,
"Get ID does not match")
io.tl.grant.ready := Mux(tl_b_grant(io.tl.grant.bits),
io.nasti.b.ready, io.nasti.r.ready)
}

View File

@ -0,0 +1,32 @@
// See LICENSE for details
package uncore.converters
import Chisel._
import junctions._
import uncore.tilelink._
import cde.Parameters
/** Convert TileLink protocol to Smi protocol */
class SmiIOTileLinkIOConverter(val dataWidth: Int, val addrWidth: Int)
(implicit p: Parameters) extends Module {
val io = new Bundle {
val tl = (new ClientUncachedTileLinkIO).flip
val smi = new SmiIO(dataWidth, addrWidth)
}
def decoupledNastiConnect(outer: NastiIO, inner: NastiIO) {
outer.ar <> Queue(inner.ar)
outer.aw <> Queue(inner.aw)
outer.w <> Queue(inner.w)
inner.r <> Queue(outer.r)
inner.b <> Queue(outer.b)
}
val tl2nasti = Module(new NastiIOTileLinkIOConverter())
val nasti2smi = Module(new SmiIONastiIOConverter(dataWidth, addrWidth))
tl2nasti.io.tl <> io.tl
decoupledNastiConnect(nasti2smi.io.nasti, tl2nasti.io.nasti)
io.smi <> nasti2smi.io.smi
}

View File

@ -0,0 +1,681 @@
package uncore.converters
import Chisel._
import junctions._
import uncore.tilelink._
import uncore.util._
import uncore.constants._
import cde.Parameters
/** Utilities for safely wrapping a *UncachedTileLink by pinning probe.ready and release.valid low */
object TileLinkIOWrapper {
def apply(tl: ClientUncachedTileLinkIO)(implicit p: Parameters): ClientTileLinkIO = {
val conv = Module(new ClientTileLinkIOWrapper)
conv.io.in <> tl
conv.io.out
}
def apply(tl: UncachedTileLinkIO)(implicit p: Parameters): TileLinkIO = {
val conv = Module(new TileLinkIOWrapper)
conv.io.in <> tl
conv.io.out
}
def apply(tl: ClientTileLinkIO): ClientTileLinkIO = tl
def apply(tl: TileLinkIO): TileLinkIO = tl
}
class TileLinkIOWrapper(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val in = new UncachedTileLinkIO().flip
val out = new TileLinkIO
}
io.out.acquire <> io.in.acquire
io.in.grant <> io.out.grant
io.out.finish <> io.in.finish
io.out.probe.ready := Bool(true)
io.out.release.valid := Bool(false)
}
class ClientTileLinkIOWrapper(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val in = new ClientUncachedTileLinkIO().flip
val out = new ClientTileLinkIO
}
io.out.acquire <> io.in.acquire
io.in.grant <> io.out.grant
io.out.probe.ready := Bool(true)
io.out.release.valid := Bool(false)
}
class ClientTileLinkIOUnwrapper(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val in = new ClientTileLinkIO().flip
val out = new ClientUncachedTileLinkIO
}
val acqArb = Module(new LockingRRArbiter(new Acquire, 2, tlDataBeats,
Some((acq: Acquire) => acq.hasMultibeatData())))
val acqRoq = Module(new ReorderQueue(Bool(), tlClientXactIdBits))
val relRoq = Module(new ReorderQueue(Bool(), tlClientXactIdBits))
val iacq = io.in.acquire.bits
val irel = io.in.release.bits
val ognt = io.out.grant.bits
val acq_roq_enq = iacq.first()
val rel_roq_enq = irel.first()
val acq_roq_ready = !acq_roq_enq || acqRoq.io.enq.ready
val rel_roq_ready = !rel_roq_enq || relRoq.io.enq.ready
val acq_helper = DecoupledHelper(
io.in.acquire.valid,
acq_roq_ready,
acqArb.io.in(0).ready)
val rel_helper = DecoupledHelper(
io.in.release.valid,
rel_roq_ready,
acqArb.io.in(1).ready)
acqRoq.io.enq.valid := acq_helper.fire(acq_roq_ready, acq_roq_enq)
acqRoq.io.enq.bits.data := iacq.isBuiltInType()
acqRoq.io.enq.bits.tag := iacq.client_xact_id
acqArb.io.in(0).valid := acq_helper.fire(acqArb.io.in(0).ready)
acqArb.io.in(0).bits := Acquire(
is_builtin_type = Bool(true),
a_type = Mux(iacq.isBuiltInType(),
iacq.a_type, Acquire.getBlockType),
client_xact_id = iacq.client_xact_id,
addr_block = iacq.addr_block,
addr_beat = iacq.addr_beat,
data = iacq.data,
union = iacq.union)
io.in.acquire.ready := acq_helper.fire(io.in.acquire.valid)
relRoq.io.enq.valid := rel_helper.fire(rel_roq_ready, rel_roq_enq)
relRoq.io.enq.bits.data := irel.isVoluntary()
relRoq.io.enq.bits.tag := irel.client_xact_id
acqArb.io.in(1).valid := rel_helper.fire(acqArb.io.in(1).ready)
acqArb.io.in(1).bits := PutBlock(
client_xact_id = irel.client_xact_id,
addr_block = irel.addr_block,
addr_beat = irel.addr_beat,
data = irel.data)
io.in.release.ready := rel_helper.fire(io.in.release.valid)
io.out.acquire <> acqArb.io.out
val grant_deq_roq = io.out.grant.fire() && ognt.last()
acqRoq.io.deq.valid := acqRoq.io.deq.matches && grant_deq_roq
acqRoq.io.deq.tag := ognt.client_xact_id
relRoq.io.deq.valid := !acqRoq.io.deq.matches && grant_deq_roq
relRoq.io.deq.tag := ognt.client_xact_id
assert(!grant_deq_roq || acqRoq.io.deq.matches || relRoq.io.deq.matches,
"TileLink Unwrapper: client_xact_id mismatch")
val gnt_builtin = acqRoq.io.deq.data
val gnt_voluntary = relRoq.io.deq.data
val acq_grant = Grant(
is_builtin_type = gnt_builtin,
g_type = Mux(gnt_builtin, ognt.g_type, tlCoh.getExclusiveGrantType),
client_xact_id = ognt.client_xact_id,
manager_xact_id = ognt.manager_xact_id,
addr_beat = ognt.addr_beat,
data = ognt.data)
assert(!io.in.release.valid || io.in.release.bits.isVoluntary(), "Unwrapper can only process voluntary releases.")
val rel_grant = Grant(
is_builtin_type = Bool(true),
g_type = Grant.voluntaryAckType, // We should only every be working with voluntary releases
client_xact_id = ognt.client_xact_id,
manager_xact_id = ognt.manager_xact_id,
addr_beat = ognt.addr_beat,
data = ognt.data)
io.in.grant.valid := io.out.grant.valid
io.in.grant.bits := Mux(acqRoq.io.deq.matches, acq_grant, rel_grant)
io.out.grant.ready := io.in.grant.ready
io.in.probe.valid := Bool(false)
}
object TileLinkWidthAdapter {
def apply(in: ClientUncachedTileLinkIO, outerId: String)(implicit p: Parameters) = {
val outerDataBits = p(TLKey(outerId)).dataBitsPerBeat
if (outerDataBits > in.tlDataBits) {
val widener = Module(new TileLinkIOWidener(in.p(TLId), outerId))
widener.io.in <> in
widener.io.out
} else if (outerDataBits < in.tlDataBits) {
val narrower = Module(new TileLinkIONarrower(in.p(TLId), outerId))
narrower.io.in <> in
narrower.io.out
} else { in }
}
def apply(out: ClientUncachedTileLinkIO, in: ClientUncachedTileLinkIO)(implicit p: Parameters): Unit = {
require(out.tlDataBits * out.tlDataBeats == in.tlDataBits * in.tlDataBeats)
out <> apply(in, out.p(TLId))
}
}
class TileLinkIOWidener(innerTLId: String, outerTLId: String)
(implicit p: Parameters) extends TLModule()(p) {
val paddrBits = p(PAddrBits)
val innerParams = p(TLKey(innerTLId))
val outerParams = p(TLKey(outerTLId))
val innerDataBeats = innerParams.dataBeats
val innerDataBits = innerParams.dataBitsPerBeat
val innerWriteMaskBits = innerParams.writeMaskBits
val innerByteAddrBits = log2Up(innerWriteMaskBits)
val innerMaxXacts = innerParams.maxClientXacts * innerParams.maxClientsPerPort
val innerXactIdBits = log2Up(innerMaxXacts)
val outerDataBeats = outerParams.dataBeats
val outerDataBits = outerParams.dataBitsPerBeat
val outerWriteMaskBits = outerParams.writeMaskBits
val outerByteAddrBits = log2Up(outerWriteMaskBits)
val outerBeatAddrBits = log2Up(outerDataBeats)
val outerBlockOffset = outerBeatAddrBits + outerByteAddrBits
val outerMaxClients = outerParams.maxClientsPerPort
val outerClientIdBits = log2Up(outerParams.maxClientXacts * outerMaxClients)
val outerManagerIdBits = log2Up(outerParams.maxManagerXacts)
val outerBlockAddrBits = paddrBits - outerBlockOffset
require(outerDataBeats <= innerDataBeats)
require(outerDataBits >= innerDataBits)
require(outerDataBits % innerDataBits == 0)
require(outerDataBits * outerDataBeats == innerDataBits * innerDataBeats)
val factor = innerDataBeats / outerDataBeats
val io = new Bundle {
val in = new ClientUncachedTileLinkIO()(p.alterPartial({case TLId => innerTLId})).flip
val out = new ClientUncachedTileLinkIO()(p.alterPartial({case TLId => outerTLId}))
}
val iacq = io.in.acquire.bits
val oacq = io.out.acquire.bits
val ognt = io.out.grant.bits
val ignt = io.in.grant.bits
val shrink = iacq.a_type === Acquire.putBlockType
val stretch = ognt.g_type === Grant.getDataBlockType
val smallget = iacq.a_type === Acquire.getType
val smallput = iacq.a_type === Acquire.putType
val smallgnt = ognt.g_type === Grant.getDataBeatType
val sending_put = Reg(init = Bool(false))
val collecting = Reg(init = Bool(false))
val put_block = Reg(UInt(width = outerBlockAddrBits))
val put_id = Reg(UInt(width = outerClientIdBits))
val put_data = Reg(Vec(factor, UInt(width = innerDataBits)))
val put_wmask = Reg(Vec(factor, UInt(width = innerWriteMaskBits)))
val put_allocate = Reg(Bool())
val (put_beat, put_done) = Counter(io.out.acquire.fire() && oacq.hasMultibeatData(), outerDataBeats)
val (recv_idx, recv_done) = Counter(io.in.acquire.fire() && iacq.hasMultibeatData(), factor)
val in_addr = iacq.full_addr()
val out_addr_block = in_addr(paddrBits - 1, outerBlockOffset)
val out_addr_beat = in_addr(outerBlockOffset - 1, outerByteAddrBits)
val out_addr_byte = in_addr(outerByteAddrBits - 1, 0)
val switch_addr = in_addr(outerByteAddrBits - 1, innerByteAddrBits)
val smallget_switch = Reg(Vec(innerMaxXacts, switch_addr))
def align_data(addr: UInt, data: UInt): UInt =
data << Cat(addr, UInt(0, log2Up(innerDataBits)))
def align_wmask(addr: UInt, wmask: UInt): UInt =
wmask << Cat(addr, UInt(0, log2Up(innerWriteMaskBits)))
val outerConfig = p.alterPartial({ case TLId => outerTLId })
val get_acquire = Get(
client_xact_id = iacq.client_xact_id,
addr_block = out_addr_block,
addr_beat = out_addr_beat,
addr_byte = out_addr_byte,
operand_size = iacq.op_size(),
alloc = iacq.allocate())(outerConfig)
val get_block_acquire = GetBlock(
client_xact_id = iacq.client_xact_id,
addr_block = out_addr_block,
alloc = iacq.allocate())(outerConfig)
val put_acquire = Put(
client_xact_id = iacq.client_xact_id,
addr_block = out_addr_block,
addr_beat = out_addr_beat,
data = align_data(switch_addr, iacq.data),
wmask = Some(align_wmask(switch_addr, iacq.wmask())),
alloc = iacq.allocate())(outerConfig)
val put_block_acquire = PutBlock(
client_xact_id = put_id,
addr_block = put_block,
addr_beat = put_beat,
data = put_data.asUInt,
wmask = Some(put_wmask.asUInt))(outerConfig)
io.out.acquire.valid := sending_put || (!shrink && io.in.acquire.valid)
io.out.acquire.bits := MuxCase(get_block_acquire, Seq(
sending_put -> put_block_acquire,
smallget -> get_acquire,
smallput -> put_acquire))
io.in.acquire.ready := !sending_put && (shrink || io.out.acquire.ready)
when (io.in.acquire.fire() && shrink) {
when (!collecting) {
put_block := out_addr_block
put_id := iacq.client_xact_id
put_allocate := iacq.allocate()
collecting := Bool(true)
}
put_data(recv_idx) := iacq.data
put_wmask(recv_idx) := iacq.wmask()
}
when (io.in.acquire.fire() && smallget) {
smallget_switch(iacq.client_xact_id) := switch_addr
}
when (recv_done) { sending_put := Bool(true) }
when (sending_put && io.out.acquire.ready) { sending_put := Bool(false) }
when (put_done) { collecting := Bool(false) }
val returning_data = Reg(init = Bool(false))
val (send_idx, send_done) = Counter(
io.in.grant.ready && returning_data, factor)
val gnt_beat = Reg(UInt(width = outerBeatAddrBits))
val gnt_client_id = Reg(UInt(width = outerClientIdBits))
val gnt_manager_id = Reg(UInt(width = outerManagerIdBits))
val gnt_data = Reg(UInt(width = outerDataBits))
when (io.out.grant.fire() && stretch) {
gnt_data := ognt.data
gnt_client_id := ognt.client_xact_id
gnt_manager_id := ognt.manager_xact_id
gnt_beat := ognt.addr_beat
returning_data := Bool(true)
}
when (send_done) { returning_data := Bool(false) }
def select_data(data: UInt, sel: UInt): UInt =
data >> (sel << log2Up(innerDataBits))
val gnt_switch = smallget_switch(ognt.client_xact_id)
val innerConfig = p.alterPartial({ case TLId => innerTLId })
val get_block_grant = Grant(
is_builtin_type = Bool(true),
g_type = Grant.getDataBlockType,
client_xact_id = gnt_client_id,
manager_xact_id = gnt_manager_id,
addr_beat = Cat(gnt_beat, send_idx),
data = select_data(gnt_data, send_idx))(innerConfig)
val get_grant = Grant(
is_builtin_type = Bool(true),
g_type = Grant.getDataBeatType,
client_xact_id = ognt.client_xact_id,
manager_xact_id = ognt.manager_xact_id,
addr_beat = Cat(ognt.addr_beat, gnt_switch),
data = select_data(ognt.data, gnt_switch))(innerConfig)
val default_grant = Grant(
is_builtin_type = Bool(true),
g_type = ognt.g_type,
client_xact_id = ognt.client_xact_id,
manager_xact_id = ognt.manager_xact_id,
addr_beat = ognt.addr_beat,
data = ognt.data)(innerConfig)
io.in.grant.valid := returning_data || (!stretch && io.out.grant.valid)
io.in.grant.bits := MuxCase(default_grant, Seq(
returning_data -> get_block_grant,
smallgnt -> get_grant))
io.out.grant.ready := !returning_data && (stretch || io.in.grant.ready)
}
class TileLinkIONarrower(innerTLId: String, outerTLId: String)
(implicit p: Parameters) extends TLModule()(p) {
val innerParams = p(TLKey(innerTLId))
val outerParams = p(TLKey(outerTLId))
val innerDataBeats = innerParams.dataBeats
val innerDataBits = innerParams.dataBitsPerBeat
val innerWriteMaskBits = innerParams.writeMaskBits
val innerByteAddrBits = log2Up(innerWriteMaskBits)
val outerDataBeats = outerParams.dataBeats
val outerDataBits = outerParams.dataBitsPerBeat
val outerWriteMaskBits = outerParams.writeMaskBits
val outerByteAddrBits = log2Up(outerWriteMaskBits)
val outerBeatAddrBits = log2Up(outerDataBeats)
val outerBlockOffset = outerBeatAddrBits + outerByteAddrBits
val outerMaxClients = outerParams.maxClientsPerPort
val outerIdBits = log2Up(outerParams.maxClientXacts * outerMaxClients)
require(outerDataBeats > innerDataBeats)
require(outerDataBeats % innerDataBeats == 0)
require(outerDataBits < innerDataBits)
require(outerDataBits * outerDataBeats == innerDataBits * innerDataBeats)
val factor = outerDataBeats / innerDataBeats
val io = new Bundle {
val in = new ClientUncachedTileLinkIO()(p.alterPartial({case TLId => innerTLId})).flip
val out = new ClientUncachedTileLinkIO()(p.alterPartial({case TLId => outerTLId}))
}
val iacq = io.in.acquire.bits
val ognt = io.out.grant.bits
val stretch = iacq.a_type === Acquire.putBlockType
val shrink = iacq.a_type === Acquire.getBlockType
val smallput = iacq.a_type === Acquire.putType
val smallget = iacq.a_type === Acquire.getType
val acq_data_buffer = Reg(UInt(width = innerDataBits))
val acq_wmask_buffer = Reg(UInt(width = innerWriteMaskBits))
val acq_client_id = Reg(iacq.client_xact_id)
val acq_addr_block = Reg(iacq.addr_block)
val acq_addr_beat = Reg(iacq.addr_beat)
val oacq_ctr = Counter(factor)
val outer_beat_addr = iacq.full_addr()(outerBlockOffset - 1, outerByteAddrBits)
val outer_byte_addr = iacq.full_addr()(outerByteAddrBits - 1, 0)
val mask_chunks = Vec.tabulate(factor) { i =>
val lsb = i * outerWriteMaskBits
val msb = (i + 1) * outerWriteMaskBits - 1
iacq.wmask()(msb, lsb)
}
val data_chunks = Vec.tabulate(factor) { i =>
val lsb = i * outerDataBits
val msb = (i + 1) * outerDataBits - 1
iacq.data(msb, lsb)
}
val beat_sel = Cat(mask_chunks.map(mask => mask.orR).reverse)
val smallput_data = Mux1H(beat_sel, data_chunks)
val smallput_wmask = Mux1H(beat_sel, mask_chunks)
val smallput_beat = Cat(iacq.addr_beat, PriorityEncoder(beat_sel))
assert(!io.in.acquire.valid || !smallput || PopCount(beat_sel) <= UInt(1),
"Can't perform Put wider than outer width")
val read_size_ok = iacq.op_size() <= UInt(log2Ceil(outerDataBits / 8))
assert(!io.in.acquire.valid || !smallget || read_size_ok,
"Can't perform Get wider than outer width")
val outerConfig = p.alterPartial({ case TLId => outerTLId })
val innerConfig = p.alterPartial({ case TLId => innerTLId })
val get_block_acquire = GetBlock(
client_xact_id = iacq.client_xact_id,
addr_block = iacq.addr_block,
alloc = iacq.allocate())(outerConfig)
val put_block_acquire = PutBlock(
client_xact_id = acq_client_id,
addr_block = acq_addr_block,
addr_beat = if (factor > 1)
Cat(acq_addr_beat, oacq_ctr.value)
else acq_addr_beat,
data = acq_data_buffer(outerDataBits - 1, 0),
wmask = Some(acq_wmask_buffer(outerWriteMaskBits - 1, 0)))(outerConfig)
val get_acquire = Get(
client_xact_id = iacq.client_xact_id,
addr_block = iacq.addr_block,
addr_beat = outer_beat_addr,
addr_byte = outer_byte_addr,
operand_size = iacq.op_size(),
alloc = iacq.allocate())(outerConfig)
val put_acquire = Put(
client_xact_id = iacq.client_xact_id,
addr_block = iacq.addr_block,
addr_beat = smallput_beat,
data = smallput_data,
wmask = Some(smallput_wmask))(outerConfig)
val sending_put = Reg(init = Bool(false))
val pass_valid = io.in.acquire.valid && !stretch
io.out.acquire.bits := MuxCase(Wire(io.out.acquire.bits, init=iacq), Seq(
(sending_put, put_block_acquire),
(shrink, get_block_acquire),
(smallput, put_acquire),
(smallget, get_acquire)))
io.out.acquire.valid := sending_put || pass_valid
io.in.acquire.ready := !sending_put && (stretch || io.out.acquire.ready)
when (io.in.acquire.fire() && stretch) {
acq_data_buffer := iacq.data
acq_wmask_buffer := iacq.wmask()
acq_client_id := iacq.client_xact_id
acq_addr_block := iacq.addr_block
acq_addr_beat := iacq.addr_beat
sending_put := Bool(true)
}
when (sending_put && io.out.acquire.ready) {
acq_data_buffer := acq_data_buffer >> outerDataBits
acq_wmask_buffer := acq_wmask_buffer >> outerWriteMaskBits
when (oacq_ctr.inc()) { sending_put := Bool(false) }
}
val ognt_block = ognt.hasMultibeatData()
val gnt_data_buffer = Reg(Vec(factor, UInt(width = outerDataBits)))
val gnt_client_id = Reg(ognt.client_xact_id)
val gnt_manager_id = Reg(ognt.manager_xact_id)
val ignt_ctr = Counter(innerDataBeats)
val ognt_ctr = Counter(factor)
val sending_get = Reg(init = Bool(false))
val get_block_grant = Grant(
is_builtin_type = Bool(true),
g_type = Grant.getDataBlockType,
client_xact_id = gnt_client_id,
manager_xact_id = gnt_manager_id,
addr_beat = ignt_ctr.value,
data = gnt_data_buffer.asUInt)(innerConfig)
val smallget_grant = ognt.g_type === Grant.getDataBeatType
val get_grant = Grant(
is_builtin_type = Bool(true),
g_type = Grant.getDataBeatType,
client_xact_id = ognt.client_xact_id,
manager_xact_id = ognt.manager_xact_id,
addr_beat = ognt.addr_beat >> UInt(log2Up(factor)),
data = Fill(factor, ognt.data))(innerConfig)
io.in.grant.valid := sending_get || (io.out.grant.valid && !ognt_block)
io.out.grant.ready := !sending_get && (ognt_block || io.in.grant.ready)
io.in.grant.bits := MuxCase(Wire(io.in.grant.bits, init=ognt), Seq(
sending_get -> get_block_grant,
smallget_grant -> get_grant))
when (io.out.grant.valid && ognt_block && !sending_get) {
gnt_data_buffer(ognt_ctr.value) := ognt.data
when (ognt_ctr.inc()) {
gnt_client_id := ognt.client_xact_id
gnt_manager_id := ognt.manager_xact_id
sending_get := Bool(true)
}
}
when (io.in.grant.ready && sending_get) {
ignt_ctr.inc()
sending_get := Bool(false)
}
}
class TileLinkFragmenterSource(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val in = Decoupled(new Acquire).flip
val out = Decoupled(new Acquire)
val que = Decoupled(UInt(width = tlBeatAddrBits))
}
// Pipeline stage with acquire data; needed to ensure in.bits stay fixed when !in.ready
val acq_valid = RegInit(Bool(false))
val acq_bits = Reg(new Acquire)
// The last beat of generate acquire to send
val acq_last_beat = Reg(UInt(width = tlBeatAddrBits))
val acq_last = acq_bits.addr_beat === acq_last_beat
// 'in' has the first beat?
val in_multi_put = io.in.bits.isBuiltInType(Acquire.putBlockType)
val in_multi_get = io.in.bits.isBuiltInType(Acquire.getBlockType)
val in_first_beat = !in_multi_put || io.in.bits.addr_beat === UInt(0)
// Move stuff from acq to out whenever out is ready
io.out.valid := acq_valid
// When can acq accept a request?
val acq_ready = !acq_valid || (acq_last && io.out.ready)
// Move the first beat from in to acq only when both acq and que are ready
io.in.ready := (!in_first_beat || io.que.ready) && acq_ready
io.que.valid := (in_first_beat && io.in.valid) && acq_ready
// in.fire moves data from in to acq and (optionally) que
// out.fire moves data from acq to out
// Desired flow control results:
assert (!io.que.fire() || io.in.fire()) // 1. que.fire => in.fire
assert (!(io.in.fire() && in_first_beat) || io.que.fire()) // 2. in.fire && in_first_beat => que.fire
assert (!io.out.fire() || acq_valid) // 3. out.fire => acq_valid
assert (!io.in.fire() || (!acq_valid || (io.out.fire() && acq_last))) // 4. in.fire => !acq_valid || (out.fire && acq_last)
// Proofs:
// 1. que.fire => que.ready && in.valid && acq_ready => in.ready && in.valid
// 2. in.fire && in_first_beat => in.valid && acq_ready && [(!in_first_beat || que.ready) && in_first_beat] =>
// in.valid && acq_ready && que.ready && in_first_beat => que.valid && que.ready
// 3. out.fire => out.valid => acq_valid
// 4. in.fire => acq_ready => !acq_valid || (acq_last && out.ready) =>
// !acq_valid || (acq_valid && acq_last && out.ready) => !acq_valid || (acq_last && out.fire)
val multi_size = SInt(-1, width = tlBeatAddrBits).asUInt // TL2: use in.bits.size()/beatBits-1
val in_sizeMinus1 = Mux(in_multi_get || in_multi_put, multi_size, UInt(0))
val in_insertSizeMinus1 = Mux(in_multi_get, multi_size, UInt(0))
when (io.in.fire()) {
// Theorem 4 makes this safe; we overwrite garbage, or replace the final acq
acq_valid := Bool(true)
acq_bits := io.in.bits
acq_last_beat := io.in.bits.addr_beat + in_insertSizeMinus1
// Replace this with size truncation in TL2:
acq_bits.a_type := Mux(in_multi_put, Acquire.putType, Mux(in_multi_get, Acquire.getType, io.in.bits.a_type))
} .elsewhen (io.out.fire()) {
acq_valid := !acq_last // false => !in.valid || (!que.ready && in_first_beat)
acq_bits.addr_beat := acq_bits.addr_beat + UInt(1)
// acq_last && out.fire => acq_last && out.ready && acq_valid => acq_ready
// Suppose in.valid, then !in.fire => !in.ready => !(!in_first_beat || que.ready) => !que.ready && in_first_beat
}
// Safe by theorem 3
io.out.bits := acq_bits
// Safe by theorem 1
io.que.bits := in_sizeMinus1
}
class TileLinkFragmenterSink(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val in = Decoupled(new Grant).flip
val out = Decoupled(new Grant)
val que = Decoupled(UInt(width = tlBeatAddrBits)).flip
}
val count_valid = RegInit(Bool(false))
val multi_op = Reg(Bool())
val count_bits = Reg(UInt(width = tlBeatAddrBits))
val last = count_bits === UInt(0)
val in_put = io.in.bits.isBuiltInType(Grant.putAckType)
val in_get = io.in.bits.isBuiltInType(Grant.getDataBeatType)
val deliver = last || in_get
// Accept the input, discarding the non-final put grant
io.in.ready := count_valid && (io.out.ready || !deliver)
// Output the grant whenever we want delivery
io.out.valid := count_valid && io.in.valid && deliver
// Take a new number whenever we deliver the last beat
io.que.ready := !count_valid || (io.in.valid && io.out.ready && last)
// Desired flow control results:
assert (!io.out.fire() || (count_valid && io.in.fire())) // 1. out.fire => in.fire && count_valid
assert (!(io.in.fire() && deliver) || io.out.fire()) // 2. in.fire && deliver => out.fire
assert (!(io.out.fire() && last) || io.que.ready) // 3. out.fire && last => que.ready
assert (!io.que.fire() || (!count_valid || io.out.fire())) // 4. que.fire => !count_valid || (out.fire && last)
// Proofs:
// 1. out.fire => out.ready && (count_valid && in.valid && deliver) => (count_valid && out.ready) && in.valid => in.fire
// 2. in.fire && deliver => in.valid && count_valid && [(out.ready || !deliver) && deliver] =>
// in.valid && count_valid && deliver && out.ready => out.fire
// 3. out.fire && last => out.valid && out.ready && last => in.valid && out.ready && last => que.ready
// 4. que.fire => que.valid && (!count_valid || (in.valid && out.ready && last))
// => !count_valid || (count_valid && in.valid && out.ready && [last => deliver])
// => !count_valid || (out.valid && out.ready && last)
when (io.que.fire()) {
// Theorem 4 makes this safe; we overwrite garbage or last output
count_valid := Bool(true)
count_bits := io.que.bits
multi_op := io.que.bits =/= UInt(0)
} .elsewhen (io.in.fire()) {
count_valid := !last // false => !que.valid
count_bits := count_bits - UInt(1)
// Proof: in.fire && [last => deliver] =2=> out.fire && last =3=> que.ready
// !que.fire && que.ready => !que.valid
}
// Safe by Theorem 1
io.out.bits := io.in.bits
io.out.bits.g_type := Mux(multi_op, Mux(in_get, Grant.getDataBlockType, Grant.putAckType), io.in.bits.g_type)
}
class TileLinkFragmenter(depth: Int = 1)(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val in = new ClientUncachedTileLinkIO().flip
val out = new ClientUncachedTileLinkIO
}
// TL2:
// supportsAcquire = false
// modify all outward managers to supportsMultibeat = true
// assert: all managers must behaveFIFO (not inspect duplicated id field)
val source = Module(new TileLinkFragmenterSource)
val sink = Module(new TileLinkFragmenterSink)
sink.io.que <> Queue(source.io.que, depth)
source.io.in <> io.in.acquire
io.out.acquire <> source.io.out
sink.io.in <> io.out.grant
io.in.grant <> sink.io.out
}
object TileLinkFragmenter {
// Pass the source/client to fragment
def apply(source: ClientUncachedTileLinkIO, depth: Int = 1)(implicit p: Parameters): ClientUncachedTileLinkIO = {
val fragmenter = Module(new TileLinkFragmenter(depth))
fragmenter.io.in <> source
fragmenter.io.out
}
}

View File

@ -0,0 +1,161 @@
package uncore.devices
import Chisel._
import cde.{Parameters, Field}
import junctions._
import uncore.tilelink._
import uncore.util._
import HastiConstants._
class BRAMSlave(depth: Int)(implicit val p: Parameters) extends Module
with HasTileLinkParameters {
val io = new ClientUncachedTileLinkIO().flip
// For TL2:
// supportsAcquire = false
// supportsMultibeat = false
// supportsHint = false
// supportsAtomic = false
// Timing-wise, we assume the input is coming out of registers
// since you probably needed a TileLinkFragmenter infront of us
// Thus, only one pipeline stage: the grant result
val g_valid = RegInit(Bool(false))
val g_bits = Reg(new Grant)
// Just pass the pipeline straight through
io.grant.valid := g_valid
io.grant.bits := g_bits
io.acquire.ready := !g_valid || io.grant.ready
val acq_get = io.acquire.bits.isBuiltInType(Acquire.getType)
val acq_put = io.acquire.bits.isBuiltInType(Acquire.putType)
val acq_addr = Cat(io.acquire.bits.addr_block, io.acquire.bits.addr_beat)
val bram = Mem(depth, Bits(width = tlDataBits))
val ren = acq_get && io.acquire.fire()
val wen = acq_put && io.acquire.fire()
when (io.grant.fire()) {
g_valid := Bool(false)
}
when (io.acquire.fire()) {
g_valid := Bool(true)
g_bits := Grant(
is_builtin_type = Bool(true),
g_type = io.acquire.bits.getBuiltInGrantType(),
client_xact_id = io.acquire.bits.client_xact_id,
manager_xact_id = UInt(0),
addr_beat = io.acquire.bits.addr_beat,
data = UInt(0))
}
when (wen) {
bram.write(acq_addr, io.acquire.bits.data)
assert(io.acquire.bits.wmask().andR, "BRAMSlave: partial write masks not supported")
}
io.grant.bits.data := RegEnable(bram.read(acq_addr), ren)
}
class HastiRAM(depth: Int)(implicit p: Parameters) extends HastiModule()(p) {
val io = new HastiSlaveIO
val wdata = Vec.tabulate(hastiDataBytes)(i => io.hwdata(8*(i+1)-1,8*i))
val waddr = Reg(UInt(width = hastiAddrBits))
val wvalid = Reg(init = Bool(false))
val wsize = Reg(UInt(width = SZ_HSIZE))
val ram = SeqMem(depth, Vec(hastiDataBytes, Bits(width = 8)))
val max_size = log2Ceil(hastiDataBytes)
val wmask_lut = MuxLookup(wsize, SInt(-1, hastiDataBytes).asUInt,
(0 until max_size).map(sz => (UInt(sz) -> UInt((1 << (1 << sz)) - 1))))
val wmask = (wmask_lut << waddr(max_size - 1, 0))(hastiDataBytes - 1, 0)
val is_trans = io.hsel && io.htrans.isOneOf(HTRANS_NONSEQ, HTRANS_SEQ)
val raddr = io.haddr >> UInt(max_size)
val ren = is_trans && !io.hwrite
val bypass = Reg(init = Bool(false))
when (is_trans && io.hwrite) {
waddr := io.haddr
wsize := io.hsize
wvalid := Bool(true)
} .otherwise { wvalid := Bool(false) }
when (ren) { bypass := wvalid && (waddr >> UInt(max_size)) === raddr }
when (wvalid) {
ram.write(waddr >> UInt(max_size), wdata, wmask.toBools)
}
val rdata = ram.read(raddr, ren)
io.hrdata := Cat(rdata.zip(wmask.toBools).zip(wdata).map {
case ((rbyte, wsel), wbyte) => Mux(wsel && bypass, wbyte, rbyte)
}.reverse)
io.hready := Bool(true)
io.hresp := HRESP_OKAY
}
/**
* This RAM is not meant to be particularly performant.
* It just supports the entire range of uncached TileLink operations in the
* simplest way possible.
*/
class TileLinkTestRAM(depth: Int)(implicit val p: Parameters) extends Module
with HasTileLinkParameters {
val io = new ClientUncachedTileLinkIO().flip
val ram = Mem(depth, UInt(width = tlDataBits))
val responding = Reg(init = Bool(false))
val acq = io.acquire.bits
val r_acq = Reg(io.acquire.bits)
val acq_addr = Cat(acq.addr_block, acq.addr_beat)
val r_acq_addr = Cat(r_acq.addr_block, r_acq.addr_beat)
when (io.acquire.fire() && io.acquire.bits.last()) {
r_acq := io.acquire.bits
responding := Bool(true)
}
when (io.grant.fire()) {
val is_getblk = r_acq.isBuiltInType(Acquire.getBlockType)
val last_beat = r_acq.addr_beat === UInt(tlDataBeats - 1)
when (is_getblk && !last_beat) {
r_acq.addr_beat := r_acq.addr_beat + UInt(1)
} .otherwise { responding := Bool(false) }
}
val old_data = ram(acq_addr)
val new_data = acq.data
val r_old_data = RegEnable(old_data, io.acquire.fire())
io.acquire.ready := !responding
io.grant.valid := responding
io.grant.bits := Grant(
is_builtin_type = Bool(true),
g_type = r_acq.getBuiltInGrantType(),
client_xact_id = r_acq.client_xact_id,
manager_xact_id = UInt(0),
addr_beat = r_acq.addr_beat,
data = Mux(r_acq.isAtomic(), r_old_data, ram(r_acq_addr)))
val amo_shift_bits = acq.amo_shift_bytes() << UInt(3)
val amoalu = Module(new AMOALU(amoAluOperandBits, rhsIsAligned = true))
amoalu.io.addr := Cat(acq.addr_block, acq.addr_beat, acq.addr_byte())
amoalu.io.cmd := acq.op_code()
amoalu.io.typ := acq.op_size()
amoalu.io.lhs := old_data >> amo_shift_bits
amoalu.io.rhs := new_data >> amo_shift_bits
val result = Mux(acq.isAtomic(), amoalu.io.out << amo_shift_bits, new_data)
val wmask = FillInterleaved(8, acq.wmask())
when (io.acquire.fire() && acq.hasData()) {
ram(acq_addr) := (old_data & ~wmask) | (result & wmask)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
// See LICENSE for license details.
package uncore.devices
import Chisel._
import Chisel.ImplicitConversions._
import junctions._
import uncore.tilelink._
import cde.Parameters
class GatewayPLICIO extends Bundle {
val valid = Bool(OUTPUT)
val ready = Bool(INPUT)
val complete = Bool(INPUT)
}
class LevelGateway extends Module {
val io = new Bundle {
val interrupt = Bool(INPUT)
val plic = new GatewayPLICIO
}
val inFlight = Reg(init=Bool(false))
when (io.interrupt && io.plic.ready) { inFlight := true }
when (io.plic.complete) { inFlight := false }
io.plic.valid := io.interrupt && !inFlight
}
case class PLICConfig(nHartsIn: Int, supervisor: Boolean, nDevices: Int, nPriorities: Int) {
def contextsPerHart = if (supervisor) 2 else 1
def nHarts = contextsPerHart * nHartsIn
def context(i: Int, mode: Char) = mode match {
case 'M' => i * contextsPerHart
case 'S' => require(supervisor); i * contextsPerHart + 1
}
def claimAddr(i: Int, mode: Char) = hartBase + hartOffset(context(i, mode)) + claimOffset
def threshAddr(i: Int, mode: Char) = hartBase + hartOffset(context(i, mode))
def enableAddr(i: Int, mode: Char) = enableBase + enableOffset(context(i, mode))
def size = hartBase + hartOffset(maxHarts)
def maxDevices = 1023
def maxHarts = 15872
def pendingBase = 0x1000
def enableBase = 0x2000
def hartBase = 0x200000
require(hartBase >= enableBase + enableOffset(maxHarts))
def enableOffset(i: Int) = i * ((maxDevices+7)/8)
def hartOffset(i: Int) = i * 0x1000
def claimOffset = 4
def priorityBytes = 4
require(nDevices > 0 && nDevices <= maxDevices)
require(nHarts > 0 && nHarts <= maxHarts)
require(nPriorities >= 0 && nPriorities <= nDevices)
}
/** Platform-Level Interrupt Controller */
class PLIC(val cfg: PLICConfig)(implicit val p: Parameters) extends Module
with HasTileLinkParameters
with HasAddrMapParameters {
val io = new Bundle {
val devices = Vec(cfg.nDevices, new GatewayPLICIO).flip
val harts = Vec(cfg.nHarts, Bool()).asOutput
val tl = new ClientUncachedTileLinkIO().flip
}
val priority =
if (cfg.nPriorities > 0) Reg(Vec(cfg.nDevices+1, UInt(width=log2Up(cfg.nPriorities+1))))
else Wire(init=Vec.fill(cfg.nDevices+1)(UInt(1)))
val threshold =
if (cfg.nPriorities > 0) Reg(Vec(cfg.nHarts, UInt(width = log2Up(cfg.nPriorities+1))))
else Wire(init=Vec.fill(cfg.nHarts)(UInt(0)))
val pending = Reg(init=Vec.fill(cfg.nDevices+1){Bool(false)})
val enables = Reg(Vec(cfg.nHarts, Vec(cfg.nDevices+1, Bool())))
for ((p, g) <- pending.tail zip io.devices) {
g.ready := !p
g.complete := false
when (g.valid) { p := true }
}
def findMax(x: Seq[UInt]): (UInt, UInt) = {
if (x.length > 1) {
val half = 1 << (log2Ceil(x.length) - 1)
val lMax = findMax(x take half)
val rMax = findMax(x drop half)
val useLeft = lMax._1 >= rMax._1
(Mux(useLeft, lMax._1, rMax._1), Mux(useLeft, lMax._2, UInt(half) + rMax._2))
} else (x.head, UInt(0))
}
val maxDevs = Wire(Vec(cfg.nHarts, UInt(width = log2Up(pending.size))))
for (hart <- 0 until cfg.nHarts) {
val effectivePriority =
for (((p, en), pri) <- (pending zip enables(hart) zip priority).tail)
yield Cat(p && en, pri)
val (maxPri, maxDev) = findMax((UInt(1) << priority(0).getWidth) +: effectivePriority)
maxDevs(hart) := Reg(next = maxDev)
io.harts(hart) := Reg(next = maxPri) > Cat(UInt(1), threshold(hart))
}
val acq = Queue(io.tl.acquire, 1)
val read = acq.fire() && acq.bits.isBuiltInType(Acquire.getType)
val write = acq.fire() && acq.bits.isBuiltInType(Acquire.putType)
assert(!acq.fire() || read || write, "unsupported PLIC operation")
val addr = acq.bits.full_addr()(log2Up(cfg.size)-1,0)
val claimant =
if (cfg.nHarts == 1) UInt(0)
else (addr - cfg.hartBase)(log2Up(cfg.hartOffset(cfg.nHarts))-1,log2Up(cfg.hartOffset(1)))
val hart = Wire(init = claimant)
val myMaxDev = maxDevs(claimant)
val myEnables = enables(hart)
val rdata = Wire(init = UInt(0, tlDataBits))
val masked_wdata = (acq.bits.data & acq.bits.full_wmask()) | (rdata & ~acq.bits.full_wmask())
when (addr >= cfg.hartBase) {
val word =
if (tlDataBytes > cfg.claimOffset) UInt(0)
else addr(log2Up(cfg.claimOffset),log2Up(tlDataBytes))
rdata := Cat(myMaxDev, UInt(0, 8*cfg.priorityBytes-threshold(0).getWidth), threshold(claimant)) >> (word * tlDataBits)
when (read && addr(log2Ceil(cfg.claimOffset))) {
pending(myMaxDev) := false
}
when (write) {
when (if (tlDataBytes > cfg.claimOffset) acq.bits.wmask()(cfg.claimOffset) else addr(log2Ceil(cfg.claimOffset))) {
val dev = (acq.bits.data >> ((8 * cfg.claimOffset) % tlDataBits))(log2Up(pending.size)-1,0)
when (myEnables(dev)) { io.devices(dev-1).complete := true }
}.otherwise {
if (cfg.nPriorities > 0) threshold(claimant) := acq.bits.data
}
}
}.elsewhen (addr >= cfg.enableBase) {
val enableHart =
if (cfg.nHarts > 1) (addr - cfg.enableBase)(log2Up(cfg.enableOffset(cfg.nHarts))-1,log2Up(cfg.enableOffset(1)))
else UInt(0)
hart := enableHart
val word =
if (tlDataBits >= cfg.nHarts) UInt(0)
else addr(log2Up((cfg.nHarts+7)/8)-1,log2Up(tlDataBytes))
for (i <- 0 until cfg.nHarts by tlDataBits) {
when (word === i/tlDataBits) {
rdata := Cat(myEnables.slice(i, i + tlDataBits).reverse)
for (j <- 0 until (tlDataBits min (myEnables.size - i))) {
when (write) { enables(enableHart)(i+j) := masked_wdata(j) }
}
}
}
}.elsewhen (addr >= cfg.pendingBase) {
val word =
if (tlDataBytes >= pending.size) UInt(0)
else addr(log2Up(pending.size)-1,log2Up(tlDataBytes))
rdata := pending.asUInt >> (word * tlDataBits)
}.otherwise {
val regsPerBeat = tlDataBytes >> log2Up(cfg.priorityBytes)
val word =
if (regsPerBeat >= priority.size) UInt(0)
else addr(log2Up(priority.size*cfg.priorityBytes)-1,log2Up(tlDataBytes))
for (i <- 0 until priority.size by regsPerBeat) {
when (word === i/regsPerBeat) {
rdata := Cat(priority.slice(i, i + regsPerBeat).map(p => Cat(UInt(0, 8*cfg.priorityBytes-p.getWidth), p)).reverse)
for (j <- 0 until (regsPerBeat min (priority.size - i))) {
if (cfg.nPriorities > 0) when (write) { priority(i+j) := masked_wdata >> (j * 8 * cfg.priorityBytes) }
}
}
}
}
priority(0) := 0
pending(0) := false
for (e <- enables)
e(0) := false
io.tl.grant.valid := acq.valid
acq.ready := io.tl.grant.ready
io.tl.grant.bits := Grant(
is_builtin_type = Bool(true),
g_type = acq.bits.getBuiltInGrantType(),
client_xact_id = acq.bits.client_xact_id,
manager_xact_id = UInt(0),
addr_beat = UInt(0),
data = rdata)
}

View File

@ -0,0 +1,127 @@
// See LICENSE for license details.
package uncore.devices
import Chisel._
import Chisel.ImplicitConversions._
import junctions._
import junctions.NastiConstants._
import uncore.tilelink._
import cde.{Parameters, Field}
/** Number of tiles */
case object NTiles extends Field[Int]
class PRCIInterrupts extends Bundle {
val meip = Bool()
val seip = Bool()
val debug = Bool()
}
class PRCITileIO(implicit p: Parameters) extends Bundle {
val reset = Bool(OUTPUT)
val id = UInt(OUTPUT, log2Up(p(NTiles)))
val interrupts = new PRCIInterrupts {
val mtip = Bool()
val msip = Bool()
}.asOutput
override def cloneType: this.type = new PRCITileIO().asInstanceOf[this.type]
}
object PRCI {
def msip(hart: Int) = hart * msipBytes
def timecmp(hart: Int) = 0x4000 + hart * timecmpBytes
def time = 0xbff8
def msipBytes = 4
def timecmpBytes = 8
def size = 0xc000
}
/** Power, Reset, Clock, Interrupt */
class PRCI(implicit val p: Parameters) extends Module
with HasTileLinkParameters
with HasAddrMapParameters {
val io = new Bundle {
val interrupts = Vec(p(NTiles), new PRCIInterrupts).asInput
val tl = new ClientUncachedTileLinkIO().flip
val tiles = Vec(p(NTiles), new PRCITileIO)
val rtcTick = Bool(INPUT)
}
val timeWidth = 64
val timecmp = Reg(Vec(p(NTiles), UInt(width = timeWidth)))
val time = Reg(init=UInt(0, timeWidth))
when (io.rtcTick) { time := time + UInt(1) }
val ipi = Reg(init=Vec.fill(p(NTiles))(UInt(0, 32)))
val acq = Queue(io.tl.acquire, 1)
val addr = acq.bits.full_addr()(log2Ceil(PRCI.size)-1,0)
val read = acq.bits.isBuiltInType(Acquire.getType)
val rdata = Wire(init=UInt(0))
io.tl.grant.valid := acq.valid
acq.ready := io.tl.grant.ready
io.tl.grant.bits := Grant(
is_builtin_type = Bool(true),
g_type = acq.bits.getBuiltInGrantType(),
client_xact_id = acq.bits.client_xact_id,
manager_xact_id = UInt(0),
addr_beat = UInt(0),
data = rdata)
when (addr(log2Floor(PRCI.time))) {
require(log2Floor(PRCI.timecmp(p(NTiles)-1)) < log2Floor(PRCI.time))
rdata := load(Vec(time + UInt(0)), acq.bits)
}.elsewhen (addr >= PRCI.timecmp(0)) {
rdata := store(timecmp, acq.bits)
}.otherwise {
rdata := store(ipi, acq.bits) & Fill(tlDataBits/32, UInt(1, 32))
}
for ((tile, i) <- io.tiles zipWithIndex) {
tile.interrupts := io.interrupts(i)
tile.interrupts.msip := ipi(i)(0)
tile.interrupts.mtip := time >= timecmp(i)
tile.id := UInt(i)
}
// TODO generalize these to help other TL slaves
def load(v: Vec[UInt], acq: Acquire): UInt = {
val w = v.head.getWidth
val a = acq.full_addr()
require(isPow2(w) && w >= 8)
if (w > tlDataBits) {
(v(a(log2Up(w/8*v.size)-1,log2Up(w/8))) >> a(log2Up(w/8)-1,log2Up(tlDataBytes)))(tlDataBits-1,0)
} else {
val row = for (i <- 0 until v.size by tlDataBits/w)
yield Cat(v.slice(i, i + tlDataBits/w).reverse)
if (row.size == 1) row.head
else Vec(row)(a(log2Up(w/8*v.size)-1,log2Up(tlDataBytes)))
}
}
def store(v: Vec[UInt], acq: Acquire): UInt = {
val w = v.head.getWidth
require(isPow2(w) && w >= 8)
val a = acq.full_addr()
val rdata = load(v, acq)
val wdata = (acq.data & acq.full_wmask()) | (rdata & ~acq.full_wmask())
if (w <= tlDataBits) {
val word =
if (tlDataBits/w >= v.size) UInt(0)
else a(log2Up(w/8*v.size)-1,log2Up(tlDataBytes))
for (i <- 0 until v.size) {
when (acq.isBuiltInType(Acquire.putType) && word === i/(tlDataBits/w)) {
val base = i % (tlDataBits/w)
v(i) := wdata >> (w * (i % (tlDataBits/w)))
}
}
} else {
val i = a(log2Up(w/8*v.size)-1,log2Up(w/8))
val mask = FillInterleaved(tlDataBits, UIntToOH(a(log2Up(w/8)-1,log2Up(tlDataBytes))))
v(i) := (wdata & mask) | (v(i) & ~mask)
}
rdata
}
}

View File

@ -0,0 +1,66 @@
package uncore.devices
import Chisel._
import junctions._
import uncore.tilelink._
import uncore.util._
import cde.{Parameters, Field}
class ROMSlave(contents: Seq[Byte])(implicit val p: Parameters) extends Module
with HasTileLinkParameters
with HasAddrMapParameters {
val io = new ClientUncachedTileLinkIO().flip
val acq = Queue(io.acquire, 1)
val single_beat = acq.bits.isBuiltInType(Acquire.getType)
val multi_beat = acq.bits.isBuiltInType(Acquire.getBlockType)
assert(!acq.valid || single_beat || multi_beat, "unsupported ROMSlave operation")
val addr_beat = Reg(UInt())
when (io.grant.fire()) { addr_beat := addr_beat + UInt(1) }
when (io.acquire.fire()) { addr_beat := io.acquire.bits.addr_beat }
val byteWidth = tlDataBits / 8
val rows = (contents.size + byteWidth - 1)/byteWidth
val rom = Vec.tabulate(rows) { i =>
val slice = contents.slice(i*byteWidth, (i+1)*byteWidth)
UInt(slice.foldRight(BigInt(0)) { case (x,y) => (y << 8) + (x.toInt & 0xFF) }, byteWidth*8)
}
val raddr = Cat(acq.bits.addr_block, addr_beat)
val rdata = rom(if (rows == 1) UInt(0) else raddr(log2Up(rom.size)-1,0))
val last = !multi_beat || addr_beat === UInt(tlDataBeats-1)
io.grant.valid := acq.valid
acq.ready := io.grant.ready && last
io.grant.bits := Grant(
is_builtin_type = Bool(true),
g_type = acq.bits.getBuiltInGrantType(),
client_xact_id = acq.bits.client_xact_id,
manager_xact_id = UInt(0),
addr_beat = addr_beat,
data = rdata)
}
class NastiROM(contents: Seq[Byte])(implicit p: Parameters) extends Module {
val io = new NastiIO().flip
val ar = Queue(io.ar, 1)
// This assumes ROMs are in read-only parts of the address map.
// Reuse b_queue code from NastiErrorSlave if this assumption is bad.
when (ar.valid) { assert(ar.bits.len === UInt(0), "Can't burst-read from NastiROM") }
assert(!(io.aw.valid || io.w.valid), "Can't write to NastiROM")
io.aw.ready := Bool(false)
io.w.ready := Bool(false)
io.b.valid := Bool(false)
val byteWidth = io.r.bits.nastiXDataBits / 8
val rows = (contents.size + byteWidth - 1)/byteWidth
val rom = Vec.tabulate(rows) { i =>
val slice = contents.slice(i*byteWidth, (i+1)*byteWidth)
UInt(slice.foldRight(BigInt(0)) { case (x,y) => (y << 8) + (x.toInt & 0xFF) }, byteWidth*8)
}
val rdata = rom(if (rows == 1) UInt(0) else ar.bits.addr(log2Up(contents.size)-1,log2Up(byteWidth)))
io.r <> ar
io.r.bits := NastiReadDataChannel(ar.bits.id, rdata)
}

View File

@ -0,0 +1,196 @@
package uncore.tilelink
import Chisel._
import junctions._
import cde.{Parameters, Field}
/** Utility functions for constructing TileLinkIO arbiters */
trait TileLinkArbiterLike extends HasTileLinkParameters {
// Some shorthand type variables
type ManagerSourcedWithId = ManagerToClientChannel with HasClientTransactionId
type ClientSourcedWithId = ClientToManagerChannel with HasClientTransactionId
type ClientSourcedWithIdAndData = ClientToManagerChannel with HasClientTransactionId with HasTileLinkData
val arbN: Int // The number of ports on the client side
// These abstract funcs are filled in depending on whether the arbiter mucks with the
// outgoing client ids to track sourcing and then needs to revert them on the way back
def clientSourcedClientXactId(in: ClientSourcedWithId, id: Int): Bits
def managerSourcedClientXactId(in: ManagerSourcedWithId): Bits
def arbIdx(in: ManagerSourcedWithId): UInt
// The following functions are all wiring helpers for each of the different types of TileLink channels
def hookupClientSource[M <: ClientSourcedWithIdAndData](
clts: Seq[DecoupledIO[LogicalNetworkIO[M]]],
mngr: DecoupledIO[LogicalNetworkIO[M]]) {
def hasData(m: LogicalNetworkIO[M]) = m.payload.hasMultibeatData()
val arb = Module(new LockingRRArbiter(mngr.bits, arbN, tlDataBeats, Some(hasData _)))
clts.zipWithIndex.zip(arb.io.in).map{ case ((req, id), arb) => {
arb.valid := req.valid
arb.bits := req.bits
arb.bits.payload.client_xact_id := clientSourcedClientXactId(req.bits.payload, id)
req.ready := arb.ready
}}
mngr <> arb.io.out
}
def hookupClientSourceHeaderless[M <: ClientSourcedWithIdAndData](
clts: Seq[DecoupledIO[M]],
mngr: DecoupledIO[M]) {
def hasData(m: M) = m.hasMultibeatData()
val arb = Module(new LockingRRArbiter(mngr.bits, arbN, tlDataBeats, Some(hasData _)))
clts.zipWithIndex.zip(arb.io.in).map{ case ((req, id), arb) => {
arb.valid := req.valid
arb.bits := req.bits
arb.bits.client_xact_id := clientSourcedClientXactId(req.bits, id)
req.ready := arb.ready
}}
mngr <> arb.io.out
}
def hookupManagerSourceWithHeader[M <: ManagerToClientChannel](
clts: Seq[DecoupledIO[LogicalNetworkIO[M]]],
mngr: DecoupledIO[LogicalNetworkIO[M]]) {
mngr.ready := Bool(false)
for (i <- 0 until arbN) {
clts(i).valid := Bool(false)
when (mngr.bits.header.dst === UInt(i)) {
clts(i).valid := mngr.valid
mngr.ready := clts(i).ready
}
clts(i).bits := mngr.bits
}
}
def hookupManagerSourceWithId[M <: ManagerSourcedWithId](
clts: Seq[DecoupledIO[LogicalNetworkIO[M]]],
mngr: DecoupledIO[LogicalNetworkIO[M]]) {
mngr.ready := Bool(false)
for (i <- 0 until arbN) {
clts(i).valid := Bool(false)
when (arbIdx(mngr.bits.payload) === UInt(i)) {
clts(i).valid := mngr.valid
mngr.ready := clts(i).ready
}
clts(i).bits := mngr.bits
clts(i).bits.payload.client_xact_id := managerSourcedClientXactId(mngr.bits.payload)
}
}
def hookupManagerSourceHeaderlessWithId[M <: ManagerSourcedWithId](
clts: Seq[DecoupledIO[M]],
mngr: DecoupledIO[M]) {
mngr.ready := Bool(false)
for (i <- 0 until arbN) {
clts(i).valid := Bool(false)
when (arbIdx(mngr.bits) === UInt(i)) {
clts(i).valid := mngr.valid
mngr.ready := clts(i).ready
}
clts(i).bits := mngr.bits
clts(i).bits.client_xact_id := managerSourcedClientXactId(mngr.bits)
}
}
def hookupManagerSourceBroadcast[M <: Data](clts: Seq[DecoupledIO[M]], mngr: DecoupledIO[M]) {
clts.map{ _.valid := mngr.valid }
clts.map{ _.bits := mngr.bits }
mngr.ready := clts.map(_.ready).reduce(_&&_)
}
def hookupFinish[M <: LogicalNetworkIO[Finish]]( clts: Seq[DecoupledIO[M]], mngr: DecoupledIO[M]) {
val arb = Module(new RRArbiter(mngr.bits, arbN))
arb.io.in <> clts
mngr <> arb.io.out
}
}
/** Abstract base case for any Arbiters that have UncachedTileLinkIOs */
abstract class UncachedTileLinkIOArbiter(val arbN: Int)(implicit val p: Parameters) extends Module
with TileLinkArbiterLike {
val io = new Bundle {
val in = Vec(arbN, new UncachedTileLinkIO).flip
val out = new UncachedTileLinkIO
}
hookupClientSource(io.in.map(_.acquire), io.out.acquire)
hookupFinish(io.in.map(_.finish), io.out.finish)
hookupManagerSourceWithId(io.in.map(_.grant), io.out.grant)
}
/** Abstract base case for any Arbiters that have cached TileLinkIOs */
abstract class TileLinkIOArbiter(val arbN: Int)(implicit val p: Parameters) extends Module
with TileLinkArbiterLike {
val io = new Bundle {
val in = Vec(arbN, new TileLinkIO).flip
val out = new TileLinkIO
}
hookupClientSource(io.in.map(_.acquire), io.out.acquire)
hookupClientSource(io.in.map(_.release), io.out.release)
hookupFinish(io.in.map(_.finish), io.out.finish)
hookupManagerSourceBroadcast(io.in.map(_.probe), io.out.probe)
hookupManagerSourceWithId(io.in.map(_.grant), io.out.grant)
}
/** Appends the port index of the arbiter to the client_xact_id */
trait AppendsArbiterId extends TileLinkArbiterLike {
def clientSourcedClientXactId(in: ClientSourcedWithId, id: Int) =
Cat(in.client_xact_id, UInt(id, log2Up(arbN)))
def managerSourcedClientXactId(in: ManagerSourcedWithId) = {
/* This shouldn't be necessary, but Chisel3 doesn't emit correct Verilog
* when right shifting by too many bits. See
* https://github.com/ucb-bar/firrtl/issues/69 */
if (in.client_xact_id.getWidth > log2Up(arbN))
in.client_xact_id >> log2Up(arbN)
else
UInt(0)
}
def arbIdx(in: ManagerSourcedWithId) = in.client_xact_id(log2Up(arbN)-1,0)
}
/** Uses the client_xact_id as is (assumes it has been set to port index) */
trait PassesId extends TileLinkArbiterLike {
def clientSourcedClientXactId(in: ClientSourcedWithId, id: Int) = in.client_xact_id
def managerSourcedClientXactId(in: ManagerSourcedWithId) = in.client_xact_id
def arbIdx(in: ManagerSourcedWithId) = in.client_xact_id
}
/** Overwrites some default client_xact_id with the port idx */
trait UsesNewId extends TileLinkArbiterLike {
def clientSourcedClientXactId(in: ClientSourcedWithId, id: Int) = UInt(id, log2Up(arbN))
def managerSourcedClientXactId(in: ManagerSourcedWithId) = UInt(0)
def arbIdx(in: ManagerSourcedWithId) = in.client_xact_id
}
// Now we can mix-in thevarious id-generation traits to make concrete arbiter classes
class UncachedTileLinkIOArbiterThatAppendsArbiterId(val n: Int)(implicit p: Parameters) extends UncachedTileLinkIOArbiter(n)(p) with AppendsArbiterId
class UncachedTileLinkIOArbiterThatPassesId(val n: Int)(implicit p: Parameters) extends UncachedTileLinkIOArbiter(n)(p) with PassesId
class UncachedTileLinkIOArbiterThatUsesNewId(val n: Int)(implicit p: Parameters) extends UncachedTileLinkIOArbiter(n)(p) with UsesNewId
class TileLinkIOArbiterThatAppendsArbiterId(val n: Int)(implicit p: Parameters) extends TileLinkIOArbiter(n)(p) with AppendsArbiterId
class TileLinkIOArbiterThatPassesId(val n: Int)(implicit p: Parameters) extends TileLinkIOArbiter(n)(p) with PassesId
class TileLinkIOArbiterThatUsesNewId(val n: Int)(implicit p: Parameters) extends TileLinkIOArbiter(n)(p) with UsesNewId
/** Concrete uncached client-side arbiter that appends the arbiter's port id to client_xact_id */
class ClientUncachedTileLinkIOArbiter(val arbN: Int)(implicit val p: Parameters) extends Module with TileLinkArbiterLike with AppendsArbiterId {
val io = new Bundle {
val in = Vec(arbN, new ClientUncachedTileLinkIO).flip
val out = new ClientUncachedTileLinkIO
}
if (arbN > 1) {
hookupClientSourceHeaderless(io.in.map(_.acquire), io.out.acquire)
hookupManagerSourceHeaderlessWithId(io.in.map(_.grant), io.out.grant)
} else { io.out <> io.in.head }
}
/** Concrete client-side arbiter that appends the arbiter's port id to client_xact_id */
class ClientTileLinkIOArbiter(val arbN: Int)(implicit val p: Parameters) extends Module with TileLinkArbiterLike with AppendsArbiterId {
val io = new Bundle {
val in = Vec(arbN, new ClientTileLinkIO).flip
val out = new ClientTileLinkIO
}
if (arbN > 1) {
hookupClientSourceHeaderless(io.in.map(_.acquire), io.out.acquire)
hookupClientSourceHeaderless(io.in.map(_.release), io.out.release)
hookupManagerSourceBroadcast(io.in.map(_.probe), io.out.probe)
hookupManagerSourceHeaderlessWithId(io.in.map(_.grant), io.out.grant)
} else { io.out <> io.in.head }
}

View File

@ -0,0 +1,971 @@
// See LICENSE for license details.
package uncore.tilelink
import Chisel._
import junctions._
import uncore.coherence.CoherencePolicy
import uncore.util._
import scala.math.max
import uncore.constants._
import cde.{Parameters, Field}
case object CacheBlockOffsetBits extends Field[Int]
case object AmoAluOperandBits extends Field[Int]
case object TLId extends Field[String]
case class TLKey(id: String) extends Field[TileLinkParameters]
/** Parameters exposed to the top-level design, set based on
* external requirements or design space exploration
*
* Coherency policy used to define custom mesage types
* Number of manager agents
* Number of client agents that cache data and use custom [[uncore.Acquire]] types
* Number of client agents that do not cache data and use built-in [[uncore.Acquire]] types
* Maximum number of unique outstanding transactions per client
* Maximum number of clients multiplexed onto a single port
* Maximum number of unique outstanding transactions per manager
* Width of cache block addresses
* Total amount of data per cache block
* Number of data beats per cache block
**/
case class TileLinkParameters(
coherencePolicy: CoherencePolicy,
nManagers: Int,
nCachingClients: Int,
nCachelessClients: Int,
maxClientXacts: Int,
maxClientsPerPort: Int,
maxManagerXacts: Int,
dataBits: Int,
dataBeats: Int = 4,
overrideDataBitsPerBeat: Option[Int] = None
) {
val nClients = nCachingClients + nCachelessClients
val writeMaskBits: Int = ((dataBits / dataBeats) - 1) / 8 + 1
val dataBitsPerBeat: Int = overrideDataBitsPerBeat.getOrElse(dataBits / dataBeats)
}
/** Utility trait for building Modules and Bundles that use TileLink parameters */
trait HasTileLinkParameters {
implicit val p: Parameters
val tlExternal = p(TLKey(p(TLId)))
val tlCoh = tlExternal.coherencePolicy
val tlNManagers = tlExternal.nManagers
val tlNCachingClients = tlExternal.nCachingClients
val tlNCachelessClients = tlExternal.nCachelessClients
val tlNClients = tlExternal.nClients
val tlClientIdBits = log2Up(tlNClients)
val tlManagerIdBits = log2Up(tlNManagers)
val tlMaxClientXacts = tlExternal.maxClientXacts
val tlMaxClientsPerPort = tlExternal.maxClientsPerPort
val tlMaxManagerXacts = tlExternal.maxManagerXacts
val tlClientXactIdBits = log2Up(tlMaxClientXacts*tlMaxClientsPerPort)
val tlManagerXactIdBits = log2Up(tlMaxManagerXacts)
val tlBlockAddrBits = p(PAddrBits) - p(CacheBlockOffsetBits)
val tlDataBeats = tlExternal.dataBeats
val tlDataBits = tlExternal.dataBitsPerBeat
val tlDataBytes = tlDataBits/8
val tlWriteMaskBits = tlExternal.writeMaskBits
val tlBeatAddrBits = log2Up(tlDataBeats)
val tlByteAddrBits = log2Up(tlWriteMaskBits)
val tlMemoryOpcodeBits = M_SZ
val tlMemoryOperandSizeBits = log2Ceil(log2Ceil(tlWriteMaskBits) + 1)
val tlAcquireTypeBits = max(log2Up(Acquire.nBuiltInTypes),
tlCoh.acquireTypeWidth)
val tlAcquireUnionBits = max(tlWriteMaskBits,
(tlByteAddrBits +
tlMemoryOperandSizeBits +
tlMemoryOpcodeBits)) + 1
val tlGrantTypeBits = max(log2Up(Grant.nBuiltInTypes),
tlCoh.grantTypeWidth) + 1
/** Whether the underlying physical network preserved point-to-point ordering of messages */
val tlNetworkPreservesPointToPointOrdering = false
val tlNetworkDoesNotInterleaveBeats = true
val amoAluOperandBits = p(AmoAluOperandBits)
val amoAluOperandBytes = amoAluOperandBits/8
}
abstract class TLModule(implicit val p: Parameters) extends Module
with HasTileLinkParameters
abstract class TLBundle(implicit val p: Parameters) extends junctions.ParameterizedBundle()(p)
with HasTileLinkParameters
/** Base trait for all TileLink channels */
abstract class TileLinkChannel(implicit p: Parameters) extends TLBundle()(p) {
def hasData(dummy: Int = 0): Bool
def hasMultibeatData(dummy: Int = 0): Bool
}
/** Directionality of message channel. Used to hook up logical network ports to physical network ports */
abstract class ClientToManagerChannel(implicit p: Parameters) extends TileLinkChannel()(p)
/** Directionality of message channel. Used to hook up logical network ports to physical network ports */
abstract class ManagerToClientChannel(implicit p: Parameters) extends TileLinkChannel()(p)
/** Directionality of message channel. Used to hook up logical network ports to physical network ports */
abstract class ClientToClientChannel(implicit p: Parameters) extends TileLinkChannel()(p) // Unused for now
/** Common signals that are used in multiple channels.
* These traits are useful for type parameterizing bundle wiring functions.
*/
/** Address of a cache block. */
trait HasCacheBlockAddress extends HasTileLinkParameters {
val addr_block = UInt(width = tlBlockAddrBits)
def conflicts(that: HasCacheBlockAddress) = this.addr_block === that.addr_block
def conflicts(addr: UInt) = this.addr_block === addr
}
/** Sub-block address or beat id of multi-beat data */
trait HasTileLinkBeatId extends HasTileLinkParameters {
val addr_beat = UInt(width = tlBeatAddrBits)
}
/* Client-side transaction id. Usually Miss Status Handling Register File index */
trait HasClientTransactionId extends HasTileLinkParameters {
val client_xact_id = Bits(width = tlClientXactIdBits)
}
/** Manager-side transaction id. Usually Transaction Status Handling Register File index. */
trait HasManagerTransactionId extends HasTileLinkParameters {
val manager_xact_id = Bits(width = tlManagerXactIdBits)
}
/** A single beat of cache block data */
trait HasTileLinkData extends HasTileLinkBeatId {
val data = UInt(width = tlDataBits)
def hasData(dummy: Int = 0): Bool
def hasMultibeatData(dummy: Int = 0): Bool
def first(dummy: Int = 0): Bool = !hasMultibeatData() || addr_beat === UInt(0)
def last(dummy: Int = 0): Bool = !hasMultibeatData() || addr_beat === UInt(tlDataBeats-1)
}
/** An entire cache block of data */
trait HasTileLinkBlock extends HasTileLinkParameters {
val data_buffer = Vec(tlDataBeats, UInt(width = tlDataBits))
val wmask_buffer = Vec(tlDataBeats, UInt(width = tlWriteMaskBits))
}
/** The id of a client source or destination. Used in managers. */
trait HasClientId extends HasTileLinkParameters {
val client_id = UInt(width = tlClientIdBits)
}
trait HasManagerId extends HasTileLinkParameters {
val manager_id = UInt(width = tlManagerIdBits)
}
trait HasAcquireUnion extends HasTileLinkParameters {
val union = Bits(width = tlAcquireUnionBits)
// Utility funcs for accessing subblock union:
def isBuiltInType(t: UInt): Bool
val opCodeOff = 1
val opSizeOff = tlMemoryOpcodeBits + opCodeOff
val addrByteOff = tlMemoryOperandSizeBits + opSizeOff
val addrByteMSB = tlByteAddrBits + addrByteOff
/** Hint whether to allocate the block in any interveneing caches */
def allocate(dummy: Int = 0) = union(0)
/** Op code for [[uncore.PutAtomic]] operations */
def op_code(dummy: Int = 0) = Mux(
isBuiltInType(Acquire.putType) || isBuiltInType(Acquire.putBlockType),
M_XWR, union(opSizeOff-1, opCodeOff))
/** Operand size for [[uncore.PutAtomic]] */
def op_size(dummy: Int = 0) = union(addrByteOff-1, opSizeOff)
/** Byte address for [[uncore.PutAtomic]] operand */
def addr_byte(dummy: Int = 0) = union(addrByteMSB-1, addrByteOff)
def amo_offset(dummy: Int = 0) =
if (tlByteAddrBits > log2Up(amoAluOperandBytes)) addr_byte()(tlByteAddrBits-1, log2Up(amoAluOperandBytes))
else UInt(0)
/** Bit offset of [[uncore.PutAtomic]] operand */
def amo_shift_bytes(dummy: Int = 0) = UInt(amoAluOperandBytes)*amo_offset()
/** Write mask for [[uncore.Put]], [[uncore.PutBlock]], [[uncore.PutAtomic]] */
def wmask(dummy: Int = 0): UInt = {
val is_amo = isBuiltInType(Acquire.putAtomicType)
val amo_mask = if (tlByteAddrBits > log2Up(amoAluOperandBytes))
FillInterleaved(amoAluOperandBytes, UIntToOH(amo_offset()))
else Acquire.fullWriteMask
val is_put = isBuiltInType(Acquire.putBlockType) || isBuiltInType(Acquire.putType)
val put_mask = union(tlWriteMaskBits, 1)
Mux(is_amo, amo_mask, Mux(is_put, put_mask, UInt(0)))
}
/** Full, beat-sized writemask */
def full_wmask(dummy: Int = 0) = FillInterleaved(8, wmask())
/** Is this message a built-in read message */
def hasPartialWritemask(dummy: Int = 0): Bool = wmask() =/= Acquire.fullWriteMask
}
trait HasAcquireType extends HasTileLinkParameters {
val is_builtin_type = Bool()
val a_type = UInt(width = tlAcquireTypeBits)
/** Message type equality */
def is(t: UInt) = a_type === t //TODO: make this more opaque; def ===?
/** Is this message a built-in or custom type */
def isBuiltInType(dummy: Int = 0): Bool = is_builtin_type
/** Is this message a particular built-in type */
def isBuiltInType(t: UInt): Bool = is_builtin_type && a_type === t
/** Does this message refer to subblock operands using info in the Acquire.union subbundle */
def isSubBlockType(dummy: Int = 0): Bool = isBuiltInType() && a_type.isOneOf(Acquire.typesOnSubBlocks)
/** Is this message a built-in prefetch message */
def isPrefetch(dummy: Int = 0): Bool = isBuiltInType() &&
(is(Acquire.getPrefetchType) || is(Acquire.putPrefetchType))
/** Is this message a built-in atomic message */
def isAtomic(dummy: Int = 0): Bool = isBuiltInType() && is(Acquire.putAtomicType)
/** Is this message a built-in read message */
def isGet(dummy: Int = 0): Bool = isBuiltInType() && (is(Acquire.getType) || is(Acquire.getBlockType))
/** Does this message contain data? Assumes that no custom message types have data. */
def hasData(dummy: Int = 0): Bool = isBuiltInType() && a_type.isOneOf(Acquire.typesWithData)
/** Does this message contain multiple beats of data? Assumes that no custom message types have data. */
def hasMultibeatData(dummy: Int = 0): Bool = Bool(tlDataBeats > 1) && isBuiltInType() &&
a_type.isOneOf(Acquire.typesWithMultibeatData)
/** Mapping between each built-in Acquire type and a built-in Grant type. */
def getBuiltInGrantType(dummy: Int = 0): UInt = Acquire.getBuiltInGrantType(this.a_type)
}
trait HasProbeType extends HasTileLinkParameters {
val p_type = UInt(width = tlCoh.probeTypeWidth)
def is(t: UInt) = p_type === t
def hasData(dummy: Int = 0) = Bool(false)
def hasMultibeatData(dummy: Int = 0) = Bool(false)
}
trait MightBeVoluntary {
def isVoluntary(dummy: Int = 0): Bool
}
trait HasReleaseType extends HasTileLinkParameters with MightBeVoluntary {
val voluntary = Bool()
val r_type = UInt(width = tlCoh.releaseTypeWidth)
def is(t: UInt) = r_type === t
def hasData(dummy: Int = 0) = r_type.isOneOf(tlCoh.releaseTypesWithData)
def hasMultibeatData(dummy: Int = 0) = Bool(tlDataBeats > 1) &&
r_type.isOneOf(tlCoh.releaseTypesWithData)
def isVoluntary(dummy: Int = 0) = voluntary
def requiresAck(dummy: Int = 0) = !Bool(tlNetworkPreservesPointToPointOrdering)
}
trait HasGrantType extends HasTileLinkParameters with MightBeVoluntary {
val is_builtin_type = Bool()
val g_type = UInt(width = tlGrantTypeBits)
// Helper funcs
def isBuiltInType(dummy: Int = 0): Bool = is_builtin_type
def isBuiltInType(t: UInt): Bool = is_builtin_type && g_type === t
def is(t: UInt):Bool = g_type === t
def hasData(dummy: Int = 0): Bool = Mux(isBuiltInType(),
g_type.isOneOf(Grant.typesWithData),
g_type.isOneOf(tlCoh.grantTypesWithData))
def hasMultibeatData(dummy: Int = 0): Bool =
Bool(tlDataBeats > 1) && Mux(isBuiltInType(),
g_type.isOneOf(Grant.typesWithMultibeatData),
g_type.isOneOf(tlCoh.grantTypesWithData))
def isVoluntary(dummy: Int = 0): Bool = isBuiltInType() && (g_type === Grant.voluntaryAckType)
def requiresAck(dummy: Int = 0): Bool = !Bool(tlNetworkPreservesPointToPointOrdering) && !isVoluntary()
}
/** TileLink channel bundle definitions */
/** The Acquire channel is used to intiate coherence protocol transactions in
* order to gain access to a cache block's data with certain permissions
* enabled. Messages sent over this channel may be custom types defined by
* a [[uncore.CoherencePolicy]] for cached data accesse or may be built-in types
* used for uncached data accesses. Acquires may contain data for Put or
* PutAtomic built-in types. After sending an Acquire, clients must
* wait for a manager to send them a [[uncore.Grant]] message in response.
*/
class AcquireMetadata(implicit p: Parameters) extends ClientToManagerChannel
with HasCacheBlockAddress
with HasClientTransactionId
with HasTileLinkBeatId
with HasAcquireType
with HasAcquireUnion {
/** Complete physical address for block, beat or operand */
def full_addr(dummy: Int = 0) =
Cat(this.addr_block, this.addr_beat,
Mux(isBuiltInType() && this.a_type.isOneOf(Acquire.typesWithAddrByte),
this.addr_byte(), UInt(0, tlByteAddrBits)))
}
/** [[uncore.AcquireMetadata]] with an extra field containing the data beat */
class Acquire(implicit p: Parameters) extends AcquireMetadata
with HasTileLinkData
/** [[uncore.AcquireMetadata]] with an extra field containing the entire cache block */
class BufferedAcquire(implicit p: Parameters) extends AcquireMetadata
with HasTileLinkBlock
/** [[uncore.Acquire]] with an extra field stating its source id */
class AcquireFromSrc(implicit p: Parameters) extends Acquire
with HasClientId
/** [[uncore.BufferedAcquire]] with an extra field stating its source id */
class BufferedAcquireFromSrc(implicit p: Parameters) extends BufferedAcquire
with HasClientId
/** Used to track metadata for transactions where multiple secondary misses have been merged
* and handled by a single transaction tracker.
*/
class SecondaryMissInfo(implicit p: Parameters) extends TLBundle
with HasClientTransactionId
with HasTileLinkBeatId
with HasClientId
with HasAcquireType
/** Contains definitions of the the built-in Acquire types and a factory
* for [[uncore.Acquire]]
*
* In general you should avoid using this factory directly and use
* [[uncore.ClientMetadata.makeAcquire]] for custom cached Acquires and
* [[uncore.Get]], [[uncore.Put]], etc. for built-in uncached Acquires.
*
* @param is_builtin_type built-in or custom type message?
* @param a_type built-in type enum or custom type enum
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (which beat)
* @param data data being put outwards
* @param union additional fields used for uncached types
*/
object Acquire {
val nBuiltInTypes = 7
//TODO: Use Enum
def getType = UInt("b000") // Get a single beat of data
def getBlockType = UInt("b001") // Get a whole block of data
def putType = UInt("b010") // Put a single beat of data
def putBlockType = UInt("b011") // Put a whole block of data
def putAtomicType = UInt("b100") // Perform an atomic memory op
def getPrefetchType = UInt("b101") // Prefetch a whole block of data
def putPrefetchType = UInt("b110") // Prefetch a whole block of data, with intent to write
def typesWithData = Vec(putType, putBlockType, putAtomicType)
def typesWithMultibeatData = Vec(putBlockType)
def typesOnSubBlocks = Vec(putType, getType, putAtomicType)
def typesWithAddrByte = Vec(getType, putAtomicType)
/** Mapping between each built-in Acquire type and a built-in Grant type. */
def getBuiltInGrantType(a_type: UInt): UInt = {
MuxLookup(a_type, Grant.putAckType, Array(
Acquire.getType -> Grant.getDataBeatType,
Acquire.getBlockType -> Grant.getDataBlockType,
Acquire.putType -> Grant.putAckType,
Acquire.putBlockType -> Grant.putAckType,
Acquire.putAtomicType -> Grant.getDataBeatType,
Acquire.getPrefetchType -> Grant.prefetchAckType,
Acquire.putPrefetchType -> Grant.prefetchAckType))
}
def makeUnion(
a_type: UInt,
addr_byte: UInt,
operand_size: UInt,
opcode: UInt,
wmask: UInt,
alloc: Bool)
(implicit p: Parameters): UInt = {
val tlExternal = p(TLKey(p(TLId)))
val tlWriteMaskBits = tlExternal.writeMaskBits
val tlByteAddrBits = log2Up(tlWriteMaskBits)
val tlMemoryOperandSizeBits = log2Ceil(log2Ceil(tlWriteMaskBits) + 1)
// These had better be the right size when we cat them together!
val my_addr_byte = (UInt(0, tlByteAddrBits) | addr_byte)(tlByteAddrBits-1, 0)
val my_operand_size = (UInt(0, tlMemoryOperandSizeBits) | operand_size)(tlMemoryOperandSizeBits-1, 0)
val my_opcode = (UInt(0, M_SZ) | opcode)(M_SZ-1, 0)
val my_wmask = (UInt(0, tlWriteMaskBits) | wmask)(tlWriteMaskBits-1, 0)
MuxLookup(a_type, UInt(0), Array(
Acquire.getType -> Cat(my_addr_byte, my_operand_size, my_opcode, alloc),
Acquire.getBlockType -> Cat(my_operand_size, my_opcode, alloc),
Acquire.putType -> Cat(my_wmask, alloc),
Acquire.putBlockType -> Cat(my_wmask, alloc),
Acquire.putAtomicType -> Cat(my_addr_byte, my_operand_size, my_opcode, alloc),
Acquire.getPrefetchType -> Cat(M_XRD, alloc),
Acquire.putPrefetchType -> Cat(M_XWR, alloc)))
}
def fullWriteMask(implicit p: Parameters) = SInt(-1, width = p(TLKey(p(TLId))).writeMaskBits).asUInt
def fullOperandSize(implicit p: Parameters) = {
val dataBits = p(TLKey(p(TLId))).dataBitsPerBeat
UInt(log2Ceil(dataBits / 8))
}
// Most generic constructor
def apply(
is_builtin_type: Bool,
a_type: Bits,
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0),
union: UInt = UInt(0))
(implicit p: Parameters): Acquire = {
val acq = Wire(new Acquire)
acq.is_builtin_type := is_builtin_type
acq.a_type := a_type
acq.client_xact_id := client_xact_id
acq.addr_block := addr_block
acq.addr_beat := addr_beat
acq.data := data
acq.union := union
acq
}
// Copy constructor
def apply(a: Acquire): Acquire = {
val acq = Wire(new Acquire()(a.p))
acq := a
acq
}
}
object BuiltInAcquireBuilder {
def apply(
a_type: UInt,
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0),
addr_byte: UInt = UInt(0),
operand_size: UInt = UInt(0),
opcode: UInt = UInt(0),
wmask: UInt = UInt(0),
alloc: Bool = Bool(true))
(implicit p: Parameters): Acquire = {
Acquire(
is_builtin_type = Bool(true),
a_type = a_type,
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
data = data,
union = Acquire.makeUnion(a_type, addr_byte, operand_size, opcode, wmask, alloc))
}
}
/** Get a single beat of data from the outer memory hierarchy
*
* The client can hint whether he block containing this beat should be
* allocated in the intervening levels of the hierarchy.
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (which beat)
* @param addr_byte sub-block address (which byte)
* @param operand_size {byte, half, word, double} from [[uncore.MemoryOpConstants]]
* @param alloc hint whether the block should be allocated in intervening caches
*/
object Get {
def apply(
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt,
alloc: Bool = Bool(true))
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.getType,
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
operand_size = Acquire.fullOperandSize,
opcode = M_XRD,
alloc = alloc)
}
def apply(
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt,
addr_byte: UInt,
operand_size: UInt,
alloc: Bool)
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.getType,
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
addr_byte = addr_byte,
operand_size = operand_size,
opcode = M_XRD,
alloc = alloc)
}
}
/** Get a whole cache block of data from the outer memory hierarchy
*
* The client can hint whether the block should be allocated in the
* intervening levels of the hierarchy.
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param alloc hint whether the block should be allocated in intervening caches
*/
object GetBlock {
def apply(
client_xact_id: UInt = UInt(0),
addr_block: UInt,
alloc: Bool = Bool(true))
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.getBlockType,
client_xact_id = client_xact_id,
addr_block = addr_block,
operand_size = Acquire.fullOperandSize,
opcode = M_XRD,
alloc = alloc)
}
}
/** Prefetch a cache block into the next-outermost level of the memory hierarchy
* with read permissions.
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
*/
object GetPrefetch {
def apply(
client_xact_id: UInt,
addr_block: UInt)
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.getPrefetchType,
client_xact_id = client_xact_id,
addr_block = addr_block)
}
}
/** Put a single beat of data into the outer memory hierarchy
*
* The block will be allocated in the next-outermost level of the hierarchy.
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (which beat)
* @param data data being refilled to the original requestor
* @param wmask per-byte write mask for this beat
* @param alloc hint whether the block should be allocated in intervening caches
*/
object Put {
def apply(
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt,
data: UInt,
wmask: Option[UInt]= None,
alloc: Bool = Bool(true))
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.putType,
addr_block = addr_block,
addr_beat = addr_beat,
client_xact_id = client_xact_id,
data = data,
wmask = wmask.getOrElse(Acquire.fullWriteMask),
alloc = alloc)
}
}
/** Put a whole cache block of data into the outer memory hierarchy
*
* If the write mask is not full, the block will be allocated in the
* next-outermost level of the hierarchy. If the write mask is full, the
* client can hint whether the block should be allocated or not.
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (which beat of several)
* @param data data being refilled to the original requestor
* @param wmask per-byte write mask for this beat
* @param alloc hint whether the block should be allocated in intervening caches
*/
object PutBlock {
def apply(
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt,
data: UInt,
wmask: Option[UInt] = None,
alloc: Bool = Bool(true))
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.putBlockType,
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
data = data,
wmask = wmask.getOrElse(Acquire.fullWriteMask),
alloc = alloc)
}
}
/** Prefetch a cache block into the next-outermost level of the memory hierarchy
* with write permissions.
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
*/
object PutPrefetch {
def apply(
client_xact_id: UInt,
addr_block: UInt)
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.putPrefetchType,
client_xact_id = client_xact_id,
addr_block = addr_block)
}
}
/** Perform an atomic memory operation in the next-outermost level of the memory hierarchy
*
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat sub-block address (within which beat)
* @param addr_byte sub-block address (which byte)
* @param atomic_opcode {swap, add, xor, and, min, max, minu, maxu} from [[uncore.MemoryOpConstants]]
* @param operand_size {byte, half, word, double} from [[uncore.MemoryOpConstants]]
* @param data source operand data
*/
object PutAtomic {
def apply(
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt,
addr_byte: UInt,
atomic_opcode: UInt,
operand_size: UInt,
data: UInt)
(implicit p: Parameters): Acquire = {
BuiltInAcquireBuilder(
a_type = Acquire.putAtomicType,
client_xact_id = client_xact_id,
addr_block = addr_block,
addr_beat = addr_beat,
data = data,
addr_byte = addr_byte,
operand_size = operand_size,
opcode = atomic_opcode)
}
}
/** The Probe channel is used to force clients to release data or cede permissions
* on a cache block. Clients respond to Probes with [[uncore.Release]] messages.
* The available types of Probes are customized by a particular
* [[uncore.CoherencePolicy]].
*/
class Probe(implicit p: Parameters) extends ManagerToClientChannel
with HasCacheBlockAddress
with HasProbeType
/** [[uncore.Probe]] with an extra field stating its destination id */
class ProbeToDst(implicit p: Parameters) extends Probe()(p) with HasClientId
/** Contains factories for [[uncore.Probe]] and [[uncore.ProbeToDst]]
*
* In general you should avoid using these factories directly and use
* [[uncore.ManagerMetadata.makeProbe(UInt,Acquire)* makeProbe]] instead.
*
* @param dst id of client to which probe should be sent
* @param p_type custom probe type
* @param addr_block address of the cache block
*/
object Probe {
def apply(p_type: UInt, addr_block: UInt)(implicit p: Parameters): Probe = {
val prb = Wire(new Probe)
prb.p_type := p_type
prb.addr_block := addr_block
prb
}
def apply(dst: UInt, p_type: UInt, addr_block: UInt)(implicit p: Parameters): ProbeToDst = {
val prb = Wire(new ProbeToDst)
prb.client_id := dst
prb.p_type := p_type
prb.addr_block := addr_block
prb
}
}
/** The Release channel is used to release data or permission back to the manager
* in response to [[uncore.Probe]] messages. It can also be used to voluntarily
* write back data, for example in the event that dirty data must be evicted on
* a cache miss. The available types of Release messages are always customized by
* a particular [[uncore.CoherencePolicy]]. Releases may contain data or may be
* simple acknowledgements. Voluntary Releases are acknowledged with [[uncore.Grant Grants]].
*/
class ReleaseMetadata(implicit p: Parameters) extends ClientToManagerChannel
with HasTileLinkBeatId
with HasCacheBlockAddress
with HasClientTransactionId
with HasReleaseType {
def full_addr(dummy: Int = 0) = Cat(this.addr_block, this.addr_beat, UInt(0, width = tlByteAddrBits))
}
/** [[uncore.ReleaseMetadata]] with an extra field containing the data beat */
class Release(implicit p: Parameters) extends ReleaseMetadata
with HasTileLinkData
/** [[uncore.ReleaseMetadata]] with an extra field containing the entire cache block */
class BufferedRelease(implicit p: Parameters) extends ReleaseMetadata
with HasTileLinkBlock
/** [[uncore.Release]] with an extra field stating its source id */
class ReleaseFromSrc(implicit p: Parameters) extends Release
with HasClientId
/** [[uncore.BufferedRelease]] with an extra field stating its source id */
class BufferedReleaseFromSrc(implicit p: Parameters) extends BufferedRelease
with HasClientId
/** Contains a [[uncore.Release]] factory
*
* In general you should avoid using this factory directly and use
* [[uncore.ClientMetadata.makeRelease]] instead.
*
* @param voluntary is this a voluntary writeback
* @param r_type type enum defined by coherence protocol
* @param client_xact_id client's transaction id
* @param addr_block address of the cache block
* @param addr_beat beat id of the data
* @param data data being written back
*/
object Release {
def apply(
voluntary: Bool,
r_type: UInt,
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt,
data: UInt)
(implicit p: Parameters): Release = {
val rel = Wire(new Release)
rel.r_type := r_type
rel.client_xact_id := client_xact_id
rel.addr_block := addr_block
rel.addr_beat := addr_beat
rel.data := data
rel.voluntary := voluntary
rel
}
def apply(
src: UInt,
voluntary: Bool,
r_type: UInt,
client_xact_id: UInt,
addr_block: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0))
(implicit p: Parameters): ReleaseFromSrc = {
val rel = Wire(new ReleaseFromSrc)
rel.client_id := src
rel.voluntary := voluntary
rel.r_type := r_type
rel.client_xact_id := client_xact_id
rel.addr_block := addr_block
rel.addr_beat := addr_beat
rel.data := data
rel
}
}
/** The Grant channel is used to refill data or grant permissions requested of the
* manager agent via an [[uncore.Acquire]] message. It is also used to acknowledge
* the receipt of voluntary writeback from clients in the form of [[uncore.Release]]
* messages. There are built-in Grant messages used for Gets and Puts, and
* coherence policies may also define custom Grant types. Grants may contain data
* or may be simple acknowledgements. Grants are responded to with [[uncore.Finish]].
*/
class GrantMetadata(implicit p: Parameters) extends ManagerToClientChannel
with HasTileLinkBeatId
with HasClientTransactionId
with HasManagerTransactionId
with HasGrantType {
def makeFinish(dummy: Int = 0): Finish = {
val f = Wire(new Finish)
f.manager_xact_id := this.manager_xact_id
f
}
}
/** [[uncore.GrantMetadata]] with an extra field containing a single beat of data */
class Grant(implicit p: Parameters) extends GrantMetadata
with HasTileLinkData
/** [[uncore.Grant]] with an extra field stating its destination */
class GrantToDst(implicit p: Parameters) extends Grant
with HasClientId
/** [[uncore.Grant]] with an extra field stating its destination */
class GrantFromSrc(implicit p: Parameters) extends Grant
with HasManagerId {
override def makeFinish(dummy: Int = 0): FinishToDst = {
val f = Wire(new FinishToDst)
f.manager_xact_id := this.manager_xact_id
f.manager_id := this.manager_id
f
}
}
/** [[uncore.GrantMetadata]] with an extra field containing an entire cache block */
class BufferedGrant(implicit p: Parameters) extends GrantMetadata
with HasTileLinkBlock
/** [[uncore.BufferedGrant]] with an extra field stating its destination */
class BufferedGrantToDst(implicit p: Parameters) extends BufferedGrant
with HasClientId
/** Contains definitions of the the built-in grant types and factories
* for [[uncore.Grant]] and [[uncore.GrantToDst]]
*
* In general you should avoid using these factories directly and use
* [[uncore.ManagerMetadata.makeGrant(uncore.AcquireFromSrc* makeGrant]] instead.
*
* @param dst id of client to which grant should be sent
* @param is_builtin_type built-in or custom type message?
* @param g_type built-in type enum or custom type enum
* @param client_xact_id client's transaction id
* @param manager_xact_id manager's transaction id
* @param addr_beat beat id of the data
* @param data data being refilled to the original requestor
*/
object Grant {
val nBuiltInTypes = 5
def voluntaryAckType = UInt("b000") // For acking Releases
def prefetchAckType = UInt("b001") // For acking any kind of Prefetch
def putAckType = UInt("b011") // For acking any kind of non-prfetch Put
def getDataBeatType = UInt("b100") // Supplying a single beat of Get
def getDataBlockType = UInt("b101") // Supplying all beats of a GetBlock
def typesWithData = Vec(getDataBlockType, getDataBeatType)
def typesWithMultibeatData= Vec(getDataBlockType)
def apply(
is_builtin_type: Bool,
g_type: UInt,
client_xact_id: UInt,
manager_xact_id: UInt,
addr_beat: UInt,
data: UInt)
(implicit p: Parameters): Grant = {
val gnt = Wire(new Grant)
gnt.is_builtin_type := is_builtin_type
gnt.g_type := g_type
gnt.client_xact_id := client_xact_id
gnt.manager_xact_id := manager_xact_id
gnt.addr_beat := addr_beat
gnt.data := data
gnt
}
def apply(
dst: UInt,
is_builtin_type: Bool,
g_type: UInt,
client_xact_id: UInt,
manager_xact_id: UInt,
addr_beat: UInt = UInt(0),
data: UInt = UInt(0))
(implicit p: Parameters): GrantToDst = {
val gnt = Wire(new GrantToDst)
gnt.client_id := dst
gnt.is_builtin_type := is_builtin_type
gnt.g_type := g_type
gnt.client_xact_id := client_xact_id
gnt.manager_xact_id := manager_xact_id
gnt.addr_beat := addr_beat
gnt.data := data
gnt
}
}
/** The Finish channel is used to provide a global ordering of transactions
* in networks that do not guarantee point-to-point ordering of messages.
* A Finsish message is sent as acknowledgement of receipt of a [[uncore.Grant]].
* When a Finish message is received, a manager knows it is safe to begin
* processing other transactions that touch the same cache block.
*/
class Finish(implicit p: Parameters) extends ClientToManagerChannel()(p)
with HasManagerTransactionId {
def hasData(dummy: Int = 0) = Bool(false)
def hasMultibeatData(dummy: Int = 0) = Bool(false)
}
/** [[uncore.Finish]] with an extra field stating its destination */
class FinishToDst(implicit p: Parameters) extends Finish
with HasManagerId
/** Complete IO definition for incoherent TileLink, including networking headers */
class UncachedTileLinkIO(implicit p: Parameters) extends TLBundle()(p) {
val acquire = new DecoupledIO(new LogicalNetworkIO(new Acquire))
val grant = new DecoupledIO(new LogicalNetworkIO(new Grant)).flip
val finish = new DecoupledIO(new LogicalNetworkIO(new Finish))
}
/** Complete IO definition for coherent TileLink, including networking headers */
class TileLinkIO(implicit p: Parameters) extends UncachedTileLinkIO()(p) {
val probe = new DecoupledIO(new LogicalNetworkIO(new Probe)).flip
val release = new DecoupledIO(new LogicalNetworkIO(new Release))
}
/** This version of UncachedTileLinkIO does not contain network headers.
* It is intended for use within client agents.
*
* Headers are provided in the top-level that instantiates the clients and network,
* probably using a [[uncore.ClientTileLinkNetworkPort]] module.
* By eliding the header subbundles within the clients we can enable
* hierarchical P-and-R while minimizing unconnected port errors in GDS.
*
* Secondly, this version of the interface elides [[uncore.Finish]] messages, with the
* assumption that a [[uncore.FinishUnit]] has been coupled to the TileLinkIO port
* to deal with acking received [[uncore.Grant Grants]].
*/
class ClientUncachedTileLinkIO(implicit p: Parameters) extends TLBundle()(p) {
val acquire = new DecoupledIO(new Acquire)
val grant = new DecoupledIO(new Grant).flip
}
/** This version of TileLinkIO does not contain network headers.
* It is intended for use within client agents.
*/
class ClientTileLinkIO(implicit p: Parameters) extends TLBundle()(p) {
val acquire = new DecoupledIO(new Acquire)
val probe = new DecoupledIO(new Probe).flip
val release = new DecoupledIO(new Release)
val grant = new DecoupledIO(new GrantFromSrc).flip
val finish = new DecoupledIO(new FinishToDst)
}
/** This version of TileLinkIO does not contain network headers, but
* every channel does include an extra client_id subbundle.
* It is intended for use within Management agents.
*
* Managers need to track where [[uncore.Acquire]] and [[uncore.Release]] messages
* originated so that they can send a [[uncore.Grant]] to the right place.
* Similarly they must be able to issues Probes to particular clients.
* However, we'd still prefer to have [[uncore.ManagerTileLinkNetworkPort]] fill in
* the header.src to enable hierarchical p-and-r of the managers. Additionally,
* coherent clients might be mapped to random network port ids, and we'll leave it to the
* [[uncore.ManagerTileLinkNetworkPort]] to apply the correct mapping. Managers do need to
* see Finished so they know when to allow new transactions on a cache
* block to proceed.
*/
class ManagerTileLinkIO(implicit p: Parameters) extends TLBundle()(p) {
val acquire = new DecoupledIO(new AcquireFromSrc).flip
val grant = new DecoupledIO(new GrantToDst)
val finish = new DecoupledIO(new Finish).flip
val probe = new DecoupledIO(new ProbeToDst)
val release = new DecoupledIO(new ReleaseFromSrc).flip
}

View File

@ -0,0 +1,386 @@
package uncore.tilelink
import Chisel._
import junctions._
import scala.collection.mutable.ArraySeq
import uncore.util._
import cde.{Parameters, Field}
/** PortedTileLinkNetworks combine a TileLink protocol with a particular physical
* network implementation.
*
* Specifically, they provide mappings between ClientTileLinkIO/
* ManagerTileLinkIO channels and LogicalNetwork ports (i.e. generic
* TileLinkIO with networking headers). Channels coming into the network have
* appropriate networking headers appended and outgoing channels have their
* headers stripped.
*
* @constructor base class constructor for Ported TileLink NoC
* @param addrToManagerId a mapping from a physical address to the network
* id of a coherence manager
* @param sharerToClientId a mapping from the id of a particular coherent
* client (as determined by e.g. the directory) and the network id
* of that client
* @param clientDepths the depths of the queue that should be used to buffer
* each channel on the client side of the network
* @param managerDepths the depths of the queue that should be used to buffer
* each channel on the manager side of the network
*/
abstract class PortedTileLinkNetwork(
addrToManagerId: UInt => UInt,
sharerToClientId: UInt => UInt,
clientDepths: TileLinkDepths,
managerDepths: TileLinkDepths)
(implicit p: Parameters) extends TLModule()(p) {
val nClients = tlNClients
val nManagers = tlNManagers
val io = new Bundle {
val clients_cached = Vec(tlNCachingClients, new ClientTileLinkIO).flip
val clients_uncached = Vec(tlNCachelessClients, new ClientUncachedTileLinkIO).flip
val managers = Vec(nManagers, new ManagerTileLinkIO).flip
}
val clients = (io.clients_cached ++ io.clients_uncached).zipWithIndex.map {
case (io, idx) => {
val qs = Module(new TileLinkEnqueuer(clientDepths))
io match {
case c: ClientTileLinkIO => {
val port = Module(new ClientTileLinkNetworkPort(idx, addrToManagerId))
port.io.client <> c
qs.io.client <> port.io.network
qs.io.manager
}
case u: ClientUncachedTileLinkIO => {
val port = Module(new ClientUncachedTileLinkNetworkPort(idx, addrToManagerId))
port.io.client <> u
qs.io.client <> port.io.network
qs.io.manager
}
}
}
}
val managers = io.managers.zipWithIndex.map {
case (m, i) => {
val port = Module(new ManagerTileLinkNetworkPort(i, sharerToClientId))
val qs = Module(new TileLinkEnqueuer(managerDepths))
port.io.manager <> m
port.io.network <> qs.io.manager
qs.io.client
}
}
}
/** A simple arbiter for each channel that also deals with header-based routing.
* Assumes a single manager agent. */
class PortedTileLinkArbiter(
sharerToClientId: UInt => UInt = (u: UInt) => u,
clientDepths: TileLinkDepths = TileLinkDepths(0,0,0,0,0),
managerDepths: TileLinkDepths = TileLinkDepths(0,0,0,0,0))
(implicit p: Parameters)
extends PortedTileLinkNetwork(u => UInt(0), sharerToClientId, clientDepths, managerDepths)(p)
with TileLinkArbiterLike
with PassesId {
val arbN = nClients
require(nManagers == 1)
if(arbN > 1) {
hookupClientSource(clients.map(_.acquire), managers.head.acquire)
hookupClientSource(clients.map(_.release), managers.head.release)
hookupFinish(clients.map(_.finish), managers.head.finish)
hookupManagerSourceWithHeader(clients.map(_.probe), managers.head.probe)
hookupManagerSourceWithHeader(clients.map(_.grant), managers.head.grant)
} else {
managers.head <> clients.head
}
}
/** Provides a separate physical crossbar for each channel. Assumes multiple manager
* agents. Managers are assigned to higher physical network port ids than
* clients, and translations between logical network id and physical crossbar
* port id are done automatically.
*/
class PortedTileLinkCrossbar(
addrToManagerId: UInt => UInt = u => UInt(0),
sharerToClientId: UInt => UInt = u => u,
clientDepths: TileLinkDepths = TileLinkDepths(0,0,0,0,0),
managerDepths: TileLinkDepths = TileLinkDepths(0,0,0,0,0))
(implicit p: Parameters)
extends PortedTileLinkNetwork(addrToManagerId, sharerToClientId, clientDepths, managerDepths)(p) {
val n = p(LNEndpoints)
val phyHdrWidth = log2Up(n)
val count = tlDataBeats
// Actually instantiate the particular networks required for TileLink
val acqNet = Module(new BasicBus(CrossbarConfig(n, new Acquire, count, Some((a: PhysicalNetworkIO[Acquire]) => a.payload.hasMultibeatData()))))
val relNet = Module(new BasicBus(CrossbarConfig(n, new Release, count, Some((r: PhysicalNetworkIO[Release]) => r.payload.hasMultibeatData()))))
val prbNet = Module(new BasicBus(CrossbarConfig(n, new Probe)))
val gntNet = Module(new BasicBus(CrossbarConfig(n, new Grant, count, Some((g: PhysicalNetworkIO[Grant]) => g.payload.hasMultibeatData()))))
val ackNet = Module(new BasicBus(CrossbarConfig(n, new Finish)))
// Aliases for the various network IO bundle types
type PNIO[T <: Data] = DecoupledIO[PhysicalNetworkIO[T]]
type LNIO[T <: Data] = DecoupledIO[LogicalNetworkIO[T]]
type FromCrossbar[T <: Data] = PNIO[T] => LNIO[T]
type ToCrossbar[T <: Data] = LNIO[T] => PNIO[T]
// Shims for converting between logical network IOs and physical network IOs
def crossbarToManagerShim[T <: Data](in: PNIO[T]): LNIO[T] = {
val out = DefaultFromPhysicalShim(in)
out.bits.header.src := in.bits.header.src - UInt(nManagers)
out
}
def crossbarToClientShim[T <: Data](in: PNIO[T]): LNIO[T] = {
val out = DefaultFromPhysicalShim(in)
out.bits.header.dst := in.bits.header.dst - UInt(nManagers)
out
}
def managerToCrossbarShim[T <: Data](in: LNIO[T]): PNIO[T] = {
val out = DefaultToPhysicalShim(n, in)
out.bits.header.dst := in.bits.header.dst + UInt(nManagers, phyHdrWidth)
out
}
def clientToCrossbarShim[T <: Data](in: LNIO[T]): PNIO[T] = {
val out = DefaultToPhysicalShim(n, in)
out.bits.header.src := in.bits.header.src + UInt(nManagers, phyHdrWidth)
out
}
// Make an individual connection between virtual and physical ports using
// a particular shim. Also pin the unused Decoupled control signal low.
def doDecoupledInputHookup[T <: Data](phys_in: PNIO[T], phys_out: PNIO[T], log_io: LNIO[T], shim: ToCrossbar[T]) = {
val s = shim(log_io)
phys_in.valid := s.valid
phys_in.bits := s.bits
s.ready := phys_in.ready
phys_out.ready := Bool(false)
}
def doDecoupledOutputHookup[T <: Data](phys_in: PNIO[T], phys_out: PNIO[T], log_io: LNIO[T], shim: FromCrossbar[T]) = {
val s = shim(phys_out)
log_io.valid := s.valid
log_io.bits := s.bits
s.ready := log_io.ready
phys_in.valid := Bool(false)
}
//Hookup all instances of a particular subbundle of TileLink
def doDecoupledHookups[T <: Data](physIO: BasicCrossbarIO[T], getLogIO: TileLinkIO => LNIO[T]) = {
physIO.in.head.bits.payload match {
case c: ClientToManagerChannel => {
managers.zipWithIndex.map { case (i, id) =>
doDecoupledOutputHookup(physIO.in(id), physIO.out(id), getLogIO(i), crossbarToManagerShim[T])
}
clients.zipWithIndex.map { case (i, id) =>
doDecoupledInputHookup(physIO.in(id+nManagers), physIO.out(id+nManagers), getLogIO(i), clientToCrossbarShim[T])
}
}
case m: ManagerToClientChannel => {
managers.zipWithIndex.map { case (i, id) =>
doDecoupledInputHookup(physIO.in(id), physIO.out(id), getLogIO(i), managerToCrossbarShim[T])
}
clients.zipWithIndex.map { case (i, id) =>
doDecoupledOutputHookup(physIO.in(id+nManagers), physIO.out(id+nManagers), getLogIO(i), crossbarToClientShim[T])
}
}
}
}
doDecoupledHookups(acqNet.io, (tl: TileLinkIO) => tl.acquire)
doDecoupledHookups(relNet.io, (tl: TileLinkIO) => tl.release)
doDecoupledHookups(prbNet.io, (tl: TileLinkIO) => tl.probe)
doDecoupledHookups(gntNet.io, (tl: TileLinkIO) => tl.grant)
doDecoupledHookups(ackNet.io, (tl: TileLinkIO) => tl.finish)
}
class ClientUncachedTileLinkIORouter(
nOuter: Int, routeSel: UInt => UInt)(implicit p: Parameters)
extends TLModule {
val io = new Bundle {
val in = (new ClientUncachedTileLinkIO).flip
val out = Vec(nOuter, new ClientUncachedTileLinkIO)
}
val acq_route = routeSel(io.in.acquire.bits.full_addr())
io.in.acquire.ready := Bool(false)
io.out.zipWithIndex.foreach { case (out, i) =>
out.acquire.valid := io.in.acquire.valid && acq_route(i)
out.acquire.bits := io.in.acquire.bits
when (acq_route(i)) { io.in.acquire.ready := out.acquire.ready }
}
val gnt_arb = Module(new LockingRRArbiter(
new Grant, nOuter, tlDataBeats, Some((gnt: Grant) => gnt.hasMultibeatData())))
gnt_arb.io.in <> io.out.map(_.grant)
io.in.grant <> gnt_arb.io.out
assert(!io.in.acquire.valid || acq_route.orR, "No valid route")
}
class TileLinkInterconnectIO(val nInner: Int, val nOuter: Int)
(implicit p: Parameters) extends Bundle {
val in = Vec(nInner, new ClientUncachedTileLinkIO).flip
val out = Vec(nOuter, new ClientUncachedTileLinkIO)
}
class ClientUncachedTileLinkIOCrossbar(
nInner: Int, nOuter: Int, routeSel: UInt => UInt)
(implicit p: Parameters) extends TLModule {
val io = new TileLinkInterconnectIO(nInner, nOuter)
if (nInner == 1) {
val router = Module(new ClientUncachedTileLinkIORouter(nOuter, routeSel))
router.io.in <> io.in.head
io.out <> router.io.out
} else {
val routers = List.fill(nInner) {
Module(new ClientUncachedTileLinkIORouter(nOuter, routeSel)) }
val arbiters = List.fill(nOuter) {
Module(new ClientUncachedTileLinkIOArbiter(nInner)) }
for (i <- 0 until nInner) {
routers(i).io.in <> io.in(i)
}
for (i <- 0 until nOuter) {
arbiters(i).io.in <> routers.map(r => r.io.out(i))
io.out(i) <> arbiters(i).io.out
}
}
}
abstract class TileLinkInterconnect(implicit p: Parameters) extends TLModule()(p) {
val nInner: Int
val nOuter: Int
lazy val io = new TileLinkInterconnectIO(nInner, nOuter)
}
class TileLinkRecursiveInterconnect(val nInner: Int, addrMap: AddrMap)
(implicit p: Parameters) extends TileLinkInterconnect()(p) {
def port(name: String) = io.out(addrMap.port(name))
val nOuter = addrMap.numSlaves
val routeSel = (addr: UInt) =>
Cat(addrMap.entries.map(e => addrMap(e.name).containsAddress(addr)).reverse)
val xbar = Module(new ClientUncachedTileLinkIOCrossbar(nInner, addrMap.length, routeSel))
xbar.io.in <> io.in
io.out <> addrMap.entries.zip(xbar.io.out).flatMap {
case (entry, xbarOut) => {
entry.region match {
case submap: AddrMap if submap.isEmpty =>
xbarOut.acquire.ready := Bool(false)
xbarOut.grant.valid := Bool(false)
None
case submap: AddrMap if !submap.collapse =>
val ic = Module(new TileLinkRecursiveInterconnect(1, submap))
ic.io.in.head <> xbarOut
ic.io.out
case _ =>
Some(xbarOut)
}
}
}
}
class TileLinkMemoryInterconnect(
nBanksPerChannel: Int, nChannels: Int)
(implicit p: Parameters) extends TileLinkInterconnect()(p) {
val nBanks = nBanksPerChannel * nChannels
val nInner = nBanks
val nOuter = nChannels
def connectChannel(outer: ClientUncachedTileLinkIO, inner: ClientUncachedTileLinkIO) {
outer <> inner
outer.acquire.bits.addr_block := inner.acquire.bits.addr_block >> UInt(log2Ceil(nChannels))
}
for (i <- 0 until nChannels) {
/* Bank assignments to channels are strided so that consecutive banks
* map to different channels. That way, consecutive cache lines also
* map to different channels */
val banks = (i until nBanks by nChannels).map(j => io.in(j))
val channelArb = Module(new ClientUncachedTileLinkIOArbiter(nBanksPerChannel))
channelArb.io.in <> banks
connectChannel(io.out(i), channelArb.io.out)
}
}
/** Allows users to switch between various memory configurations. Note that
* this is a dangerous operation: not only does switching the select input to
* this module violate TileLink, it also causes the memory of the machine to
* become garbled. It's expected that select only changes at boot time, as
* part of the memory controller configuration. */
class TileLinkMemorySelectorIO(val nBanks: Int, val maxMemChannels: Int, nConfigs: Int)
(implicit p: Parameters)
extends TileLinkInterconnectIO(nBanks, maxMemChannels) {
val select = UInt(INPUT, width = log2Up(nConfigs))
override def cloneType =
new TileLinkMemorySelectorIO(nBanks, maxMemChannels, nConfigs).asInstanceOf[this.type]
}
class TileLinkMemorySelector(nBanks: Int, maxMemChannels: Int, configs: Seq[Int])
(implicit p: Parameters)
extends TileLinkInterconnect()(p) {
val nInner = nBanks
val nOuter = maxMemChannels
val nConfigs = configs.size
override lazy val io = new TileLinkMemorySelectorIO(nBanks, maxMemChannels, nConfigs)
def muxOnSelect[T <: Data](up: DecoupledIO[T], dn: DecoupledIO[T], active: Bool): Unit = {
when (active) { dn.bits := up.bits }
when (active) { up.ready := dn.ready }
when (active) { dn.valid := up.valid }
}
def muxOnSelect(up: ClientUncachedTileLinkIO, dn: ClientUncachedTileLinkIO, active: Bool): Unit = {
muxOnSelect(up.acquire, dn.acquire, active)
muxOnSelect(dn.grant, up.grant, active)
}
def muxOnSelect(up: Vec[ClientUncachedTileLinkIO], dn: Vec[ClientUncachedTileLinkIO], active: Bool) : Unit = {
for (i <- 0 until up.size)
muxOnSelect(up(i), dn(i), active)
}
/* Disconnects a vector of TileLink ports, which involves setting them to
* invalid. Due to Chisel reasons, we need to also set the bits to 0 (since
* there can't be any unconnected inputs). */
def disconnectOuter(outer: Vec[ClientUncachedTileLinkIO]) = {
outer.foreach{ m =>
m.acquire.valid := Bool(false)
m.acquire.bits := m.acquire.bits.fromBits(UInt(0))
m.grant.ready := Bool(false)
}
}
def disconnectInner(inner: Vec[ClientUncachedTileLinkIO]) = {
inner.foreach { m =>
m.grant.valid := Bool(false)
m.grant.bits := m.grant.bits.fromBits(UInt(0))
m.acquire.ready := Bool(false)
}
}
/* Provides default wires on all our outputs. */
disconnectOuter(io.out)
disconnectInner(io.in)
/* Constructs interconnects for each of the layouts suggested by the
* configuration and switches between them based on the select input. */
configs.zipWithIndex.foreach{ case (nChannels, select) =>
val nBanksPerChannel = nBanks / nChannels
val ic = Module(new TileLinkMemoryInterconnect(nBanksPerChannel, nChannels))
disconnectInner(ic.io.out)
disconnectOuter(ic.io.in)
muxOnSelect(io.in, ic.io.in, io.select === UInt(select))
muxOnSelect(ic.io.out, io.out, io.select === UInt(select))
}
}

View File

@ -0,0 +1,308 @@
// See LICENSE for license details.
package uncore.tilelink
import Chisel._
import uncore.util._
import cde.{Parameters, Field}
case object LNEndpoints extends Field[Int]
case object LNHeaderBits extends Field[Int]
class PhysicalHeader(n: Int) extends Bundle {
val src = UInt(width = log2Up(n))
val dst = UInt(width = log2Up(n))
}
class PhysicalNetworkIO[T <: Data](n: Int, dType: T) extends Bundle {
val header = new PhysicalHeader(n)
val payload = dType.cloneType
override def cloneType = new PhysicalNetworkIO(n,dType).asInstanceOf[this.type]
}
class BasicCrossbarIO[T <: Data](n: Int, dType: T) extends Bundle {
val in = Vec(n, Decoupled(new PhysicalNetworkIO(n,dType))).flip
val out = Vec(n, Decoupled(new PhysicalNetworkIO(n,dType)))
}
abstract class PhysicalNetwork extends Module
case class CrossbarConfig[T <: Data](n: Int, dType: T, count: Int = 1, needsLock: Option[PhysicalNetworkIO[T] => Bool] = None)
abstract class AbstractCrossbar[T <: Data](conf: CrossbarConfig[T]) extends PhysicalNetwork {
val io = new BasicCrossbarIO(conf.n, conf.dType)
}
class BasicBus[T <: Data](conf: CrossbarConfig[T]) extends AbstractCrossbar(conf) {
val arb = Module(new LockingRRArbiter(io.in(0).bits, conf.n, conf.count, conf.needsLock))
arb.io.in <> io.in
arb.io.out.ready := io.out(arb.io.out.bits.header.dst).ready
for ((out, i) <- io.out zipWithIndex) {
out.valid := arb.io.out.valid && arb.io.out.bits.header.dst === UInt(i)
out.bits := arb.io.out.bits
}
}
class BasicCrossbar[T <: Data](conf: CrossbarConfig[T]) extends AbstractCrossbar(conf) {
io.in.foreach { _.ready := Bool(false) }
io.out.zipWithIndex.map{ case (out, i) => {
val rrarb = Module(new LockingRRArbiter(io.in(0).bits, conf.n, conf.count, conf.needsLock))
(rrarb.io.in, io.in).zipped.map{ case (arb, in) => {
val destined = in.bits.header.dst === UInt(i)
arb.valid := in.valid && destined
arb.bits := in.bits
when (arb.ready && destined) { in.ready := Bool(true) }
}}
out <> rrarb.io.out
}}
}
abstract class LogicalNetwork extends Module
class LogicalHeader(implicit p: Parameters) extends junctions.ParameterizedBundle()(p) {
val src = UInt(width = p(LNHeaderBits))
val dst = UInt(width = p(LNHeaderBits))
}
class LogicalNetworkIO[T <: Data](dType: T)(implicit p: Parameters) extends Bundle {
val header = new LogicalHeader
val payload = dType.cloneType
override def cloneType = new LogicalNetworkIO(dType)(p).asInstanceOf[this.type]
}
object DecoupledLogicalNetworkIOWrapper {
def apply[T <: Data](
in: DecoupledIO[T],
src: UInt = UInt(0),
dst: UInt = UInt(0))
(implicit p: Parameters): DecoupledIO[LogicalNetworkIO[T]] = {
val out = Wire(Decoupled(new LogicalNetworkIO(in.bits)))
out.valid := in.valid
out.bits.payload := in.bits
out.bits.header.dst := dst
out.bits.header.src := src
in.ready := out.ready
out
}
}
object DecoupledLogicalNetworkIOUnwrapper {
def apply[T <: Data](in: DecoupledIO[LogicalNetworkIO[T]])
(implicit p: Parameters): DecoupledIO[T] = {
val out = Wire(Decoupled(in.bits.payload))
out.valid := in.valid
out.bits := in.bits.payload
in.ready := out.ready
out
}
}
object DefaultFromPhysicalShim {
def apply[T <: Data](in: DecoupledIO[PhysicalNetworkIO[T]])
(implicit p: Parameters): DecoupledIO[LogicalNetworkIO[T]] = {
val out = Wire(Decoupled(new LogicalNetworkIO(in.bits.payload)))
out.bits.header := in.bits.header
out.bits.payload := in.bits.payload
out.valid := in.valid
in.ready := out.ready
out
}
}
object DefaultToPhysicalShim {
def apply[T <: Data](n: Int, in: DecoupledIO[LogicalNetworkIO[T]])
(implicit p: Parameters): DecoupledIO[PhysicalNetworkIO[T]] = {
val out = Wire(Decoupled(new PhysicalNetworkIO(n, in.bits.payload)))
out.bits.header := in.bits.header
out.bits.payload := in.bits.payload
out.valid := in.valid
in.ready := out.ready
out
}
}
/** A helper module that automatically issues [[uncore.Finish]] messages in repsonse
* to [[uncore.Grant]] that it receives from a manager and forwards to a client
*/
class FinishUnit(srcId: Int = 0, outstanding: Int = 2)(implicit p: Parameters) extends TLModule()(p)
with HasDataBeatCounters {
val io = new Bundle {
val grant = Decoupled(new LogicalNetworkIO(new Grant)).flip
val refill = Decoupled(new Grant)
val finish = Decoupled(new LogicalNetworkIO(new Finish))
val ready = Bool(OUTPUT)
}
val g = io.grant.bits.payload
if(tlNetworkPreservesPointToPointOrdering) {
io.finish.valid := Bool(false)
io.refill.valid := io.grant.valid
io.refill.bits := g
io.grant.ready := io.refill.ready
io.ready := Bool(true)
} else {
// We only want to send Finishes after we have collected all beats of
// a multibeat Grant. But Grants from multiple managers or transactions may
// get interleaved, so we could need a counter for each.
val done = if(tlNetworkDoesNotInterleaveBeats) {
connectIncomingDataBeatCounterWithHeader(io.grant)
} else {
val entries = 1 << tlClientXactIdBits
def getId(g: LogicalNetworkIO[Grant]) = g.payload.client_xact_id
assert(getId(io.grant.bits) <= UInt(entries), "Not enough grant beat counters, only " + entries + " entries.")
connectIncomingDataBeatCountersWithHeader(io.grant, entries, getId).reduce(_||_)
}
val q = Module(new FinishQueue(outstanding))
q.io.enq.valid := io.grant.fire() && g.requiresAck() && (!g.hasMultibeatData() || done)
q.io.enq.bits := g.makeFinish()
q.io.enq.bits.manager_id := io.grant.bits.header.src
io.finish.bits.header.src := UInt(srcId)
io.finish.bits.header.dst := q.io.deq.bits.manager_id
io.finish.bits.payload := q.io.deq.bits
io.finish.valid := q.io.deq.valid
q.io.deq.ready := io.finish.ready
io.refill.valid := (q.io.enq.ready || !g.requiresAck()) && io.grant.valid
io.refill.bits := g
io.grant.ready := (q.io.enq.ready || !g.requiresAck()) && io.refill.ready
io.ready := q.io.enq.ready
}
}
class FinishQueue(entries: Int)(implicit p: Parameters) extends Queue(new FinishToDst()(p), entries)
/** A port to convert [[uncore.ClientTileLinkIO]].flip into [[uncore.TileLinkIO]]
*
* Creates network headers for [[uncore.Acquire]] and [[uncore.Release]] messages,
* calculating header.dst and filling in header.src.
* Strips headers from [[uncore.Probe Probes]].
* Passes [[uncore.GrantFromSrc]] and accepts [[uncore.FinishFromDst]] in response,
* setting up the headers for each.
*
* @param clientId network port id of this agent
* @param addrConvert how a physical address maps to a destination manager port id
*/
class ClientTileLinkNetworkPort(clientId: Int, addrConvert: UInt => UInt)
(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val client = new ClientTileLinkIO().flip
val network = new TileLinkIO
}
val acq_with_header = ClientTileLinkHeaderCreator(io.client.acquire, clientId, addrConvert)
val rel_with_header = ClientTileLinkHeaderCreator(io.client.release, clientId, addrConvert)
val fin_with_header = ClientTileLinkHeaderCreator(io.client.finish, clientId)
val prb_without_header = DecoupledLogicalNetworkIOUnwrapper(io.network.probe)
val gnt_without_header = DecoupledLogicalNetworkIOUnwrapper(io.network.grant)
io.network.acquire <> acq_with_header
io.network.release <> rel_with_header
io.network.finish <> fin_with_header
io.client.probe <> prb_without_header
io.client.grant.bits.manager_id := io.network.grant.bits.header.src
io.client.grant <> gnt_without_header
}
/** A port to convert [[uncore.ClientUncachedTileLinkIO]].flip into [[uncore.TileLinkIO]]
*
* Creates network headers for [[uncore.Acquire]] and [[uncore.Release]] messages,
* calculating header.dst and filling in header.src.
* Responds to [[uncore.Grant]] by automatically issuing [[uncore.Finish]] to the granting managers.
*
* @param clientId network port id of this agent
* @param addrConvert how a physical address maps to a destination manager port id
*/
class ClientUncachedTileLinkNetworkPort(clientId: Int, addrConvert: UInt => UInt)
(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val client = new ClientUncachedTileLinkIO().flip
val network = new TileLinkIO
}
val finisher = Module(new FinishUnit(clientId))
finisher.io.grant <> io.network.grant
io.network.finish <> finisher.io.finish
val acq_with_header = ClientTileLinkHeaderCreator(io.client.acquire, clientId, addrConvert)
val gnt_without_header = finisher.io.refill
io.network.acquire.bits := acq_with_header.bits
io.network.acquire.valid := acq_with_header.valid && finisher.io.ready
acq_with_header.ready := io.network.acquire.ready && finisher.io.ready
io.client.grant <> gnt_without_header
io.network.probe.ready := Bool(false)
io.network.release.valid := Bool(false)
}
object ClientTileLinkHeaderCreator {
def apply[T <: ClientToManagerChannel with HasManagerId](
in: DecoupledIO[T],
clientId: Int)
(implicit p: Parameters): DecoupledIO[LogicalNetworkIO[T]] = {
val out = Wire(new DecoupledIO(new LogicalNetworkIO(in.bits)))
out.bits.payload := in.bits
out.bits.header.src := UInt(clientId)
out.bits.header.dst := in.bits.manager_id
out.valid := in.valid
in.ready := out.ready
out
}
def apply[T <: ClientToManagerChannel with HasCacheBlockAddress](
in: DecoupledIO[T],
clientId: Int,
addrConvert: UInt => UInt)
(implicit p: Parameters): DecoupledIO[LogicalNetworkIO[T]] = {
val out = Wire(new DecoupledIO(new LogicalNetworkIO(in.bits)))
out.bits.payload := in.bits
out.bits.header.src := UInt(clientId)
out.bits.header.dst := addrConvert(in.bits.addr_block)
out.valid := in.valid
in.ready := out.ready
out
}
}
/** A port to convert [[uncore.ManagerTileLinkIO]].flip into [[uncore.TileLinkIO]].flip
*
* Creates network headers for [[uncore.Probe]] and [[uncore.Grant]] messagess,
* calculating header.dst and filling in header.src.
* Strips headers from [[uncore.Acquire]], [[uncore.Release]] and [[uncore.Finish]],
* but supplies client_id instead.
*
* @param managerId the network port id of this agent
* @param idConvert how a sharer id maps to a destination client port id
*/
class ManagerTileLinkNetworkPort(managerId: Int, idConvert: UInt => UInt)
(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val manager = new ManagerTileLinkIO().flip
val network = new TileLinkIO().flip
}
io.network.grant <> ManagerTileLinkHeaderCreator(io.manager.grant, managerId, (u: UInt) => u)
io.network.probe <> ManagerTileLinkHeaderCreator(io.manager.probe, managerId, idConvert)
io.manager.acquire <> DecoupledLogicalNetworkIOUnwrapper(io.network.acquire)
io.manager.acquire.bits.client_id := io.network.acquire.bits.header.src
io.manager.release <> DecoupledLogicalNetworkIOUnwrapper(io.network.release)
io.manager.release.bits.client_id := io.network.release.bits.header.src
io.manager.finish <> DecoupledLogicalNetworkIOUnwrapper(io.network.finish)
}
object ManagerTileLinkHeaderCreator {
def apply[T <: ManagerToClientChannel with HasClientId](
in: DecoupledIO[T],
managerId: Int,
idConvert: UInt => UInt)
(implicit p: Parameters): DecoupledIO[LogicalNetworkIO[T]] = {
val out = Wire(new DecoupledIO(new LogicalNetworkIO(in.bits)))
out.bits.payload := in.bits
out.bits.header.src := UInt(managerId)
out.bits.header.dst := idConvert(in.bits.client_id)
out.valid := in.valid
in.ready := out.ready
out
}
}

View File

@ -0,0 +1,422 @@
package uncore.unittests
import Chisel._
import junctions._
import uncore.tilelink._
import uncore.constants._
import uncore.util._
import cde.Parameters
abstract class Driver(implicit p: Parameters) extends TLModule()(p) {
val io = new Bundle {
val mem = new ClientUncachedTileLinkIO
val start = Bool(INPUT)
val finished = Bool(OUTPUT)
}
}
/**
* Tests that single-beat Gets of decreasing size return subsets of the
* data returned by larger Gets
*/
class GetMultiWidthDriver(implicit p: Parameters) extends Driver()(p) {
val s_start :: s_send :: s_recv :: s_done :: Nil = Enum(Bits(), 4)
val state = Reg(init = s_start)
val w = 64
val initialSize = UInt(log2Ceil(w/8))
val size = Reg(UInt(width = log2Ceil(log2Ceil(w/8)+1)))
val ref = Reg(UInt(width = w))
val bytemask = (UInt(1) << (UInt(1) << size)) - UInt(1)
val bitmask = FillInterleaved(8, bytemask)
io.mem.acquire.valid := (state === s_send)
io.mem.acquire.bits := Get(
client_xact_id = UInt(0),
addr_block = UInt(0),
addr_beat = UInt(0),
addr_byte = UInt(0),
operand_size = size,
alloc = Bool(false))
io.mem.grant.ready := (state === s_recv)
when (state === s_start && io.start) {
size := initialSize
state := s_send
}
when (io.mem.acquire.fire()) { state := s_recv }
when (io.mem.grant.fire()) {
when (size === initialSize) { ref := io.mem.grant.bits.data }
size := size - UInt(1)
state := Mux(size === UInt(0), s_done, s_send)
}
io.finished := state === s_done
assert(!io.mem.grant.valid || size === initialSize ||
(io.mem.grant.bits.data & bitmask) === (ref & bitmask),
"GetMultiWidth: smaller get does not match larger get")
}
/**
* Tests that single-beat Gets across a range of memory return
* the expected data.
* @param expected The values of the data expected to be read.
* Each element is the data for one beat.
*/
class GetSweepDriver(expected: Seq[BigInt])
(implicit p: Parameters) extends Driver()(p) {
val s_start :: s_send :: s_recv :: s_done :: Nil = Enum(Bits(), 4)
val state = Reg(init = s_start)
val nReqs = expected.size
val (req_cnt, req_done) = Counter(io.mem.grant.fire(), nReqs)
when (state === s_start && io.start) { state := s_send }
when (io.mem.acquire.fire()) { state := s_recv }
when (io.mem.grant.fire()) { state := s_send }
when (req_done) { state := s_done }
val (addr_block, addr_beat) = if (nReqs > tlDataBeats) {
(req_cnt(log2Up(nReqs) - 1, tlBeatAddrBits),
req_cnt(tlBeatAddrBits - 1, 0))
} else {
(UInt(0), req_cnt)
}
val exp_data = Vec(expected.map(e => UInt(e, tlDataBits)))
io.mem.acquire.valid := (state === s_send)
io.mem.acquire.bits := Get(
client_xact_id = UInt(0),
addr_block = addr_block,
addr_beat = addr_beat)
io.mem.grant.ready := (state === s_recv)
io.finished := state === s_done
assert(!io.mem.grant.valid || io.mem.grant.bits.data === exp_data(req_cnt),
"GetSweep: data does not match expected")
}
/**
* Tests that multi-beat GetBlocks across a range of memory return
* the expected data.
* @param expected The values of the data expected to be read.
* Each element is the data for one beat.
*/
class GetBlockSweepDriver(expected: Seq[BigInt])
(implicit p: Parameters) extends Driver()(p) {
val s_start :: s_send :: s_recv :: s_done :: Nil = Enum(Bits(), 4)
val state = Reg(init = s_start)
val nReqs = ((expected.size - 1) / tlDataBeats + 1) * tlDataBeats
val (req_cnt, req_done) = Counter(io.mem.grant.fire(), nReqs)
val (addr_beat, beats_done) = Counter(io.mem.grant.fire(), tlDataBeats)
val tlBlockOffset = tlByteAddrBits + tlBeatAddrBits
val addr_block =
if (nReqs > tlDataBeats) req_cnt(log2Up(nReqs) - 1, tlBlockOffset)
else UInt(0)
io.mem.acquire.valid := (state === s_send)
io.mem.acquire.bits := GetBlock(
client_xact_id = UInt(0),
addr_block = addr_block)
io.mem.grant.ready := (state === s_recv)
io.finished := state === s_done
when (state === s_start && io.start) { state := s_send }
when (io.mem.acquire.fire()) { state := s_recv }
when (beats_done) { state := s_send }
when (req_done) { state := s_done }
val exp_data = Vec(expected.map(e => UInt(e, tlDataBits)))
assert(!io.mem.grant.valid || req_cnt >= UInt(expected.size) ||
io.mem.grant.bits.data === exp_data(req_cnt),
"GetBlockSweep: data does not match expected")
}
/**
* Tests that single-beat Puts across a range of memory persists correctly.
* @param n the number of beats to put
*/
class PutSweepDriver(val n: Int)(implicit p: Parameters) extends Driver()(p) {
val (s_idle :: s_put_req :: s_put_resp ::
s_get_req :: s_get_resp :: s_done :: Nil) = Enum(Bits(), 6)
val state = Reg(init = s_idle)
val (put_cnt, put_done) = Counter(state === s_put_resp && io.mem.grant.valid, n)
val (get_cnt, get_done) = Counter(state === s_get_resp && io.mem.grant.valid, n)
val (put_block, put_beat) = if (n > tlDataBeats) {
(put_cnt(log2Up(n) - 1, tlBeatAddrBits),
put_cnt(tlBeatAddrBits - 1, 0))
} else {
(UInt(0), put_cnt)
}
val (get_block, get_beat) = if (n > tlDataBeats) {
(get_cnt(log2Up(n) - 1, tlBeatAddrBits),
get_cnt(tlBeatAddrBits - 1, 0))
} else {
(UInt(0), get_cnt)
}
val dataRep = (tlDataBits - 1) / log2Up(n) + 1
val put_data = Fill(dataRep, put_cnt)(tlDataBits - 1, 0)
val get_data = Fill(dataRep, get_cnt)(tlDataBits - 1, 0)
io.mem.acquire.valid := state.isOneOf(s_put_req, s_get_req)
io.mem.acquire.bits := Mux(state === s_put_req,
Put(
client_xact_id = UInt(0),
addr_block = put_block,
addr_beat = put_beat,
data = put_data),
Get(
client_xact_id = UInt(0),
addr_block = get_block,
addr_beat = get_beat))
io.mem.grant.ready := state.isOneOf(s_put_resp, s_get_resp)
when (state === s_idle && io.start) { state := s_put_req }
when (state === s_put_req && io.mem.acquire.ready) { state := s_put_resp }
when (state === s_put_resp && io.mem.grant.valid) {
state := Mux(put_done, s_get_req, s_put_req)
}
when (state === s_get_req && io.mem.acquire.ready) { state := s_get_resp }
when (state === s_get_resp && io.mem.grant.valid) {
state := Mux(get_done, s_done, s_get_req)
}
io.finished := (state === s_done)
assert(!io.mem.grant.valid || !io.mem.grant.bits.hasData() ||
io.mem.grant.bits.data === get_data,
"PutSweepDriver: data does not match")
}
/**
* Tests that write-masked single-beat puts work correctly by putting
* data with steadily smaller write-masks to the same beat.
* @param minBytes the smallest number of bytes that can be in the writemask
*/
class PutMaskDriver(minBytes: Int = 1)(implicit p: Parameters) extends Driver()(p) {
val (s_idle :: s_put_req :: s_put_resp ::
s_get_req :: s_get_resp :: s_done :: Nil) = Enum(Bits(), 6)
val state = Reg(init = s_idle)
val nbytes = Reg(UInt(width = log2Up(tlWriteMaskBits) + 1))
val wmask = (UInt(1) << nbytes) - UInt(1)
val wdata = Fill(tlDataBits / 8, Wire(UInt(width = 8), init = nbytes))
// TL data bytes down to minBytes logarithmically by 2
val expected = (log2Ceil(tlDataBits / 8) to log2Ceil(minBytes) by -1)
.map(1 << _).foldLeft(UInt(0, tlDataBits)) {
// Change the lower nbytes of the value
(value, nbytes) => {
val mask = UInt((BigInt(1) << (nbytes * 8)) - BigInt(1), tlDataBits)
val wval = Fill(tlDataBits / 8, UInt(nbytes, 8))
(value & ~mask) | (wval & mask)
}
}
when (state === s_idle && io.start) {
state := s_put_req
nbytes := UInt(8)
}
when (state === s_put_req && io.mem.acquire.ready) {
state := s_put_resp
}
when (state === s_put_resp && io.mem.grant.valid) {
nbytes := nbytes >> UInt(1)
state := Mux(nbytes === UInt(minBytes), s_get_req, s_put_req)
}
when (state === s_get_req && io.mem.acquire.ready) {
state := s_get_resp
}
when (state === s_get_resp && io.mem.grant.valid) {
state := s_done
}
io.finished := (state === s_done)
io.mem.acquire.valid := state.isOneOf(s_put_req, s_get_req)
io.mem.acquire.bits := Mux(state === s_put_req,
Put(
client_xact_id = UInt(0),
addr_block = UInt(0),
addr_beat = UInt(0),
data = wdata,
wmask = Some(wmask)),
Get(
client_xact_id = UInt(0),
addr_block = UInt(0),
addr_beat = UInt(0)))
io.mem.grant.ready := state.isOneOf(s_put_resp, s_get_resp)
assert(!io.mem.grant.valid || state =/= s_get_resp ||
io.mem.grant.bits.data === expected,
"PutMask: data does not match expected")
}
class PutBlockSweepDriver(val n: Int)(implicit p: Parameters)
extends Driver()(p) {
val (s_idle :: s_put_req :: s_put_resp ::
s_get_req :: s_get_resp :: s_done :: Nil) = Enum(Bits(), 6)
val state = Reg(init = s_idle)
val (put_beat, put_beat_done) = Counter(
state === s_put_req && io.mem.acquire.ready, tlDataBeats)
val (put_cnt, put_done) = Counter(
state === s_put_resp && io.mem.grant.valid, n)
val (get_beat, get_beat_done) = Counter(
state === s_get_resp && io.mem.grant.valid, tlDataBeats)
val (get_cnt, get_done) = Counter(get_beat_done, n)
val dataRep = (tlDataBits - 1) / (log2Up(n) + tlBeatAddrBits) + 1
val put_data = Fill(dataRep, Cat(put_cnt, put_beat))(tlDataBits - 1, 0)
val get_data = Fill(dataRep, Cat(get_cnt, get_beat))(tlDataBits - 1, 0)
when (state === s_idle && io.start) { state := s_put_req }
when (put_beat_done) { state := s_put_resp }
when (state === s_put_resp && io.mem.grant.valid) {
state := Mux(put_done, s_get_req, s_put_req)
}
when (state === s_get_req && io.mem.acquire.ready) { state := s_get_resp }
when (get_beat_done) { state := Mux(get_done, s_done, s_get_req) }
val put_acquire = PutBlock(
client_xact_id = UInt(0),
addr_block = put_cnt,
addr_beat = put_beat,
data = put_data)
val get_acquire = GetBlock(
client_xact_id = UInt(0),
addr_block = get_cnt)
io.finished := (state === s_done)
io.mem.acquire.valid := state.isOneOf(s_put_req, s_get_req)
io.mem.acquire.bits := Mux(state === s_put_req, put_acquire, get_acquire)
io.mem.grant.ready := state.isOneOf(s_put_resp, s_get_resp)
assert(!io.mem.grant.valid || state =/= s_get_resp ||
io.mem.grant.bits.data === get_data,
"PutBlockSweep: data does not match expected")
}
class PutAtomicDriver(implicit p: Parameters) extends Driver()(p) {
val s_idle :: s_put :: s_atomic :: s_get :: s_done :: Nil = Enum(Bits(), 5)
val state = Reg(init = s_idle)
val sending = Reg(init = Bool(false))
val put_acquire = Put(
client_xact_id = UInt(0),
addr_block = UInt(0),
addr_beat = UInt(0),
// Put 15 in bytes 7:4
data = UInt(15L << 32),
wmask = Some(UInt(0xf0)))
val amo_acquire = PutAtomic(
client_xact_id = UInt(0),
addr_block = UInt(0),
addr_beat = UInt(0),
addr_byte = UInt(4),
atomic_opcode = M_XA_ADD,
operand_size = UInt(log2Ceil(32 / 8)),
data = UInt(3L << 32))
val get_acquire = Get(
client_xact_id = UInt(0),
addr_block = UInt(0),
addr_beat = UInt(0))
io.finished := (state === s_done)
io.mem.acquire.valid := sending
io.mem.acquire.bits := MuxLookup(state, get_acquire, Seq(
s_put -> put_acquire,
s_atomic -> amo_acquire,
s_get -> get_acquire))
io.mem.grant.ready := !sending
when (io.mem.acquire.fire()) { sending := Bool(false) }
when (state === s_idle && io.start) {
state := s_put
sending := Bool(true)
}
when (io.mem.grant.fire()) {
when (state === s_put) { sending := Bool(true); state := s_atomic }
when (state === s_atomic) { sending := Bool(true); state := s_get }
when (state === s_get) { state := s_done }
}
assert(!io.mem.grant.valid || state =/= s_get ||
io.mem.grant.bits.data(63, 32) === UInt(18))
}
class PrefetchDriver(implicit p: Parameters) extends Driver()(p) {
val s_idle :: s_put_pf :: s_get_pf :: s_done :: Nil = Enum(Bits(), 4)
val state = Reg(init = s_idle)
val sending = Reg(init = Bool(false))
when (state === s_idle) {
sending := Bool(true)
state := s_put_pf
}
when (io.mem.acquire.fire()) { sending := Bool(false) }
when (io.mem.grant.fire()) {
when (state === s_put_pf) { sending := Bool(true); state := s_get_pf }
when (state === s_get_pf) { state := s_done }
}
io.finished := (state === s_done)
io.mem.acquire.valid := sending
io.mem.acquire.bits := Mux(state === s_put_pf,
PutPrefetch(
client_xact_id = UInt(0),
addr_block = UInt(0)),
GetPrefetch(
client_xact_id = UInt(0),
addr_block = UInt(0)))
io.mem.grant.ready := !sending
}
class DriverSet(driverGen: Parameters => Seq[Driver])(implicit p: Parameters)
extends Driver()(p) {
val s_start :: s_run :: s_done :: Nil = Enum(Bits(), 3)
val state = Reg(init = s_start)
val drivers = driverGen(p)
val idx = Reg(init = UInt(0, log2Up(drivers.size)))
val finished = Wire(init = Bool(false))
when (state === s_start && io.start) { state := s_run }
when (state === s_run && finished) {
when (idx === UInt(drivers.size - 1)) { state := s_done }
idx := idx + UInt(1)
}
io.finished := state === s_done
io.mem.acquire.valid := Bool(false)
io.mem.grant.ready := Bool(false)
drivers.zipWithIndex.foreach { case (driv, i) =>
val me = idx === UInt(i)
driv.io.start := me && state === s_run
driv.io.mem.acquire.ready := io.mem.acquire.ready && me
driv.io.mem.grant.valid := io.mem.grant.valid && me
driv.io.mem.grant.bits := io.mem.grant.bits
when (me) {
io.mem.acquire.valid := driv.io.mem.acquire.valid
io.mem.acquire.bits := driv.io.mem.acquire.bits
io.mem.grant.ready := driv.io.mem.grant.ready
finished := driv.io.finished
}
}
}

View File

@ -0,0 +1,85 @@
package uncore.unittests
import Chisel._
import junctions._
import junctions.unittests._
import uncore.devices._
import uncore.tilelink._
import uncore.converters._
import cde.Parameters
class SmiConverterTest(implicit val p: Parameters) extends UnitTest
with HasTileLinkParameters {
val outermostParams = p.alterPartial({ case TLId => "Outermost" })
val smiWidth = 32
val smiDepth = 64
val tlDepth = (smiWidth * smiDepth) / tlDataBits
val smimem = Module(new SmiMem(smiWidth, smiDepth))
val conv = Module(new SmiIOTileLinkIOConverter(
smiWidth, log2Up(smiDepth))(outermostParams))
val driver = Module(new DriverSet(
(driverParams: Parameters) => {
implicit val p = driverParams
Seq(
Module(new PutSweepDriver(tlDepth)),
Module(new PutMaskDriver(smiWidth / 8)),
Module(new PutBlockSweepDriver(tlDepth / tlDataBeats)),
Module(new GetMultiWidthDriver))
})(outermostParams))
conv.io.tl <> driver.io.mem
smimem.io <> conv.io.smi
driver.io.start := io.start
io.finished := driver.io.finished
}
class ROMSlaveTest(implicit p: Parameters) extends UnitTest {
implicit val testName = "ROMSlaveTest"
val romdata = Seq(
BigInt("01234567deadbeef", 16),
BigInt("ab32fee8d00dfeed", 16))
val rombytes = romdata.map(_.toByteArray.reverse).flatten
val rom = Module(new ROMSlave(rombytes))
val driver = Module(new DriverSet(
(driverParams: Parameters) => {
implicit val p = driverParams
Seq(
Module(new GetMultiWidthDriver),
Module(new GetSweepDriver(romdata)),
Module(new GetBlockSweepDriver(romdata)))
}))
rom.io <> driver.io.mem
driver.io.start := io.start
io.finished := driver.io.finished
}
class TileLinkRAMTest(implicit val p: Parameters)
extends UnitTest with HasTileLinkParameters {
val depth = 2 * tlDataBeats
val ram = Module(new TileLinkTestRAM(depth))
val driver = Module(new DriverSet(
(driverParams: Parameters) => {
implicit val p = driverParams
Seq(
Module(new PutSweepDriver(depth)),
Module(new PutMaskDriver),
Module(new PutAtomicDriver),
Module(new PutBlockSweepDriver(depth / tlDataBeats)),
Module(new PrefetchDriver),
Module(new GetMultiWidthDriver))
}))
ram.io <> driver.io.mem
driver.io.start := io.start
io.finished := driver.io.finished
}
object UncoreUnitTests {
def apply(implicit p: Parameters): Seq[UnitTest] =
Seq(
Module(new SmiConverterTest),
Module(new ROMSlaveTest),
Module(new TileLinkRAMTest))
}

View File

@ -0,0 +1,105 @@
// See LICENSE for license details.
package uncore.util
import Chisel._
import uncore.tilelink._
import cde.Parameters
import uncore.constants._
class StoreGen(typ: UInt, addr: UInt, dat: UInt, maxSize: Int) {
val size = typ(log2Up(log2Up(maxSize)+1)-1,0)
def misaligned =
(addr & ((UInt(1) << size) - UInt(1))(log2Up(maxSize)-1,0)).orR
def mask = {
var res = UInt(1)
for (i <- 0 until log2Up(maxSize)) {
val upper = Mux(addr(i), res, UInt(0)) | Mux(size >= UInt(i+1), UInt((BigInt(1) << (1 << i))-1), UInt(0))
val lower = Mux(addr(i), UInt(0), res)
res = Cat(upper, lower)
}
res
}
protected def genData(i: Int): UInt =
if (i >= log2Up(maxSize)) dat
else Mux(size === UInt(i), Fill(1 << (log2Up(maxSize)-i), dat((8 << i)-1,0)), genData(i+1))
def data = genData(0)
def wordData = genData(2)
}
class StoreGenAligned(typ: UInt, addr: UInt, dat: UInt, maxSize: Int) extends StoreGen(typ, addr, dat, maxSize) {
override def genData(i: Int) = dat
}
class LoadGen(typ: UInt, signed: Bool, addr: UInt, dat: UInt, zero: Bool, maxSize: Int) {
private val size = new StoreGen(typ, addr, dat, maxSize).size
private def genData(logMinSize: Int): UInt = {
var res = dat
for (i <- log2Up(maxSize)-1 to logMinSize by -1) {
val pos = 8 << i
val shifted = Mux(addr(i), res(2*pos-1,pos), res(pos-1,0))
val doZero = Bool(i == 0) && zero
val zeroed = Mux(doZero, UInt(0), shifted)
res = Cat(Mux(size === UInt(i) || doZero, Fill(8*maxSize-pos, signed && zeroed(pos-1)), res(8*maxSize-1,pos)), zeroed)
}
res
}
def wordData = genData(2)
def data = genData(0)
}
class AMOALU(operandBits: Int, rhsIsAligned: Boolean = false)(implicit p: Parameters) extends Module {
require(operandBits == 32 || operandBits == 64)
val io = new Bundle {
val addr = Bits(INPUT, log2Ceil(operandBits/8))
val cmd = Bits(INPUT, M_SZ)
val typ = Bits(INPUT, log2Ceil(log2Ceil(operandBits/8) + 1))
val lhs = Bits(INPUT, operandBits)
val rhs = Bits(INPUT, operandBits)
val out = Bits(OUTPUT, operandBits)
}
val storegen =
if(rhsIsAligned) new StoreGenAligned(io.typ, io.addr, io.rhs, operandBits/8)
else new StoreGen(io.typ, io.addr, io.rhs, operandBits/8)
val rhs = storegen.wordData
val sgned = io.cmd === M_XA_MIN || io.cmd === M_XA_MAX
val max = io.cmd === M_XA_MAX || io.cmd === M_XA_MAXU
val min = io.cmd === M_XA_MIN || io.cmd === M_XA_MINU
val adder_out =
if (operandBits == 32) io.lhs + rhs
else {
val mask = ~UInt(0,64) ^ (io.addr(2) << 31)
(io.lhs & mask) + (rhs & mask)
}
val less =
if (operandBits == 32) Mux(io.lhs(31) === rhs(31), io.lhs < rhs, Mux(sgned, io.lhs(31), io.rhs(31)))
else {
val word = !io.typ(0)
val cmp_lhs = Mux(word && !io.addr(2), io.lhs(31), io.lhs(63))
val cmp_rhs = Mux(word && !io.addr(2), rhs(31), rhs(63))
val lt_lo = io.lhs(31,0) < rhs(31,0)
val lt_hi = io.lhs(63,32) < rhs(63,32)
val eq_hi = io.lhs(63,32) === rhs(63,32)
val lt = Mux(word, Mux(io.addr(2), lt_hi, lt_lo), lt_hi || eq_hi && lt_lo)
Mux(cmp_lhs === cmp_rhs, lt, Mux(sgned, cmp_lhs, cmp_rhs))
}
val out = Mux(io.cmd === M_XA_ADD, adder_out,
Mux(io.cmd === M_XA_AND, io.lhs & rhs,
Mux(io.cmd === M_XA_OR, io.lhs | rhs,
Mux(io.cmd === M_XA_XOR, io.lhs ^ rhs,
Mux(Mux(less, min, max), io.lhs,
storegen.data)))))
val wmask = FillInterleaved(8, storegen.mask)
io.out := wmask & out | ~wmask & io.lhs
}

View File

@ -0,0 +1,134 @@
package uncore.util
import Chisel._
import uncore.tilelink._
import cde.Parameters
// Produces 0-width value when counting to 1
class ZCounter(val n: Int) {
val value = Reg(init=UInt(0, log2Ceil(n)))
def inc(): Bool = {
if (n == 1) Bool(true)
else {
val wrap = value === UInt(n-1)
value := Mux(Bool(!isPow2(n)) && wrap, UInt(0), value + UInt(1))
wrap
}
}
}
object ZCounter {
def apply(n: Int) = new ZCounter(n)
def apply(cond: Bool, n: Int): (UInt, Bool) = {
val c = new ZCounter(n)
var wrap: Bool = null
when (cond) { wrap = c.inc() }
(c.value, cond && wrap)
}
}
object TwoWayCounter {
def apply(up: Bool, down: Bool, max: Int): UInt = {
val cnt = Reg(init = UInt(0, log2Up(max+1)))
when (up && !down) { cnt := cnt + UInt(1) }
when (down && !up) { cnt := cnt - UInt(1) }
cnt
}
}
class BeatCounterStatus extends Bundle {
val idx = UInt()
val done = Bool()
}
class TwoWayBeatCounterStatus extends Bundle {
val pending = Bool()
val up = new BeatCounterStatus()
val down = new BeatCounterStatus()
}
/** Utility trait containing wiring functions to keep track of how many data beats have
* been sent or recieved over a particular [[uncore.TileLinkChannel]] or pair of channels.
*
* Won't count message types that don't have data.
* Used in [[uncore.XactTracker]] and [[uncore.FinishUnit]].
*/
trait HasDataBeatCounters {
type HasBeat = TileLinkChannel with HasTileLinkBeatId
type HasId = TileLinkChannel with HasClientId
/** Returns the current count on this channel and when a message is done
* @param inc increment the counter (usually .valid or .fire())
* @param data the actual channel data
* @param beat count to return for single-beat messages
*/
def connectDataBeatCounter[S <: TileLinkChannel](inc: Bool, data: S, beat: UInt) = {
val multi = data.hasMultibeatData()
val (multi_cnt, multi_done) = Counter(inc && multi, data.tlDataBeats)
val cnt = Mux(multi, multi_cnt, beat)
val done = Mux(multi, multi_done, inc)
(cnt, done)
}
/** Counter for beats on outgoing [[chisel.DecoupledIO]] */
def connectOutgoingDataBeatCounter[T <: TileLinkChannel](
out: DecoupledIO[T],
beat: UInt = UInt(0)): (UInt, Bool) =
connectDataBeatCounter(out.fire(), out.bits, beat)
/** Returns done but not cnt. Use the addr_beat subbundle instead of cnt for beats on
* incoming channels in case of network reordering.
*/
def connectIncomingDataBeatCounter[T <: TileLinkChannel](in: DecoupledIO[T]): Bool =
connectDataBeatCounter(in.fire(), in.bits, UInt(0))._2
/** Counter for beats on incoming DecoupledIO[LogicalNetworkIO[]]s returns done */
def connectIncomingDataBeatCounterWithHeader[T <: TileLinkChannel](in: DecoupledIO[LogicalNetworkIO[T]]): Bool =
connectDataBeatCounter(in.fire(), in.bits.payload, UInt(0))._2
/** If the network might interleave beats from different messages, we need a Vec of counters,
* one for every outstanding message id that might be interleaved.
*
* @param getId mapping from Message to counter id
*/
def connectIncomingDataBeatCountersWithHeader[T <: TileLinkChannel with HasClientTransactionId](
in: DecoupledIO[LogicalNetworkIO[T]],
entries: Int,
getId: LogicalNetworkIO[T] => UInt): Vec[Bool] = {
Vec((0 until entries).map { i =>
connectDataBeatCounter(in.fire() && getId(in.bits) === UInt(i), in.bits.payload, UInt(0))._2
})
}
/** Provides counters on two channels, as well a meta-counter that tracks how many
* messages have been sent over the up channel but not yet responded to over the down channel
*
* @param status bundle of status of the counters
* @param up outgoing channel
* @param down incoming channel
* @param max max number of outstanding ups with no down
* @param beat overrides cnts on single-beat messages
* @param track whether up's message should be tracked
* @return a tuple containing whether their are outstanding messages, up's count,
* up's done, down's count, down's done
*/
def connectTwoWayBeatCounters[T <: TileLinkChannel, S <: TileLinkChannel](
status: TwoWayBeatCounterStatus,
up: DecoupledIO[T],
down: DecoupledIO[S],
max: Int = 1,
beat: UInt = UInt(0),
trackUp: T => Bool = (t: T) => Bool(true),
trackDown: S => Bool = (s: S) => Bool(true)) {
val (up_idx, up_done) = connectDataBeatCounter(up.fire() && trackUp(up.bits), up.bits, beat)
val (dn_idx, dn_done) = connectDataBeatCounter(down.fire() && trackDown(down.bits), down.bits, beat)
val cnt = TwoWayCounter(up_done, dn_done, max)
status.pending := cnt > UInt(0)
status.up.idx := up_idx
status.up.done := up_done
status.down.idx := dn_idx
status.down.done := dn_done
}
}

View File

@ -0,0 +1,56 @@
package uncore.util
import Chisel._
import uncore.tilelink._
import cde.Parameters
/** Struct for describing per-channel queue depths */
case class TileLinkDepths(acq: Int, prb: Int, rel: Int, gnt: Int, fin: Int)
/** Optionally enqueues each [[uncore.TileLinkChannel]] individually */
class TileLinkEnqueuer(depths: TileLinkDepths)(implicit p: Parameters) extends Module {
val io = new Bundle {
val client = new TileLinkIO().flip
val manager = new TileLinkIO
}
io.manager.acquire <> (if(depths.acq > 0) Queue(io.client.acquire, depths.acq) else io.client.acquire)
io.client.probe <> (if(depths.prb > 0) Queue(io.manager.probe, depths.prb) else io.manager.probe)
io.manager.release <> (if(depths.rel > 0) Queue(io.client.release, depths.rel) else io.client.release)
io.client.grant <> (if(depths.gnt > 0) Queue(io.manager.grant, depths.gnt) else io.manager.grant)
io.manager.finish <> (if(depths.fin > 0) Queue(io.client.finish, depths.fin) else io.client.finish)
}
object TileLinkEnqueuer {
def apply(in: TileLinkIO, depths: TileLinkDepths)(implicit p: Parameters): TileLinkIO = {
val t = Module(new TileLinkEnqueuer(depths))
t.io.client <> in
t.io.manager
}
def apply(in: TileLinkIO, depth: Int)(implicit p: Parameters): TileLinkIO = {
apply(in, TileLinkDepths(depth, depth, depth, depth, depth))
}
}
class ClientTileLinkEnqueuer(depths: TileLinkDepths)(implicit p: Parameters) extends Module {
val io = new Bundle {
val inner = new ClientTileLinkIO().flip
val outer = new ClientTileLinkIO
}
io.outer.acquire <> (if(depths.acq > 0) Queue(io.inner.acquire, depths.acq) else io.inner.acquire)
io.inner.probe <> (if(depths.prb > 0) Queue(io.outer.probe, depths.prb) else io.outer.probe)
io.outer.release <> (if(depths.rel > 0) Queue(io.inner.release, depths.rel) else io.inner.release)
io.inner.grant <> (if(depths.gnt > 0) Queue(io.outer.grant, depths.gnt) else io.outer.grant)
io.outer.finish <> (if(depths.fin > 0) Queue(io.inner.finish, depths.fin) else io.inner.finish)
}
object ClientTileLinkEnqueuer {
def apply(in: ClientTileLinkIO, depths: TileLinkDepths)(implicit p: Parameters): ClientTileLinkIO = {
val t = Module(new ClientTileLinkEnqueuer(depths))
t.io.inner <> in
t.io.outer
}
def apply(in: ClientTileLinkIO, depth: Int)(implicit p: Parameters): ClientTileLinkIO = {
apply(in, TileLinkDepths(depth, depth, depth, depth, depth))
}
}

View File

@ -0,0 +1,25 @@
package uncore
import Chisel._
package object util {
implicit class UIntIsOneOf(val x: UInt) extends AnyVal {
def isOneOf(s: Seq[UInt]): Bool = s.map(x === _).reduce(_||_)
def isOneOf(u1: UInt, u2: UInt*): Bool = isOneOf(u1 +: u2.toSeq)
}
implicit class SeqToAugmentedSeq[T <: Data](val x: Seq[T]) extends AnyVal {
def apply(idx: UInt): T = {
if (x.size == 1) {
x.head
} else {
val half = 1 << (log2Ceil(x.size) - 1)
val newIdx = idx & UInt(half - 1)
Mux(idx >= UInt(half), x.drop(half)(newIdx), x.take(half)(newIdx))
}
}
def asUInt(): UInt = Cat(x.map(_.asUInt).reverse)
}
}

View File

@ -0,0 +1,69 @@
// See LICENSE for license details.
package uncore.util
import Chisel._
import uncore.tilelink._
class FlowThroughSerializer[T <: Bundle with HasTileLinkData](gen: T, n: Int) extends Module {
val io = new Bundle {
val in = Decoupled(gen).flip
val out = Decoupled(gen)
val cnt = UInt(OUTPUT, log2Up(n))
val done = Bool(OUTPUT)
}
val narrowWidth = io.in.bits.data.getWidth / n
require(io.in.bits.data.getWidth % narrowWidth == 0)
if(n == 1) {
io.out <> io.in
io.cnt := UInt(0)
io.done := Bool(true)
} else {
val cnt = Reg(init=UInt(0, width = log2Up(n)))
val wrap = cnt === UInt(n-1)
val rbits = Reg{io.in.bits}
val active = Reg(init=Bool(false))
val shifter = Wire(Vec(n, Bits(width = narrowWidth)))
(0 until n).foreach {
i => shifter(i) := rbits.data((i+1)*narrowWidth-1,i*narrowWidth)
}
io.done := Bool(false)
io.cnt := cnt
io.in.ready := !active
io.out.valid := active || io.in.valid
io.out.bits := io.in.bits
when(!active && io.in.valid) {
when(io.in.bits.hasData()) {
cnt := Mux(io.out.ready, UInt(1), UInt(0))
rbits := io.in.bits
active := Bool(true)
}
io.done := !io.in.bits.hasData()
}
when(active) {
io.out.bits := rbits
io.out.bits.data := shifter(cnt)
when(io.out.ready) {
cnt := cnt + UInt(1)
when(wrap) {
cnt := UInt(0)
io.done := Bool(true)
active := Bool(false)
}
}
}
}
}
object FlowThroughSerializer {
def apply[T <: Bundle with HasTileLinkData](in: DecoupledIO[T], n: Int): DecoupledIO[T] = {
val fs = Module(new FlowThroughSerializer(in.bits, n))
fs.io.in.valid := in.valid
fs.io.in.bits := in.bits
in.ready := fs.io.in.ready
fs.io.out
}
}