1
0
rocket-chip/uncore/src/main/scala/agents/Trackers.scala
2016-06-30 18:29:51 -07:00

595 lines
22 KiB
Scala

// See LICENSE for license details.
package uncore.agents
import Chisel._
import uncore.coherence._
import uncore.tilelink._
import uncore.util._
import cde.Parameters
import scala.math.max
class TrackerAllocation extends Bundle {
val matches = Bool(OUTPUT)
val can = Bool(OUTPUT)
val should = Bool(INPUT)
}
trait HasTrackerAllocationIO extends Bundle {
val alloc = new Bundle {
val iacq = new TrackerAllocation
val irel = new TrackerAllocation
val oprb = new TrackerAllocation
}
}
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], inc: Bool = Bool(true)): UInt = {
val a = in.bits
val needs_read = (a.isGet() || a.isAtomic() || a.hasPartialWritemask()) || inc
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)))
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
}
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
state := next
}
when(io.inner.release.fire()) {
when(io.alloc.irel.should || (irel_can_merge && io.irel().first())) {
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))
} .elsewhen (irel_same_xact) {
pending_irel_data := (pending_irel_data & dropPendingBitWhenBeatHasData(io.inner.release))
}
}
io.inner.grant.valid := Vec(s_wb_req, s_wb_resp, s_inner_probe, s_busy).contains(state) &&
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
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)) {
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()) { 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 := 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
(io.alloc.irel.should || 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.toBits
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) {
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
io.alloc.irel.can := state === s_idle
io.alloc.oprb.can := Bool(false)
}
}
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_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) {
// Enqueue some metadata information that we'll use to make coherence updates with later
ignt_q.io.enq.valid := iacq_is_allocating ||
(!iacq_same_xact && 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 & dropPendingBitWhenBeatHasData(io.inner.grant)) |
addPendingBitWhenBeatHasData(io.inner.release) |
addPendingBitWhenBeatHasData(io.outer.grant) |
add_pending_bits
}
// 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 {
}