1
0

Add "NastiMemorySelector", a memory interconnect

On Hurricane we want to be able to support multiple memory channels but have a
fallback to fewer, since the full configuration is going to require a
complicated FPGA setup.  This adds another sort of interconnect that can switch
between having different numbers of top-level memory channels active at chip
boot time.

This interconnect is a bit funny: changing the select input when there is
memory traffic is a bad idea.  This is fine for this use case, since we really
only care about changing the memory configuration at boot time -- since it'll
scramble the memory of the machine it's not so useful, anyway.

The advantage is that we don't have to have a full 8x8 Nasti crossbar in our
chip, which would be fairly expensive.  Changing the crossbar would garble
memory as well, so it's not like it would add any extra functionality.
This commit is contained in:
Palmer Dabbelt 2016-02-16 23:50:23 -08:00
parent fef8a2d862
commit 6b39db8ce6

View File

@ -596,3 +596,82 @@ class NastiMemoryInterconnect(
connectChannel(io.slaves(i), channelArb.io.slave) connectChannel(io.slaves(i), channelArb.io.slave)
} }
} }
/** 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 Nasti, 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 NastiMemorySelector(nBanks: Int, maxMemChannels: Int, configs: Seq[Int])(implicit p: Parameters) extends NastiModule()(p) {
val nMasters = nBanks
val nSlaves = maxMemChannels
val nConfigs = configs.size
val io = new Bundle {
val masters = Vec(nMasters, new NastiIO).flip
val slaves = Vec(nSlaves, new NastiIO)
val select = UInt(INPUT, width = log2Up(nConfigs))
}
def muxOnSelect(up: DecoupledIO[Bundle], dn: DecoupledIO[Bundle], active: Bool): Unit = {
when (active) { dn.bits := up.bits }
when (active) { up.ready := dn.ready }
when (active) { dn.valid := up.valid }
}
def muxOnSelect(up: NastiIO, dn: NastiIO, active: Bool): Unit = {
muxOnSelect(up.aw, dn.aw, active)
muxOnSelect(up.w, dn.w, active)
muxOnSelect(dn.b, up.b, active)
muxOnSelect(up.ar, dn.ar, active)
muxOnSelect(dn.r, up.r, active)
}
def muxOnSelect(up: Vec[NastiIO], dn: Vec[NastiIO], active: Bool) : Unit = {
for (i <- 0 until up.size)
muxOnSelect(up(i), dn(i), active)
}
/* Disconnects a vector of Nasti 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 disconnectSlave(slave: Vec[NastiIO]) = {
slave.foreach{ m =>
m.aw.valid := Bool(false)
m.aw.bits := m.aw.bits.fromBits( UInt(0) )
m.w.valid := Bool(false)
m.w.bits := m.w.bits.fromBits( UInt(0) )
m.b.ready := Bool(false)
m.ar.valid := Bool(false)
m.ar.bits := m.ar.bits.fromBits( UInt(0) )
m.r.ready := Bool(false)
}
}
def disconnectMaster(master: Vec[NastiIO]) = {
master.foreach{ m =>
m.aw.ready := Bool(false)
m.w.ready := Bool(false)
m.b.valid := Bool(false)
m.b.bits := m.b.bits.fromBits( UInt(0) )
m.ar.ready := Bool(false)
m.r.valid := Bool(false)
m.r.bits := m.r.bits.fromBits( UInt(0) )
}
}
/* Provides default wires on all our outputs. */
disconnectMaster(io.masters)
disconnectSlave(io.slaves)
/* 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 NastiMemoryInterconnect(nBanksPerChannel, nChannels))
disconnectMaster(ic.io.slaves)
disconnectSlave(ic.io.masters)
muxOnSelect( io.masters, ic.io.masters, io.select === UInt(select))
muxOnSelect(ic.io.slaves, io.slaves, io.select === UInt(select))
}
}