-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathHasTiles.scala
435 lines (387 loc) · 21.1 KB
/
HasTiles.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
// See LICENSE.SiFive for license details.
package freechips.rocketchip.subsystem
import Chisel._
import chisel3.dontTouch
import freechips.rocketchip.config.{Field, Parameters}
import freechips.rocketchip.devices.debug.{HasPeripheryDebug, HasPeripheryDebugModuleImp}
import freechips.rocketchip.devices.tilelink.{BasicBusBlocker, BasicBusBlockerParams, CLINTConsts, PLICKey, CanHavePeripheryPLIC, CanHavePeripheryCLINT}
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.diplomaticobjectmodel.logicaltree.{LogicalModuleTree}
import freechips.rocketchip.interrupts._
import freechips.rocketchip.tile.{BaseTile, LookupByHartIdImpl, TileParams, InstantiableTileParams, MaxHartIdBits, TilePRCIDomain, NMI}
import freechips.rocketchip.tilelink._
import freechips.rocketchip.prci.{ClockGroup, ResetCrossingType}
import freechips.rocketchip.util._
/** Entry point for Config-uring the presence of Tiles */
case class TilesLocated(loc: HierarchicalLocation) extends Field[Seq[CanAttachTile]](Nil)
/** Whether to add timing-closure registers along the path of the hart id
* as it propagates through the subsystem and into the tile.
*
* These are typically only desirable when a dynamically programmable prefix is being combined
* with the static hart id via [[freechips.rocketchip.subsystem.HasTiles.tileHartIdNexusNode]].
*/
case object InsertTimingClosureRegistersOnHartIds extends Field[Boolean](false)
/** Whether per-tile hart ids are going to be driven as inputs into the subsystem,
* and if so, what their width should be.
*/
case object SubsystemExternalHartIdWidthKey extends Field[Option[Int]](None)
/** Whether per-tile reset vectors are going to be driven as inputs into the subsystem.
*
* Unlike the hart ids, the reset vector width is determined by the sinks within the tiles,
* based on the size of the address map visible to the tiles.
*/
case object SubsystemExternalResetVectorKey extends Field[Boolean](true)
/** An interface for describing the parameteization of how Tiles are connected to interconnects */
trait TileCrossingParamsLike {
/** The type of clock crossing that should be inserted at the tile boundary. */
def crossingType: ClockCrossingType
/** Parameters describing the contents and behavior of the point where the tile is attached as an interconnect master. */
def master: TilePortParamsLike
/** Parameters describing the contents and behavior of the point where the tile is attached as an interconnect slave. */
def slave: TilePortParamsLike
/** The subnetwork location of the device selecting the apparent base address of MMIO devices inside the tile */
def mmioBaseAddressPrefixWhere: TLBusWrapperLocation
/** Inject a reset management subgraph that effects the tile child reset only */
def resetCrossingType: ResetCrossingType
/** Keep the tile clock separate from the interconnect clock (e.g. even if they are synchronous to one another) */
def forceSeparateClockReset: Boolean
}
/** An interface for describing the parameterization of how a particular tile port is connected to an interconnect */
trait TilePortParamsLike {
/** The subnetwork location of the interconnect to which this tile port should be connected. */
def where: TLBusWrapperLocation
/** Allows port-specific adapters to be injected into the interconnect side of the attachment point. */
def injectNode(context: Attachable)(implicit p: Parameters): TLNode
}
/** A default implementation of parameterizing the connectivity of the port where the tile is the master.
* Optional timing buffers and/or an optional CacheCork can be inserted in the interconnect's clock domain.
*/
case class TileMasterPortParams(
buffers: Int = 0,
cork: Option[Boolean] = None,
where: TLBusWrapperLocation = SBUS
) extends TilePortParamsLike {
def injectNode(context: Attachable)(implicit p: Parameters): TLNode = {
(TLBuffer.chainNode(buffers) :=* cork.map { u => TLCacheCork(unsafe = u) } .getOrElse { TLTempNode() })
}
}
/** A default implementation of parameterizing the connectivity of the port giving access to slaves inside the tile.
* Optional timing buffers and/or an optional BusBlocker adapter can be inserted in the interconnect's clock domain.
*/
case class TileSlavePortParams(
buffers: Int = 0,
blockerCtrlAddr: Option[BigInt] = None,
blockerCtrlWhere: TLBusWrapperLocation = CBUS,
where: TLBusWrapperLocation = CBUS
) extends TilePortParamsLike {
def injectNode(context: Attachable)(implicit p: Parameters): TLNode = {
val controlBus = context.locateTLBusWrapper(where)
val blockerBus = context.locateTLBusWrapper(blockerCtrlWhere)
blockerCtrlAddr
.map { BasicBusBlockerParams(_, blockerBus.beatBytes, controlBus.beatBytes) }
.map { bbbp =>
val blocker = LazyModule(new BasicBusBlocker(bbbp))
blockerBus.coupleTo("tile_slave_port_bus_blocker") { blocker.controlNode := TLFragmenter(blockerBus) := _ }
blocker.node :*= TLBuffer.chainNode(buffers)
} .getOrElse { TLBuffer.chainNode(buffers) }
}
}
/** These are sources of interrupts that are driven into the tile.
* They need to be instantiated before tiles are attached to the subsystem containing them.
*/
trait HasTileInterruptSources
extends CanHavePeripheryPLIC
with CanHavePeripheryCLINT
with HasPeripheryDebug
with InstantiatesTiles
{ this: BaseSubsystem => // TODO ideally this bound would be softened to LazyModule
/** meipNode is used to create a single bit subsystem input in Configs without a PLIC */
val meipNode = p(PLICKey) match {
case Some(_) => None
case None => Some(IntNexusNode(
sourceFn = { _ => IntSourcePortParameters(Seq(IntSourceParameters(1))) },
sinkFn = { _ => IntSinkPortParameters(Seq(IntSinkParameters())) },
outputRequiresInput = false,
inputRequiresOutput = false))
}
val seipNode = p(PLICKey) match {
case Some(_) => None
case None => Some(IntNexusNode(
sourceFn = { _ => IntSourcePortParameters(Seq(IntSourceParameters(1))) },
sinkFn = { _ => IntSinkPortParameters(Seq(IntSinkParameters())) },
outputRequiresInput = false,
inputRequiresOutput = false))
}
/** Source of Non-maskable Interrupt (NMI) input bundle to each tile. */
val tileNMINode = BundleBridgeEphemeralNode[NMI]()
val tileNMIIONodes: Seq[BundleBridgeSource[NMI]] = {
Seq.fill(tiles.size) {
val nmiSource = BundleBridgeSource[NMI]()
tileNMINode := nmiSource
nmiSource
}
}
}
/** These are sources of "constants" that are driven into the tile.
*
* While they are not expected to change dyanmically while the tile is executing code,
* they may be either tied to a contant value or programmed during boot or reset.
* They need to be instantiated before tiles are attached within the subsystem containing them.
*/
trait HasTileInputConstants extends InstantiatesTiles { this: BaseSubsystem =>
/** tileHartIdNode is used to collect publishers and subscribers of hartids. */
val tileHartIdNode = BundleBridgeEphemeralNode[UInt]()
/** tileHartIdNexusNode is a BundleBridgeNexus that collects dynamic hart prefixes.
*
* Each "prefix" input is actually the same full width as the outer hart id; the expected usage
* is that each prefix source would set only some non-overlapping portion of the bits to non-zero values.
* This node orReduces them, and further combines the reduction with the static ids assigned to each tile,
* producing a unique, dynamic hart id for each tile.
*
* If p(InsertTimingClosureRegistersOnHartIds) is set, the input and output values are registered.
*
* The output values are [[dontTouch]]'d to prevent constant propagation from pulling the values into
* the tiles if they are constant, which would ruin deduplication of tiles that are otherwise homogeneous.
*/
val tileHartIdNexusNode = LazyModule(new BundleBridgeNexus[UInt](
inputFn = BundleBridgeNexus.orReduction[UInt](registered = p(InsertTimingClosureRegistersOnHartIds)) _,
outputFn = (prefix: UInt, n: Int) => Seq.tabulate(n) { i =>
val y = dontTouch(prefix | hartIdList(i).U(p(MaxHartIdBits).W)) // dontTouch to keep constant prop from breaking tile dedup
if (p(InsertTimingClosureRegistersOnHartIds)) BundleBridgeNexus.safeRegNext(y) else y
},
default = Some(() => 0.U(p(MaxHartIdBits).W)),
inputRequiresOutput = true, // guard against this being driven but then ignored in tileHartIdIONodes below
shouldBeInlined = false // can't inline something whose output we are are dontTouching
)).node
// TODO: Replace the DebugModuleHartSelFuncs config key with logic to consume the dynamic hart IDs
/** tileResetVectorNode is used to collect publishers and subscribers of tile reset vector addresses. */
val tileResetVectorNode = BundleBridgeEphemeralNode[UInt]()
/** tileResetVectorNexusNode is a BundleBridgeNexus that accepts a single reset vector source, and broadcasts it to all tiles. */
val tileResetVectorNexusNode = BundleBroadcast[UInt](
inputRequiresOutput = true // guard against this being driven but ignored in tileResetVectorIONodes below
)
/** tileHartIdIONodes may generate subsystem IOs, one per tile, allowing the parent to assign unique hart ids.
*
* Or, if such IOs are not configured to exist, tileHartIdNexusNode is used to supply an id to each tile.
*/
val tileHartIdIONodes: Seq[BundleBridgeSource[UInt]] = p(SubsystemExternalHartIdWidthKey) match {
case Some(w) => Seq.fill(tiles.size) {
val hartIdSource = BundleBridgeSource(() => UInt(w.W))
tileHartIdNode := hartIdSource
hartIdSource
}
case None => { tileHartIdNode :*= tileHartIdNexusNode; Nil }
}
/** tileResetVectorIONodes may generate subsystem IOs, one per tile, allowing the parent to assign unique reset vectors.
*
* Or, if such IOs are not configured to exist, tileResetVectorNexusNode is used to supply a single reset vector to every tile.
*/
val tileResetVectorIONodes: Seq[BundleBridgeSource[UInt]] = p(SubsystemExternalResetVectorKey) match {
case true => Seq.fill(tiles.size) {
val resetVectorSource = BundleBridgeSource[UInt]()
tileResetVectorNode := resetVectorSource
resetVectorSource
}
case false => { tileResetVectorNode :*= tileResetVectorNexusNode; Nil }
}
}
/** These are sinks of notifications that are driven out from the tile.
*
* They need to be instantiated before tiles are attached to the subsystem containing them.
*/
trait HasTileNotificationSinks { this: LazyModule =>
val tileHaltXbarNode = IntXbar(p)
val tileHaltSinkNode = IntSinkNode(IntSinkPortSimple())
tileHaltSinkNode := tileHaltXbarNode
val tileWFIXbarNode = IntXbar(p)
val tileWFISinkNode = IntSinkNode(IntSinkPortSimple())
tileWFISinkNode := tileWFIXbarNode
val tileCeaseXbarNode = IntXbar(p)
val tileCeaseSinkNode = IntSinkNode(IntSinkPortSimple())
tileCeaseSinkNode := tileCeaseXbarNode
}
/** Most tile types require only these traits in order for their standardized connect functions to apply.
*
* BaseTiles subtypes with different needs can extend this trait to provide themselves with
* additional external connection points.
*/
trait DefaultTileContextType
extends Attachable
with HasTileInterruptSources
with HasTileNotificationSinks
with HasTileInputConstants
{ this: BaseSubsystem => } // TODO: ideally this bound would be softened to LazyModule
/** Standardized interface by which parameterized tiles can be attached to contexts containing interconnect resources.
*
* Sub-classes of this trait can optionally override the individual connect functions in order to specialize
* their attachment behaviors, but most use cases should be be handled simply by changing the implementation
* of the injectNode functions in crossingParams.
*/
trait CanAttachTile {
type TileType <: BaseTile
type TileContextType <: DefaultTileContextType
def tileParams: InstantiableTileParams[TileType]
def crossingParams: TileCrossingParamsLike
def lookup: LookupByHartIdImpl
/** Narrow waist through which all tiles are intended to pass while being instantiated. */
def instantiate(implicit p: Parameters): TilePRCIDomain[TileType] = {
val clockSinkParams = tileParams.clockSinkParams.copy(name = Some(s"${tileParams.name.getOrElse("core")}_${tileParams.hartId}"))
val tile_prci_domain = LazyModule(new TilePRCIDomain[TileType](clockSinkParams, crossingParams) { self =>
val tile = self.tile_reset_domain { LazyModule(tileParams.instantiate(crossingParams, lookup)) }
})
tile_prci_domain
}
/** A default set of connections that need to occur for most tile types */
def connect(domain: TilePRCIDomain[TileType], context: TileContextType): Unit = {
connectMasterPorts(domain, context)
connectSlavePorts(domain, context)
connectInterrupts(domain, context)
connectPRC(domain, context)
connectOutputNotifications(domain, context)
connectInputConstants(domain, context)
LogicalModuleTree.add(context.logicalTreeNode, domain.tile.logicalTreeNode)
}
/** Connect the port where the tile is the master to a TileLink interconnect. */
def connectMasterPorts(domain: TilePRCIDomain[TileType], context: Attachable): Unit = {
implicit val p = context.p
val dataBus = context.locateTLBusWrapper(crossingParams.master.where)
dataBus.coupleFrom(tileParams.name.getOrElse("tile")) { bus =>
bus :=* crossingParams.master.injectNode(context) :=* domain.crossMasterPort(crossingParams.crossingType)
}
}
/** Connect the port where the tile is the slave to a TileLink interconnect. */
def connectSlavePorts(domain: TilePRCIDomain[TileType], context: Attachable): Unit = {
implicit val p = context.p
DisableMonitors { implicit p =>
val controlBus = context.locateTLBusWrapper(crossingParams.slave.where)
controlBus.coupleTo(tileParams.name.getOrElse("tile")) { bus =>
domain.crossSlavePort(crossingParams.crossingType) :*= crossingParams.slave.injectNode(context) :*= TLWidthWidget(controlBus.beatBytes) :*= bus
}
}
}
/** Connect the various interrupts sent to and and raised by the tile. */
def connectInterrupts(domain: TilePRCIDomain[TileType], context: TileContextType): Unit = {
implicit val p = context.p
// NOTE: The order of calls to := matters! They must match how interrupts
// are decoded from tile.intInwardNode inside the tile. For this reason,
// we stub out missing interrupts with constant sources here.
// 1. Debug interrupt is definitely asynchronous in all cases.
domain.tile.intInwardNode :=
context.debugOpt
.map { domain { IntSyncAsyncCrossingSink(3) } := _.intnode }
.getOrElse { NullIntSource() }
// 2. The CLINT and PLIC output interrupts are synchronous to the TileLink bus clock,
// so might need to be synchronized depending on the Tile's crossing type.
// From CLINT: "msip" and "mtip"
domain.crossIntIn(crossingParams.crossingType) :=
context.clintOpt.map { _.intnode }
.getOrElse { NullIntSource(sources = CLINTConsts.ints) }
// From PLIC: "meip"
domain.crossIntIn(crossingParams.crossingType) :=
context.plicOpt .map { _.intnode }
.getOrElse { context.meipNode.get }
// From PLIC: "seip" (only if supervisor mode is enabled)
if (domain.tile.tileParams.core.hasSupervisorMode) {
domain.crossIntIn(crossingParams.crossingType) :=
context.plicOpt .map { _.intnode }
.getOrElse { context.seipNode.get }
}
// 3. Local Interrupts ("lip") are required to already be synchronous to the Tile's clock.
// (they are connected to domain.tile.intInwardNode in a seperate trait)
// 4. Interrupts coming out of the tile are sent to the PLIC,
// so might need to be synchronized depending on the Tile's crossing type.
context.plicOpt.foreach { plic =>
FlipRendering { implicit p =>
plic.intnode :=* domain.crossIntOut(crossingParams.crossingType, domain.tile.intOutwardNode)
}
}
// 5. Connect NMI inputs to the tile. These inputs are synchronous to the respective core_clock.
domain.tile.nmiNode := context.tileNMINode
}
/** Notifications of tile status are connected to be broadcast without needing to be clock-crossed. */
def connectOutputNotifications(domain: TilePRCIDomain[TileType], context: TileContextType): Unit = {
implicit val p = context.p
context.tileHaltXbarNode :=* domain.crossIntOut(NoCrossing, domain.tile.haltNode)
context.tileWFIXbarNode :=* domain.crossIntOut(NoCrossing, domain.tile.wfiNode)
context.tileCeaseXbarNode :=* domain.crossIntOut(NoCrossing, domain.tile.ceaseNode)
// TODO should context be forced to have a trace sink connected here?
// for now this just ensures domain.trace[Core]Node has been crossed without connecting it externally
domain.crossTracesOut()
}
/** Connect inputs to the tile that are assumed to be constant during normal operation, and so are not clock-crossed. */
def connectInputConstants(domain: TilePRCIDomain[TileType], context: TileContextType): Unit = {
implicit val p = context.p
val tlBusToGetPrefixFrom = context.locateTLBusWrapper(crossingParams.mmioBaseAddressPrefixWhere)
domain.tile.hartIdNode := context.tileHartIdNode
domain.tile.resetVectorNode := context.tileResetVectorNode
tlBusToGetPrefixFrom.prefixNode.foreach { domain.tile.mmioAddressPrefixNode := _ }
}
/** Connect power/reset/clock resources. */
def connectPRC(domain: TilePRCIDomain[TileType], context: TileContextType): Unit = {
implicit val p = context.p
val tlBusToGetClockDriverFrom = context.locateTLBusWrapper(crossingParams.master.where)
val clockSource = (crossingParams.crossingType match {
case _: SynchronousCrossing | _: CreditedCrossing =>
if (crossingParams.forceSeparateClockReset) tlBusToGetClockDriverFrom.clockNode
else tlBusToGetClockDriverFrom.fixedClockNode
case _: RationalCrossing => tlBusToGetClockDriverFrom.clockNode
case _: AsynchronousCrossing => {
val tileClockGroup = ClockGroup()
tileClockGroup := context.asyncClockGroupsNode
tileClockGroup
}
})
domain {
domain.tile_reset_domain.clockNode := crossingParams.resetCrossingType.injectClockNode := domain.clockNode
} := clockSource
}
}
/** InstantiatesTiles adds a Config-urable sequence of tiles of any type
* to the subsystem class into which it is mixed.
*/
trait InstantiatesTiles { this: BaseSubsystem =>
/** Record the order in which to instantiate all tiles, based on statically-assigned ids.
*
* Note that these ids, which are often used as the tiles' default hartid input,
* may or may not be those actually reflected at runtime in e.g. the $mhartid CSR
*/
val tileAttachParams: Seq[CanAttachTile] = p(TilesLocated(location)).sortBy(_.tileParams.hartId)
/** The actual list of instantiated tiles in this subsystem. */
val tile_prci_domains: Seq[TilePRCIDomain[_]] = tileAttachParams.map(_.instantiate(p))
val tiles: Seq[BaseTile] = tile_prci_domains.map(_.tile.asInstanceOf[BaseTile])
// Helper functions for accessing certain parameters that are popular to refer to in subsystem code
val tileParams: Seq[TileParams] = tileAttachParams.map(_.tileParams)
val tileCrossingTypes: Seq[ClockCrossingType] = tileAttachParams.map(_.crossingParams.crossingType)
def nTiles: Int = tileAttachParams.size
def hartIdList: Seq[Int] = tileParams.map(_.hartId)
def localIntCounts: Seq[Int] = tileParams.map(_.core.nLocalInterrupts)
require(hartIdList.distinct.size == tiles.size, s"Every tile must be statically assigned a unique id, but got:\n${hartIdList}")
}
/** HasTiles instantiates and also connects a Config-urable sequence of tiles of any type to subsystem interconnect resources. */
trait HasTiles extends InstantiatesTiles with HasCoreMonitorBundles with DefaultTileContextType
{ this: BaseSubsystem => // TODO: ideally this bound would be softened to Attachable
implicit val p: Parameters
// connect all the tiles to interconnect attachment points made available in this subsystem context
tileAttachParams.zip(tile_prci_domains).foreach { case (params, td) =>
params.connect(td.asInstanceOf[TilePRCIDomain[params.TileType]], this.asInstanceOf[params.TileContextType])
}
}
/** Provides some Chisel connectivity to certain tile IOs */
trait HasTilesModuleImp extends LazyModuleImp with HasPeripheryDebugModuleImp {
val outer: HasTiles with HasTileInterruptSources with HasTileInputConstants
val reset_vector = outer.tileResetVectorIONodes.zipWithIndex.map { case (n, i) => n.makeIO(s"reset_vector_$i") }
val tile_hartids = outer.tileHartIdIONodes.zipWithIndex.map { case (n, i) => n.makeIO(s"tile_hartids_$i") }
val meip = if(outer.meipNode.isDefined) Some(IO(Vec(outer.meipNode.get.out.size, Bool()).asInput)) else None
meip.foreach { m =>
m.zipWithIndex.foreach{ case (pin, i) =>
(outer.meipNode.get.out(i)._1)(0) := pin
}
}
val seip = if(outer.seipNode.isDefined) Some(IO(Vec(outer.seipNode.get.out.size, Bool()).asInput)) else None
seip.foreach { s =>
s.zipWithIndex.foreach{ case (pin, i) =>
(outer.seipNode.get.out(i)._1)(0) := pin
}
}
val nmi = outer.tiles.zip(outer.tileNMIIONodes).zipWithIndex.map { case ((tile, n), i) => tile.tileParams.core.useNMI.option(n.makeIO(s"nmi_$i")) }
}