Skip to content

Commit

Permalink
[tilelink] Initial implementation for crossbar
Browse files Browse the repository at this point in the history
  • Loading branch information
OceanS2000 committed Feb 15, 2023
1 parent 7ff4c23 commit b24ddf7
Show file tree
Hide file tree
Showing 5 changed files with 525 additions and 0 deletions.
13 changes: 13 additions & 0 deletions tilelink/src/utils/Serializers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: Apache-2.0

package utils

import upickle.default.{readwriter, ReadWriter => RW}

object Serializers {
// Inject serializers for chisel3 BitSet and BitPat
implicit val bitPatSerializer: RW[chisel3.util.BitPat] =
readwriter[String].bimap(_.rawString, chisel3.util.BitPat(_))
implicit val bitSetSerializer: RW[chisel3.util.experimental.BitSet] =
readwriter[Seq[chisel3.util.BitPat]].bimap(_.terms.toSeq, chisel3.util.experimental.BitSet(_: _*))
}
110 changes: 110 additions & 0 deletions tilelink/src/xbar/TLArbiter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2016-2017 SiFive, Inc. See LICENSE.SiFive for details

package xbar

import chisel3.util._
import chisel3._

import bundle._
import utils.rightOR

import upickle.default.{macroRW, ReadWriter => RW}

sealed trait TLArbiterPolicy
object TLArbiterPolicy {
case object Priority extends TLArbiterPolicy {
implicit val rw: RW[this.type] = macroRW
}
case object RoundRobin extends TLArbiterPolicy {
implicit val rw: RW[this.type] = macroRW
}
implicit val rw: RW[TLArbiterPolicy] = RW.merge(Priority.rw, RoundRobin.rw)
}

case class TLArbiterParameter(
policy: TLArbiterPolicy,
inputLinkParameters: Seq[TLChannelParameter],
outputLinkParameter: TLChannelParameter)
extends chisel3.experimental.SerializableModuleParameter
object TLArbiterParameter {
implicit val rw: RW[TLArbiterParameter] = macroRW
}

class TLArbiter(val parameter: TLArbiterParameter)
extends Module
with chisel3.experimental.SerializableModule[TLArbiterParameter] {

// (width, valid_s, select) => ready_s
val policyImpl: (Integer, UInt, Bool) => UInt = {
parameter.policy match {
case TLArbiterPolicy.Priority =>
(width, valids, _) => (~(scanLeftOr(valids) << 1)(width - 1, 0)).asUInt
case TLArbiterPolicy.RoundRobin =>
(width, valids, select) =>
if (width == 1) 1.U(1.W)
else {
val valid = valids(width - 1, 0)
assert(valid === valids)
val mask = RegInit(((BigInt(1) << width) - 1).U(width - 1, 0))
val filter = Cat(valid & (~mask).asUInt, valid)
val unready = (rightOR(filter, width * 2, width) >> 1).asUInt | (mask << width).asUInt
val readys = (~((unready >> width).asUInt & unready(width - 1, 0))).asUInt
when(select && valid.orR) {
mask := scanLeftOr(readys & valid)
}
readys(width - 1, 0)
}
}
}

val sink = IO(
Flipped(
DecoupledIO(TLChannelParameter.bundle(parameter.outputLinkParameter))
)
)
val sources = parameter.inputLinkParameters.map(p => IO(DecoupledIO(TLChannelParameter.bundle(p))))

if (parameter.inputLinkParameters.isEmpty) {
sink.valid := false.B
sink.bits := DontCare
} else if (parameter.inputLinkParameters.size == 1) {
sink <> sources.head
} else {
val beatsIn = sources.map(s => TLLink.numBeatsMinus1(s.bits))

val beatsLeft = RegInit(0.U)
val idle = beatsLeft === 0.U
val latch = idle && sink.ready // TODO: winner (if any) claims sink

// Who wants access to the sink?
val valids = sources.map(_.valid)

val readys = VecInit(policyImpl(valids.size, Cat(valids.reverse).asUInt, latch).asBools)
val winner = VecInit(readys.zip(valids).map { case (r, v) => r && v })

// confirm policy make sense
require(readys.size == valids.size)

// Never two winners
val prefixOR = winner.scanLeft(false.B)(_ || _).init
assert(prefixOR.zip(winner).map { case (p, w) => !p || !w }.reduce { _ && _ })
// If there was any request, there is a winner
assert(!valids.reduce(_ || _) || winner.reduce(_ || _))

// Track remaining beats
val maskedBeats = winner.zip(beatsIn).map { case (w, b) => Mux(w, b, 0.U) }
val initBeats = maskedBeats.reduce(_ | _) // no winner => 0 beats
beatsLeft := Mux(latch, initBeats, beatsLeft - sink.fire)

// The one-hot source granted access in the previous cycle
val state = RegInit(VecInit(Seq.fill(sources.size)(false.B)))
val muxState = Mux(idle, winner, state)
state := muxState

val allowed = Mux(idle, readys, state)
sources.zip(allowed).foreach { case (s, r) => s.ready := sink.ready && r }
sink.valid := Mux(idle, valids.reduce(_ || _), Mux1H(state, valids))
sink.bits :<= Mux1H(state, sources.map(_.bits))
}
}
261 changes: 261 additions & 0 deletions tilelink/src/xbar/TLCrossBar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2016-2017 SiFive, Inc. See LICENSE.SiFive for details

package xbar

import chisel3._
import chisel3.util._
import chisel3.util.experimental.BitSet
import chisel3.util.experimental.decode.decoder

import bundle._

object TLCrossBar {
private def fanout(
input: DecoupledIO[TLChannel],
select: Seq[Bool]
): Seq[DecoupledIO[TLChannel]] = {
val filtered = Wire(Vec(select.size, chiselTypeOf(input)))
filtered.zip(select).foreach { case (chan, selected) =>
chan.bits := input.bits
chan.valid := input.valid && (selected || (select.size == 1).B)
}
input.ready := Mux1H(select, filtered.map(_.ready))
filtered
}
}

class TLCrossBar(val parameter: TLCrossBarParameter)
extends Module
with chisel3.experimental.SerializableModule[TLCrossBarParameter] {

val masterLinksIO = parameter.masters.map(_.linkParameter).map { link =>
IO(Flipped(new TLLink(link)))
}
val slaveLinksIO = parameter.slaves.map(_.linkParameter).map { link =>
IO(new TLLink(link))
}

val masterLinksRemapped = Wire(
Vec(parameter.masters.size, new TLLink(parameter.commonLinkParameter))
)
val slaveLinksRemapped = Wire(
Vec(parameter.slaves.size, new TLLink(parameter.commonLinkParameter))
)

private def trim(id: UInt, size: Int): UInt = id(log2Ceil(size) - 1, 0)

// id remapping
parameter.adReachableIO
.lazyZip(masterLinksIO)
.lazyZip(masterLinksRemapped)
.lazyZip(parameter.srcIdRemapTable)
.foreach { case (connects, io, remapped, range) =>
if (connects.exists(x => x)) {
remapped.a :<>= io.a
remapped.a.bits.source := io.a.bits.source | range.start.U

io.d :<>= remapped.d
io.d.bits.source := trim(remapped.d.bits.source, range.size)
} else {
remapped.a.valid := false.B
remapped.a.bits := DontCare
io.a.ready := false.B
io.a.bits := DontCare

io.d.valid := false.B
io.d.bits := DontCare
remapped.d.ready := false.B
remapped.d.bits := DontCare
}
}
parameter.bceReachableIO
.lazyZip(masterLinksIO)
.lazyZip(masterLinksRemapped)
.lazyZip(parameter.srcIdRemapTable)
.foreach { case (connects, io, remapped, range) =>
if (connects.exists(x => x)) {
io.b :<>= remapped.b
io.b.bits.source := trim(remapped.b.bits.source, range.size)

remapped.c :<>= io.c
remapped.c.bits.source := io.c.bits.source | range.start.U

remapped.e :<>= io.e
} else {
io.b.valid := false.B
io.b.bits := DontCare
remapped.b.ready := false.B
remapped.b.bits := DontCare

remapped.c.valid := false.B
remapped.c.bits := DontCare
io.c.ready := false.B
io.c.bits := DontCare

remapped.e.valid := false.B
remapped.e.bits := DontCare
io.e.ready := false.B
io.e.bits := DontCare
}
}

parameter.adReachableOI
.lazyZip(slaveLinksIO)
.lazyZip(slaveLinksRemapped)
.lazyZip(parameter.sinkIdRemapTable)
.foreach { case (connects, io, remapped, range) =>
if (connects.exists(x => x)) {
remapped.a :<>= io.a

io.d :<>= remapped.d
io.d.bits.sink := trim(remapped.d.bits.sink, range.size)
} else {
remapped.a.valid := false.B
remapped.a.bits := DontCare
io.a.ready := false.B
io.a.bits := DontCare

io.d.valid := false.B
io.d.bits := DontCare
remapped.d.ready := false.B
remapped.d.bits := DontCare
}
}

parameter.bceReachableOI
.lazyZip(slaveLinksIO)
.lazyZip(slaveLinksRemapped)
.lazyZip(parameter.sinkIdRemapTable)
.foreach { case (connects, io, remapped, range) =>
if (connects.exists(x => x)) {
io.b :<>= remapped.b
remapped.c :<>= io.c
remapped.e :<>= io.e

remapped.e.bits.sink := io.e.bits.sink | range.start.U
} else {
io.b.valid := false.B
io.b.bits := DontCare
remapped.b.ready := false.B
remapped.b.bits := DontCare

remapped.c.valid := false.B
remapped.c.bits := DontCare
io.c.ready := false.B
io.c.bits := DontCare

remapped.e.valid := false.B
remapped.e.bits := DontCare
io.e.ready := false.B
io.e.bits := DontCare
}
}

private def unique(x: Vector[Boolean]) = x.count(x => x) <= 1
private def filter[T](data: Seq[T], mask: Seq[Boolean]) = data.zip(mask).filter(_._2).map(_._1)

// Based on input=>output connectivity, create per-input minimal address decode circuits
val addressableOs = (parameter.adReachableIO ++ parameter.bceReachableIO).distinct
val outputPortFns: Map[Vector[Boolean], UInt => Seq[Bool]] =
addressableOs.map { addressable =>
if (unique(addressable)) {
(addressable, (_: UInt) => addressable.map(_.B))
} else {
val ports = parameter.slaves.map(_.addressRange)
val maxBits = log2Ceil(1 + ports.map(_.getWidth).max)
val maskedPorts = ports.zip(addressable).map {
case (port, true) => port.intersect(BitPat.dontCare(maxBits))
case (_, false) => BitSet.empty
}
//noinspection RedundantDefaultArgument
(addressable, (addr: UInt) => decoder.bitset(addr, maskedPorts, errorBit = false).asBools)
}
}.toMap

val addressA = masterLinksRemapped.map(_.a.bits.address)
val addressC = masterLinksRemapped.map(_.c.bits.address)

val requestAIO = parameter.adReachableIO.zip(addressA).map { case (c, a) => outputPortFns(c)(a) }
val requestCIO = parameter.bceReachableIO.zip(addressC).map { case (c, a) => outputPortFns(c)(a) }
val requestBOI = slaveLinksRemapped.map { o => parameter.srcIdRemapTable.map { i => i.contains(o.b.bits.source) } }
val requestDOI = slaveLinksRemapped.map { o => parameter.srcIdRemapTable.map { i => i.contains(o.d.bits.source) } }
val requestEIO = masterLinksRemapped.map { i => parameter.sinkIdRemapTable.map { o => o.contains(i.e.bits.sink) } }

val portsAOI = masterLinksRemapped.zip(requestAIO).map { case (i, r) => TLCrossBar.fanout(i.a, r) }.transpose
val portsBIO = slaveLinksRemapped.zip(requestBOI).map { case (o, r) => TLCrossBar.fanout(o.b, r) }.transpose
val portsCOI = masterLinksRemapped.zip(requestCIO).map { case (i, r) => TLCrossBar.fanout(i.c, r) }.transpose
val portsDIO = slaveLinksRemapped.zip(requestDOI).map { case (o, r) => TLCrossBar.fanout(o.d, r) }.transpose
val portsEOI = masterLinksRemapped.zip(requestEIO).map { case (i, r) => TLCrossBar.fanout(i.e, r) }.transpose

slaveLinksRemapped.lazyZip(portsAOI).lazyZip(parameter.adReachableOI).foreach { case (portO, portI, reachable) =>
val arbiter = Module(
new TLArbiter(
TLArbiterParameter(
policy = parameter.arbitrationPolicy,
inputLinkParameters = filter(portI.map(_.bits.parameter), reachable),
outputLinkParameter = portO.a.bits.parameter
)
)
)
arbiter.sources.zip(filter(portI, reachable)).foreach { case (o, i) => o :<>= i }
portO.a :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelA]]
filter(portI, reachable.map(!_)).foreach { i => i.ready := false.B }
}
masterLinksRemapped.lazyZip(portsBIO).lazyZip(parameter.bceReachableIO).foreach { case (portI, portO, reachable) =>
val arbiter = Module(
new TLArbiter(
TLArbiterParameter(
policy = parameter.arbitrationPolicy,
inputLinkParameters = filter(portO.map(_.bits.parameter), reachable),
outputLinkParameter = portI.b.bits.parameter
)
)
)
arbiter.sources.zip(filter(portO, reachable)).foreach { case (i, o) => i :<>= o }
portI.b :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelB]]
filter(portO, reachable.map(!_)).foreach { o => o.ready := false.B }
}
slaveLinksRemapped.lazyZip(portsCOI).lazyZip(parameter.bceReachableOI).foreach { case (portO, portI, reachable) =>
val arbiter = Module(
new TLArbiter(
TLArbiterParameter(
policy = parameter.arbitrationPolicy,
inputLinkParameters = filter(portI.map(_.bits.parameter), reachable),
outputLinkParameter = portO.c.bits.parameter
)
)
)
arbiter.sources.zip(filter(portI, reachable)).foreach { case (o, i) => o :<>= i }
portO.c :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelC]]
filter(portI, reachable.map(!_)).foreach { i => i.ready := false.B }
}
masterLinksRemapped.lazyZip(portsDIO).lazyZip(parameter.adReachableIO).foreach { case (portI, portO, reachable) =>
val arbiter = Module(
new TLArbiter(
TLArbiterParameter(
policy = parameter.arbitrationPolicy,
inputLinkParameters = filter(portO.map(_.bits.parameter), reachable),
outputLinkParameter = portI.d.bits.parameter
)
)
)
arbiter.sources.zip(filter(portO, reachable)).foreach { case (i, o) => i :<>= o }
portI.d :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelD]]
filter(portO, reachable.map(!_)).foreach { o => o.ready := false.B }
}
slaveLinksRemapped.lazyZip(portsEOI).lazyZip(parameter.bceReachableOI).foreach { case (portO, portI, reachable) =>
val arbiter = Module(
new TLArbiter(
TLArbiterParameter(
policy = parameter.arbitrationPolicy,
inputLinkParameters = filter(portI.map(_.bits.parameter), reachable),
outputLinkParameter = portO.e.bits.parameter
)
)
)
arbiter.sources.zip(filter(portI, reachable)).foreach { case (o, i) => o :<>= i }
portO.e :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelE]]
filter(portI, reachable.map(!_)).foreach { i => i.ready := false.B }
}
}
Loading

0 comments on commit b24ddf7

Please sign in to comment.