1
0

631 lines
21 KiB
Scala
Raw Normal View History

/// See LICENSE for license details.
package junctions
import Chisel._
import scala.math.max
import scala.collection.mutable.ArraySeq
2015-10-21 18:15:46 -07:00
import cde.{Parameters, Field}
2015-10-05 20:33:55 -07:00
case object NastiKey extends Field[NastiParameters]
case class NastiParameters(dataBits: Int, addrBits: Int, idBits: Int)
trait HasNastiParameters {
implicit val p: Parameters
2015-10-05 20:33:55 -07:00
val external = p(NastiKey)
val nastiXDataBits = external.dataBits
val nastiWStrobeBits = nastiXDataBits / 8
val nastiXAddrBits = external.addrBits
val nastiWIdBits = external.idBits
val nastiRIdBits = external.idBits
val nastiXIdBits = max(nastiWIdBits, nastiRIdBits)
val nastiXUserBits = 1
val nastiAWUserBits = nastiXUserBits
val nastiWUserBits = nastiXUserBits
val nastiBUserBits = nastiXUserBits
val nastiARUserBits = nastiXUserBits
val nastiRUserBits = nastiXUserBits
val nastiXLenBits = 8
val nastiXSizeBits = 3
val nastiXBurstBits = 2
val nastiXCacheBits = 4
val nastiXProtBits = 3
val nastiXQosBits = 4
val nastiXRegionBits = 4
val nastiXRespBits = 2
def bytesToXSize(bytes: UInt) = MuxLookup(bytes, UInt("b111"), Array(
UInt(1) -> UInt(0),
UInt(2) -> UInt(1),
UInt(4) -> UInt(2),
UInt(8) -> UInt(3),
UInt(16) -> UInt(4),
UInt(32) -> UInt(5),
UInt(64) -> UInt(6),
UInt(128) -> UInt(7)))
}
2015-10-05 20:33:55 -07:00
abstract class NastiModule(implicit val p: Parameters) extends Module
with HasNastiParameters
abstract class NastiBundle(implicit val p: Parameters) extends ParameterizedBundle()(p)
2015-10-02 15:37:41 -07:00
with HasNastiParameters
abstract class NastiChannel(implicit p: Parameters) extends NastiBundle()(p)
abstract class NastiMasterToSlaveChannel(implicit p: Parameters) extends NastiChannel()(p)
abstract class NastiSlaveToMasterChannel(implicit p: Parameters) extends NastiChannel()(p)
trait HasNastiMetadata extends HasNastiParameters {
val addr = UInt(width = nastiXAddrBits)
val len = UInt(width = nastiXLenBits)
val size = UInt(width = nastiXSizeBits)
val burst = UInt(width = nastiXBurstBits)
val lock = Bool()
val cache = UInt(width = nastiXCacheBits)
val prot = UInt(width = nastiXProtBits)
val qos = UInt(width = nastiXQosBits)
val region = UInt(width = nastiXRegionBits)
}
trait HasNastiData extends HasNastiParameters {
val data = UInt(width = nastiXDataBits)
val last = Bool()
}
2015-10-05 20:33:55 -07:00
class NastiIO(implicit val p: Parameters) extends ParameterizedBundle()(p) {
val aw = Decoupled(new NastiWriteAddressChannel)
val w = Decoupled(new NastiWriteDataChannel)
val b = Decoupled(new NastiWriteResponseChannel).flip
val ar = Decoupled(new NastiReadAddressChannel)
val r = Decoupled(new NastiReadDataChannel).flip
}
class NastiAddressChannel(implicit p: Parameters) extends NastiMasterToSlaveChannel()(p)
with HasNastiMetadata
class NastiResponseChannel(implicit p: Parameters) extends NastiSlaveToMasterChannel()(p) {
val resp = UInt(width = nastiXRespBits)
}
class NastiWriteAddressChannel(implicit p: Parameters) extends NastiAddressChannel()(p) {
val id = UInt(width = nastiWIdBits)
val user = UInt(width = nastiAWUserBits)
}
class NastiWriteDataChannel(implicit p: Parameters) extends NastiMasterToSlaveChannel()(p)
with HasNastiData {
val strb = UInt(width = nastiWStrobeBits)
val user = UInt(width = nastiWUserBits)
}
class NastiWriteResponseChannel(implicit p: Parameters) extends NastiResponseChannel()(p) {
val id = UInt(width = nastiWIdBits)
val user = UInt(width = nastiBUserBits)
}
class NastiReadAddressChannel(implicit p: Parameters) extends NastiAddressChannel()(p) {
val id = UInt(width = nastiRIdBits)
val user = UInt(width = nastiARUserBits)
}
class NastiReadDataChannel(implicit p: Parameters) extends NastiResponseChannel()(p)
with HasNastiData {
val id = UInt(width = nastiRIdBits)
val user = UInt(width = nastiRUserBits)
}
object NastiWriteAddressChannel {
def apply(id: UInt, addr: UInt, size: UInt, len: UInt = UInt(0))(implicit p: Parameters) = {
val aw = Wire(new NastiWriteAddressChannel)
aw.id := id
aw.addr := addr
aw.len := len
aw.size := size
aw.burst := UInt("b01")
aw.lock := Bool(false)
aw.cache := UInt("b0000")
aw.prot := UInt("b000")
aw.qos := UInt("b0000")
aw.region := UInt("b0000")
aw.user := UInt(0)
aw
}
}
object NastiReadAddressChannel {
def apply(id: UInt, addr: UInt, size: UInt, len: UInt = UInt(0))(implicit p: Parameters) = {
val ar = Wire(new NastiReadAddressChannel)
ar.id := id
ar.addr := addr
ar.len := len
ar.size := size
ar.burst := UInt("b01")
ar.lock := Bool(false)
ar.cache := UInt(0)
ar.prot := UInt(0)
ar.qos := UInt(0)
ar.region := UInt(0)
ar.user := UInt(0)
ar
}
}
object NastiWriteDataChannel {
def apply(data: UInt, last: Bool = Bool(true))(implicit p: Parameters): NastiWriteDataChannel = {
val w = Wire(new NastiWriteDataChannel)
w.strb := Fill(w.nastiWStrobeBits, UInt(1, 1))
w.data := data
w.last := last
w.user := UInt(0)
w
}
def apply(data: UInt, strb: UInt, last: Bool)
(implicit p: Parameters): NastiWriteDataChannel = {
val w = apply(data, last)
w.strb := strb
w
}
}
object NastiReadDataChannel {
def apply(id: UInt, data: UInt, last: Bool = Bool(true), resp: UInt = UInt(0))(
implicit p: Parameters) = {
val r = Wire(new NastiReadDataChannel)
r.id := id
r.data := data
r.last := last
r.resp := resp
r.user := UInt(0)
r
}
}
object NastiWriteResponseChannel {
def apply(id: UInt, resp: UInt = UInt(0))(implicit p: Parameters) = {
val b = Wire(new NastiWriteResponseChannel)
b.id := id
b.resp := resp
b.user := UInt(0)
b
}
}
2015-10-05 20:33:55 -07:00
class MemIONastiIOConverter(cacheBlockOffsetBits: Int)(implicit p: Parameters) extends MIFModule
with HasNastiParameters {
val io = new Bundle {
val nasti = (new NastiIO).flip
val mem = new MemIO
}
require(mifDataBits == nastiXDataBits, "Data sizes between LLC and MC don't agree")
val (mif_cnt_out, mif_wrap_out) = Counter(io.mem.resp.fire(), mifDataBeats)
assert(!io.nasti.aw.valid || io.nasti.aw.bits.size === UInt(log2Up(mifDataBits/8)),
"Nasti data size does not match MemIO data size")
assert(!io.nasti.ar.valid || io.nasti.ar.bits.size === UInt(log2Up(mifDataBits/8)),
"Nasti data size does not match MemIO data size")
assert(!io.nasti.aw.valid || io.nasti.aw.bits.len === UInt(mifDataBeats - 1),
"Nasti length does not match number of MemIO beats")
assert(!io.nasti.ar.valid || io.nasti.ar.bits.len === UInt(mifDataBeats - 1),
"Nasti length does not match number of MemIO beats")
// according to the spec, we can't send b until the last transfer on w
val b_ok = Reg(init = Bool(true))
when (io.nasti.aw.fire()) { b_ok := Bool(false) }
when (io.nasti.w.fire() && io.nasti.w.bits.last) { b_ok := Bool(true) }
val id_q = Module(new Queue(UInt(width = nastiWIdBits), 2))
2015-10-19 21:43:59 -07:00
id_q.io.enq.valid := io.nasti.aw.valid && io.mem.req_cmd.ready
id_q.io.enq.bits := io.nasti.aw.bits.id
id_q.io.deq.ready := io.nasti.b.ready && b_ok
io.mem.req_cmd.bits.addr := Mux(io.nasti.aw.valid, io.nasti.aw.bits.addr, io.nasti.ar.bits.addr) >>
UInt(cacheBlockOffsetBits)
io.mem.req_cmd.bits.tag := Mux(io.nasti.aw.valid, io.nasti.aw.bits.id, io.nasti.ar.bits.id)
io.mem.req_cmd.bits.rw := io.nasti.aw.valid
io.mem.req_cmd.valid := (io.nasti.aw.valid && id_q.io.enq.ready) || io.nasti.ar.valid
io.nasti.ar.ready := io.mem.req_cmd.ready && !io.nasti.aw.valid
io.nasti.aw.ready := io.mem.req_cmd.ready && id_q.io.enq.ready
io.nasti.b.valid := id_q.io.deq.valid && b_ok
io.nasti.b.bits.id := id_q.io.deq.bits
io.nasti.b.bits.resp := UInt(0)
io.nasti.w.ready := io.mem.req_data.ready
io.mem.req_data.valid := io.nasti.w.valid
io.mem.req_data.bits.data := io.nasti.w.bits.data
assert(!io.nasti.w.valid || io.nasti.w.bits.strb.andR, "MemIO must write full cache line")
io.nasti.r.valid := io.mem.resp.valid
io.nasti.r.bits.data := io.mem.resp.bits.data
io.nasti.r.bits.last := mif_wrap_out
io.nasti.r.bits.id := io.mem.resp.bits.tag
io.nasti.r.bits.resp := UInt(0)
io.mem.resp.ready := io.nasti.r.ready
}
/** Arbitrate among arbN masters requesting to a single slave */
2015-10-05 20:33:55 -07:00
class NastiArbiter(val arbN: Int)(implicit p: Parameters) extends NastiModule {
val io = new Bundle {
val master = Vec(new NastiIO, arbN).flip
val slave = new NastiIO
}
if (arbN > 1) {
val arbIdBits = log2Up(arbN)
val ar_arb = Module(new RRArbiter(new NastiReadAddressChannel, arbN))
val aw_arb = Module(new RRArbiter(new NastiWriteAddressChannel, arbN))
val slave_r_arb_id = io.slave.r.bits.id(arbIdBits - 1, 0)
val slave_b_arb_id = io.slave.b.bits.id(arbIdBits - 1, 0)
val w_chosen = Reg(UInt(width = arbIdBits))
val w_done = Reg(init = Bool(true))
when (aw_arb.io.out.fire()) {
w_chosen := aw_arb.io.chosen
w_done := Bool(false)
}
when (io.slave.w.fire() && io.slave.w.bits.last) {
w_done := Bool(true)
}
for (i <- 0 until arbN) {
val m_ar = io.master(i).ar
val m_aw = io.master(i).aw
val m_r = io.master(i).r
val m_b = io.master(i).b
val a_ar = ar_arb.io.in(i)
val a_aw = aw_arb.io.in(i)
val m_w = io.master(i).w
a_ar <> m_ar
a_ar.bits.id := Cat(m_ar.bits.id, UInt(i, arbIdBits))
a_aw <> m_aw
a_aw.bits.id := Cat(m_aw.bits.id, UInt(i, arbIdBits))
m_r.valid := io.slave.r.valid && slave_r_arb_id === UInt(i)
m_r.bits := io.slave.r.bits
m_r.bits.id := io.slave.r.bits.id >> UInt(arbIdBits)
m_b.valid := io.slave.b.valid && slave_b_arb_id === UInt(i)
m_b.bits := io.slave.b.bits
m_b.bits.id := io.slave.b.bits.id >> UInt(arbIdBits)
m_w.ready := io.slave.w.ready && w_chosen === UInt(i) && !w_done
}
io.slave.r.ready := io.master(slave_r_arb_id).r.ready
io.slave.b.ready := io.master(slave_b_arb_id).b.ready
io.slave.w.bits := io.master(w_chosen).w.bits
io.slave.w.valid := io.master(w_chosen).w.valid && !w_done
io.slave.ar <> ar_arb.io.out
2015-09-25 11:03:24 -07:00
io.slave.aw.bits <> aw_arb.io.out.bits
io.slave.aw.valid := aw_arb.io.out.valid && w_done
aw_arb.io.out.ready := io.slave.aw.ready && w_done
} else { io.slave <> io.master.head }
}
/** A slave that send decode error for every request it receives */
2015-10-05 20:33:55 -07:00
class NastiErrorSlave(implicit p: Parameters) extends NastiModule {
val io = (new NastiIO).flip
2015-09-18 09:42:41 -07:00
when (io.ar.fire()) { printf("Invalid read address %x\n", io.ar.bits.addr) }
when (io.aw.fire()) { printf("Invalid write address %x\n", io.aw.bits.addr) }
val r_queue = Module(new Queue(new NastiReadAddressChannel, 2))
r_queue.io.enq <> io.ar
val responding = Reg(init = Bool(false))
val beats_left = Reg(init = UInt(0, nastiXLenBits))
when (!responding && r_queue.io.deq.valid) {
responding := Bool(true)
beats_left := r_queue.io.deq.bits.len
}
io.r.valid := r_queue.io.deq.valid && responding
io.r.bits.id := r_queue.io.deq.bits.id
io.r.bits.data := UInt(0)
io.r.bits.resp := Bits("b11")
io.r.bits.last := beats_left === UInt(0)
r_queue.io.deq.ready := io.r.fire() && io.r.bits.last
when (io.r.fire()) {
when (beats_left === UInt(0)) {
responding := Bool(false)
} .otherwise {
2015-11-26 12:57:04 -08:00
beats_left := beats_left - UInt(1)
}
}
val draining = Reg(init = Bool(false))
io.w.ready := draining
when (io.aw.fire()) { draining := Bool(true) }
when (io.w.fire() && io.w.bits.last) { draining := Bool(false) }
val b_queue = Module(new Queue(UInt(width = nastiWIdBits), 2))
b_queue.io.enq.valid := io.aw.valid && !draining
b_queue.io.enq.bits := io.aw.bits.id
io.aw.ready := b_queue.io.enq.ready && !draining
io.b.valid := b_queue.io.deq.valid && !draining
io.b.bits.id := b_queue.io.deq.bits
io.b.bits.resp := Bits("b11")
b_queue.io.deq.ready := io.b.ready && !draining
}
/** Take a single Nasti master and route its requests to various slaves
* @param nSlaves the number of slaves
* @param routeSel a function which takes an address and produces
* a one-hot encoded selection of the slave to write to */
class NastiRouter(nSlaves: Int, routeSel: UInt => UInt)(implicit p: Parameters)
extends NastiModule {
val io = new Bundle {
val master = (new NastiIO).flip
val slave = Vec(new NastiIO, nSlaves)
}
val ar_route = routeSel(io.master.ar.bits.addr)
val aw_route = routeSel(io.master.aw.bits.addr)
var ar_ready = Bool(false)
var aw_ready = Bool(false)
var w_ready = Bool(false)
io.slave.zipWithIndex.foreach { case (s, i) =>
s.ar.valid := io.master.ar.valid && ar_route(i)
s.ar.bits := io.master.ar.bits
ar_ready = ar_ready || (s.ar.ready && ar_route(i))
s.aw.valid := io.master.aw.valid && aw_route(i)
s.aw.bits := io.master.aw.bits
aw_ready = aw_ready || (s.aw.ready && aw_route(i))
val chosen = Reg(init = Bool(false))
when (s.aw.fire()) { chosen := Bool(true) }
when (s.w.fire() && s.w.bits.last) { chosen := Bool(false) }
s.w.valid := io.master.w.valid && chosen
s.w.bits := io.master.w.bits
w_ready = w_ready || (s.w.ready && chosen)
}
val r_invalid = !ar_route.orR
val w_invalid = !aw_route.orR
val err_slave = Module(new NastiErrorSlave)
err_slave.io.ar.valid := r_invalid && io.master.ar.valid
err_slave.io.ar.bits := io.master.ar.bits
err_slave.io.aw.valid := w_invalid && io.master.aw.valid
err_slave.io.aw.bits := io.master.aw.bits
err_slave.io.w.valid := io.master.w.valid
err_slave.io.w.bits := io.master.w.bits
io.master.ar.ready := ar_ready || (r_invalid && err_slave.io.ar.ready)
io.master.aw.ready := aw_ready || (w_invalid && err_slave.io.aw.ready)
io.master.w.ready := w_ready || err_slave.io.w.ready
val b_arb = Module(new RRArbiter(new NastiWriteResponseChannel, nSlaves + 1))
2015-10-20 18:36:19 -07:00
val r_arb = Module(new JunctionsPeekingArbiter(
new NastiReadDataChannel, nSlaves + 1,
// we can unlock if it's the last beat
(r: NastiReadDataChannel) => r.last))
for (i <- 0 until nSlaves) {
b_arb.io.in(i) <> io.slave(i).b
r_arb.io.in(i) <> io.slave(i).r
}
b_arb.io.in(nSlaves) <> err_slave.io.b
r_arb.io.in(nSlaves) <> err_slave.io.r
io.master.b <> b_arb.io.out
io.master.r <> r_arb.io.out
}
/** Crossbar between multiple Nasti masters and slaves
* @param nMasters the number of Nasti masters
* @param nSlaves the number of Nasti slaves
* @param routeSel a function selecting the slave to route an address to */
class NastiCrossbar(nMasters: Int, nSlaves: Int, routeSel: UInt => UInt)
2015-10-05 20:33:55 -07:00
(implicit p: Parameters) extends NastiModule {
val io = new Bundle {
val masters = Vec(new NastiIO, nMasters).flip
val slaves = Vec(new NastiIO, nSlaves)
}
if (nMasters == 1) {
val router = Module(new NastiRouter(nSlaves, routeSel))
router.io.master <> io.masters.head
io.slaves <> router.io.slave
} else {
val routers = Vec.fill(nMasters) { Module(new NastiRouter(nSlaves, routeSel)).io }
val arbiters = Vec.fill(nSlaves) { Module(new NastiArbiter(nMasters)).io }
for (i <- 0 until nMasters) {
routers(i).master <> io.masters(i)
}
for (i <- 0 until nSlaves) {
arbiters(i).master <> Vec(routers.map(r => r.slave(i)))
io.slaves(i) <> arbiters(i).slave
}
}
}
object NastiMultiChannelRouter {
def apply(master: NastiIO, nChannels: Int)(implicit p: Parameters): Vec[NastiIO] = {
if (nChannels == 1) {
Vec(master)
} else {
val dataBytes = p(MIFDataBits) * p(MIFDataBeats) / 8
2015-10-26 12:23:03 -07:00
val selOffset = log2Up(dataBytes)
val selBits = log2Ceil(nChannels)
2015-10-26 12:23:03 -07:00
// Consecutive blocks route to alternating channels
val routeSel = (addr: UInt) => {
2015-10-26 12:23:03 -07:00
val sel = addr(selOffset + selBits - 1, selOffset)
Vec.tabulate(nChannels)(i => sel === UInt(i)).toBits
}
val router = Module(new NastiRouter(nChannels, routeSel))
router.io.master <> master
router.io.slave
}
}
}
class NastiInterconnectIO(val nMasters: Int, val nSlaves: Int)
(implicit p: Parameters) extends Bundle {
/* This is a bit confusing. The interconnect is a slave to the masters and
* a master to the slaves. Hence why the declarations seem to be backwards. */
val masters = Vec(new NastiIO, nMasters).flip
val slaves = Vec(new NastiIO, nSlaves)
override def cloneType =
new NastiInterconnectIO(nMasters, nSlaves).asInstanceOf[this.type]
}
2015-10-05 20:33:55 -07:00
abstract class NastiInterconnect(implicit p: Parameters) extends NastiModule()(p) {
val nMasters: Int
val nSlaves: Int
lazy val io = new NastiInterconnectIO(nMasters, nSlaves)
}
class NastiRecursiveInterconnect(
val nMasters: Int,
val nSlaves: Int,
addrmap: AddrMap,
base: BigInt = 0)
(implicit p: Parameters) extends NastiInterconnect()(p) {
var lastEnd = base
var slaveInd = 0
val levelSize = addrmap.size
val realAddrMap = new ArraySeq[(BigInt, BigInt)](addrmap.size)
addrmap.zipWithIndex.foreach { case (AddrMapEntry(name, startOpt, region), i) =>
val start = startOpt.getOrElse(lastEnd)
val size = region.size
realAddrMap(i) = (start, size)
lastEnd = start + size
require(bigIntPow2(size),
s"Region $name size $size is not a power of 2")
require(start % size == 0,
f"Region $name start address 0x$start%x not divisible by 0x$size%x" )
}
val routeSel = (addr: UInt) => {
Vec(realAddrMap.map { case (start, size) =>
addr >= UInt(start) && addr < UInt(start + size)
}).toBits
}
val xbar = Module(new NastiCrossbar(nMasters, levelSize, routeSel))
xbar.io.masters <> io.masters
addrmap.zip(realAddrMap).zip(xbar.io.slaves).zipWithIndex.foreach {
case (((entry, (start, size)), xbarSlave), i) => {
entry.region match {
2015-09-22 09:43:22 -07:00
case MemSize(_, _) =>
io.slaves(slaveInd) <> xbarSlave
slaveInd += 1
case MemSubmap(_, submap) =>
val subSlaves = submap.countSlaves
val outputs = Vec(io.slaves.drop(slaveInd).take(subSlaves))
val ic = Module(new NastiRecursiveInterconnect(1, subSlaves, submap, start))
ic.io.masters.head <> xbarSlave
outputs <> ic.io.slaves
slaveInd += subSlaves
case MemChannels(_, nchannels, _) =>
require(nchannels == 1, "Recursive interconnect cannot handle MultiChannel interface")
io.slaves(slaveInd) <> xbarSlave
slaveInd += 1
}
}
}
}
class ChannelHelper(nChannels: Int)
(implicit val p: Parameters) extends HasNastiParameters {
val dataBytes = p(MIFDataBits) * p(MIFDataBeats) / 8
val chanSelBits = log2Ceil(nChannels)
val selOffset = log2Up(dataBytes)
val blockOffset = selOffset + chanSelBits
def getSelect(addr: UInt) =
addr(blockOffset - 1, selOffset)
def getAddr(addr: UInt) =
Cat(addr(nastiXAddrBits - 1, blockOffset), addr(selOffset - 1, 0))
}
/** NASTI interconnect for multi-channel memory + regular IO
* We do routing for the memory channels differently from the IO ports
* Routing memory banks onto memory channels is done via arbiters
* (N-to-1 correspondence between banks and channels)
* Routing extra NASTI masters to memory requires a channel selecting router
* Routing anything to IO just uses standard recursive interconnect
*/
class NastiPerformanceInterconnect(
nBanksPerChannel: Int,
nChannels: Int,
nExtraMasters: Int,
nExtraSlaves: Int,
addrmap: AddrMap)(implicit p: Parameters) extends NastiInterconnect()(p) {
val nBanks = nBanksPerChannel * nChannels
val nMasters = nBanks + nExtraMasters
val nSlaves = nChannels + nExtraSlaves
val split = addrmap.head.region.size
val iomap = new AddrMap(addrmap.tail)
def routeMemOrIO(addr: UInt): UInt = {
Cat(addr >= UInt(split), addr < UInt(split))
}
val chanHelper = new ChannelHelper(nChannels)
def connectChannel(outer: NastiIO, inner: NastiIO) {
outer <> inner
outer.ar.bits.addr := chanHelper.getAddr(inner.ar.bits.addr)
outer.aw.bits.addr := chanHelper.getAddr(inner.aw.bits.addr)
}
val topRouters = List.fill(nMasters){Module(new NastiRouter(2, routeMemOrIO(_)))}
topRouters.zip(io.masters).foreach {
case (router, master) => router.io.master <> master
}
val channelRouteFunc = (addr: UInt) => UIntToOH(chanHelper.getSelect(addr))
val channelXbar = Module(new NastiCrossbar(nExtraMasters, nChannels, channelRouteFunc))
channelXbar.io.masters <> topRouters.drop(nBanks).map(_.io.slave(0))
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 => topRouters(j).io.slave(0))
val extra = channelXbar.io.slaves(i)
val channelArb = Module(new NastiArbiter(nBanksPerChannel + nExtraMasters))
channelArb.io.master <> (banks :+ extra)
connectChannel(io.slaves(i), channelArb.io.slave)
}
val ioslaves = Vec(io.slaves.drop(nChannels))
val iomasters = topRouters.map(_.io.slave(1))
val ioxbar = Module(new NastiRecursiveInterconnect(
nMasters, nExtraSlaves, iomap, split))
ioxbar.io.masters <> iomasters
ioslaves <> ioxbar.io.slaves
}