Editing Docs

This commit is contained in:
Zitao Fang
2020-07-05 21:05:21 -07:00
parent 104c350a59
commit 744e73fa92
2 changed files with 241 additions and 117 deletions

View File

@@ -3,21 +3,24 @@
Adding a custom core Adding a custom core
==================== ====================
You may want to add a custom RISC-V core to Chipyard generator. If the top module of your core is not in Chisel, You may want to integrate a custom RISC-V core into the Chipyard framework. This documentation page provides a step-to-step
you will first need to create a Verilog blackbox for it. See :ref:`incorporating-verilog-blocks` for instructions. instruction on how to achieve this.
Once you have a top module in Chisel, you are ready to create integrate it with Chipyard.
``generators/ariane/src/main/scala/ArianeTile.scala`` and ``generators/boom/src/main/scala/common/tile.scala``
provide two examples of how to integrate a core.
.. note:: .. note::
RoCC is not supported by custom core currently. Please use Rocket or Boom as the RoCC base core if you need to use RoCC. RoCC is currently not supported by cores other than Rocket and BOOM. Please use Rocket or BOOM as the RoCC base core if you need to use RoCC.
Parameter Case Classes
----------------------
Chipyard will generate a core for every ``InstantiableTileParams`` object it discovered in the current config. Wrap Verilog Module with Blackbox (Optional)
--------------------------------------------
Since Chipyard uses Scala and Chisel, if the top module of your core is not in Chisel, you will first need to create a Verilog
blackbox for it so that it can be processed by Chipyard. See :ref:`incorporating-verilog-blocks` for instructions.
Create Parameter Case Classes
-----------------------------
Chipyard will generate a core for every ``InstantiableTileParams`` object it discovered in the ``TilesLocated(InSubsystem)`` key.
This object is derived from``TileParams``, a trait containing the information needed to create a tile. All cores must have This object is derived from``TileParams``, a trait containing the information needed to create a tile. All cores must have
their own implementation of ``InstantiableTileParams``, as well as ``CoreParams`` which is passed as a field in ``TileParams``. their own implementation of ``InstantiableTileParams``, as well as ``CoreParams`` which is passed as a field in ``TileParams``.
@@ -41,7 +44,7 @@ need a custom field with similar purposes):
val icache: Option[ICacheParams] // Rocket specific: I1 cache option val icache: Option[ICacheParams] // Rocket specific: I1 cache option
val dcache: Option[DCacheParams] // Rocket specific: D1 cache option val dcache: Option[DCacheParams] // Rocket specific: D1 cache option
val btb: Option[BTBParams] // Rocket specific: BTB / branch predictor option val btb: Option[BTBParams] // Rocket specific: BTB / branch predictor option
val hartId: Int // Hart ID: Must be unique within a design config val hartId: Int // Hart ID: Must be unique within a design config (This MUST be a case class parameter)
val beuAddr: Option[BigInt] // Rocket specific: Bus Error Unit for Rocket Core val beuAddr: Option[BigInt] // Rocket specific: Bus Error Unit for Rocket Core
val blockerCtrlAddr: Option[BigInt] // Rocket specific: Bus Blocker for Rocket Core val blockerCtrlAddr: Option[BigInt] // Rocket specific: Bus Blocker for Rocket Core
val name: Option[String] // Name of the core val name: Option[String] // Name of the core
@@ -110,110 +113,64 @@ need a custom field with similar purposes):
dfmaLatency: Int = 4 // Rocket specific: Fused multiply-add pipeline latency (double precision) dfmaLatency: Int = 4 // Rocket specific: Fused multiply-add pipeline latency (double precision)
) )
Most of the fields here are originally designed for Rocket core and contains some architecture-specific details, but Most of the fields here are originally designed for the Rocket core and thus contain some implementation-specific details, but
many of them are general enough to be useful for other cores. It is strongly recommended to use these fields instead many of them are general enough to be useful for other cores. It is strongly recommended to use these fields instead
of creating your own custom fields when applicable. of creating your own custom fields when applicable.
You will also need a ``CanAttachTile`` class to add the tile config into the config system, with the following format:
.. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
:lines: 61-67
.. note:: .. note::
Implementations may choose to ignore some fields here or use them in a non-standard way, but using an inaccurate Implementations may choose to ignore some fields here or use them in a non-standard way, but using an inaccurate
value may break Chipyard components that rely on them (e.g. inaccurate indication of supported ISA extension will value may break Chipyard components that rely on them (e.g. an inaccurate indication of supported ISA extension will
result in incorrect test suite being generated) as well as any custom module that use them. ALWAYS document any result in an incorrect test suite being generated) as well as any custom modules that use them. ALWAYS document any
fields you ignore or with altered usage in your core implementation, and if you are implementing other devices that fields you ignore or with altered usage in your core implementation, and if you are implementing other devices that
would look up these config values, also document them. "Rocket specific" values are generally safe to ignore, but would look up these config values, also document them. "Rocket specific" values are generally safe to ignore, but
you should document them if you use them. you should document them if you use them.
Tile Class Create Tile Class
---------- -----------------
In Chipyard, all Tiles are diplomatically instantiated. In the first phase, diplomatic nodes which specify Tile-to-System In Chipyard, all Tiles are diplomatically instantiated. In the first phase, diplomatic nodes which specify Tile-to-System
interconnects are evaluated, while in the second "Module Implementation" phase, hardware is elaborated. interconnects are evaluated, while in the second "Module Implementation" phase, hardware is elaborated.
See :ref:`tilelink_and_diplomacy` for more details. See :ref:`tilelink_and_diplomacy` for more details. In this step, you will need to implement a tile class for your core.
All tile classes implement ``BaseTile`` and will normally implement ``SinksExternalInterrupts`` and ``SourcesExternalNotifications``, All tile classes implement ``BaseTile`` and will normally implement ``SinksExternalInterrupts`` and ``SourcesExternalNotifications``,
which allow the tile to accept external interrupt. A typical tile has the following form: which allow the tile to accept external interrupt. A typical tile has the following form:
.. code-block:: scala .. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
:lines: 87-125, 143
class MyTile( Connect TileLink Buses
val myParams: MyTileParams, ----------------------
crossing: ClockCrossingType,
lookup: LookupByHartIdImpl,
q: Parameters,
logicalTreeNode: LogicalTreeNode)
extends BaseTile(myParams, crossing, lookup, q)
with SinksExternalInterrupts
with SourcesExternalNotifications
{
// Private constructor ensures altered LazyModule.p is used implicitly Chipyard use TileLink as its onboard bus protocol. If your core doesn't use TileLink, you will need to insert converters
def this(params: MyTileParams, crossing: RocketCrossingParams, lookup: LookupByHartIdImpl, logicalTreeNode: LogicalTreeNode)(implicit p: Parameters) = between the core's memory protocol and TileLink in the Tile module.
this(params, crossing.crossingType, lookup, p, logicalTreeNode)
// Require TileLink nodes
val intOutwardNode = IntIdentityNode()
val masterNode = visibilityNode
val slaveNode = TLIdentityNode()
// Implementation class (See below)
override lazy val module = new MyTileModuleImp(this)
// Required entry of CPU device in the device tree for interrupt purpose
val cpuDevice: SimpleDevice = new SimpleDevice("cpu", Seq("my-organization,my-cpu", "riscv")) {
override def parent = Some(ResourceAnchors.cpus)
override def describe(resources: ResourceBindings): Description = {
val Description(name, mapping) = super.describe(resources)
Description(name, mapping ++
cpuProperties ++
nextLevelCacheProperty ++
tileProperties)
}
}
ResourceBinding {
Resource(cpuDevice, "reg").bind(ResourceAddress(hartId))
}
// (Connection to bus, interrupt, etc.)
}
TileLink Connection
-------------------
Chipyard use TileLink as its onboard bus protocol, and if your core doesn't use TileLink, you will need to convert them
in the tile class. Below is an example of how to connect a core using AXI4 to the TileLink bus with converters provided by in the tile class. Below is an example of how to connect a core using AXI4 to the TileLink bus with converters provided by
Chipyards: Rocket chip:
.. code-block:: scala .. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
val memoryTap = TLIdentityNode() // Every bus connection should have their own tap node :lines: 133-142
(tlMasterXbar.node // tlMasterXbar is the bus crossbar to be used when this core / tile is acting as a master; otherwise, use tlSlaveXBar
:= memoryTap
:= TLBuffer()
:= TLFIFOFixer(TLFIFOFixer.all) // fix FIFO ordering
:= TLWidthWidget(beatBytes) // reduce size of TL
:= AXI4ToTL() // convert to TL
:= AXI4UserYanker(Some(2)) // remove user field on AXI interface. need but in reality user intf. not needed
:= AXI4Fragmenter() // deal with multi-beat xacts
:= memAXI4Node) // The custom node, see below
Remember, you may not need all of these intermediate widgets. See :ref:`diplomatic_widgets` for the meaning of each intermediate Remember, you may not need all of these intermediate widgets. See :ref:`diplomatic_widgets` for the meaning of each intermediate
widget. If you are using TileLink, then you only need the tap node and the TileLink node used by your components. Chipyard also widget. If you are using TileLink, then you only need the tap node and the TileLink node used by your components. Chipyard also
provides converters for AHB, APB and AXIS, and most of the AXI4 widgets has equivalent widget for these bus protocol; see the provides converters for AHB, APB and AXIS, and most of the AXI4 widgets has equivalent widget for these bus protocol; see the
source files in ``generators/rocket-chip/src/main/scala/amba`` for more info. source files in ``generators/rocket-chip/src/main/scala/amba`` for more info.
If you are using other bus protocol, you may implement your own converters, using the files in ``generators/rocket-chip/src/main/scala/amba`` If you are using some other bus protocol, you may implement your own converters, using the files in ``generators/rocket-chip/src/main/scala/amba``
as the template, but it is not recommended unless you are familiar with TileLink. as the template, but it is not recommended unless you are familiar with TileLink.
``memAXI4Node`` is an AXI4 master node and is defined as following in our example: ``memAXI4Node`` is an AXI4 master node and is defined as following in our example:
.. code-block:: scala .. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
val memAXI4Node = AXI4MasterNode( :lines: 126-132
Seq(AXI4MasterPortParameters(
masters = Seq(AXI4MasterParameters(
name = portName,
id = IdRange(0, 1 << idBits))))))
where ``portName`` and ``idBits`` (number of bits to represent a port ID) are the parameter provides by the tile. where ``portName`` and ``idBits`` (number of bits to represent a port ID) are the parameter provides by the tile.
Make sure to read :ref:`node_types` to check out what type of nodes Chipyard supports and their parameters! Make sure to read :ref:`node_types` to check out what type of nodes Chipyard supports and their parameters!
@@ -228,8 +185,8 @@ can override the following two functions to control how to buffer the bus reques
You can find more information on ``TLBuffer`` in :ref:`diplomatic_widgets`. You can find more information on ``TLBuffer`` in :ref:`diplomatic_widgets`.
Interrupt Connect Interrupt
--------- -----------------
Chipyard allows a tile to either receive interrupts from other devices or initiate interrupts to notify other cores/devices. Chipyard allows a tile to either receive interrupts from other devices or initiate interrupts to notify other cores/devices.
In the tile that inherited ``SinksExternalInterrupts``, one can create a ``TileInterrupts`` object (a Chisel bundle) and In the tile that inherited ``SinksExternalInterrupts``, one can create a ``TileInterrupts`` object (a Chisel bundle) and
@@ -258,48 +215,33 @@ from the implementation class:
reportCease(could_cease: Option[Bool], quiescenceCycles: Int = 8) // Triggered when the core stop retiring instructions (like clock gating) reportCease(could_cease: Option[Bool], quiescenceCycles: Int = 8) // Triggered when the core stop retiring instructions (like clock gating)
reportWFI(could_wfi: Option[Bool]) // Triggered when a WFI instruction is executed reportWFI(could_wfi: Option[Bool]) // Triggered when a WFI instruction is executed
Implementation Class Create Implementation Class
-------------------- ---------------------------
The implementation class is of the following form: The implementation class for your core is of the following form:
.. code-block:: scala .. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
class MyTileModuleImp(outer: MyTile) extends BaseTileModuleImp(outer){ :lines: 145-149, 160
// annotate the parameters
Annotated.params(this, outer.tileParams)
// TODO: Create the top module of the core and connect it with the ports in "outer"
}
In the body of this class, you can look up any parameters by calling ``p({key})``, where ``{key}`` is the config key of In the body of this class, you can look up any parameters by calling ``p({key})``, where ``{key}`` is the config key of
the value you want to look up. For a list of available keys, see the appendix below. the value you want to look up. For a list of frequently used keys, see the appendix below.
If you create an AXI4 node (or equivalents), you will need to connect them to your core. You can connect a port like this: If you create an AXI4 node (or equivalents), you will need to connect them to your core. You can connect a port like this:
.. code-block:: scala .. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
:lines: 151-159
outer.myAXI4Node.out foreach { case (out, edgeOut) => Create Config Fragments to Integrate the Core
// Connect your module IO port to "out" ---------------------------------------------
// The type of "out" here is AXI4Bundle, which is defined in generators/rocket-chip/src/main/scala/amba/axi4/Bundles.scala
// Please refer to this file for the definition of the ports.
// If you are using APB, check APBBundle in generators/rocket-chip/src/main/scala/amba/apb/Bundles.scala
// If you are using AHB, check AHBSlaveBundle or AHBMasterBundle in generators/rocket-chip/src/main/scala/amba/ahb/Bundles.scala
// (choose one depends on the type of AHB node you create)
// If you are using AXIS, check AXISBundle and AXISBundleBits in generators/rocket-chip/src/main/scala/amba/axis/Bundles.scala
}
Integrate the Core To use your core in a Chipyard config, you would need a config fragment that would create a ``TileParams`` object of your core in
------------------
To use your core in a set of config, you would need a config fragment that would create a ``TileParams`` object of your core in
the current config. An example of such config will be like this: the current config. An example of such config will be like this:
.. code-block:: scala .. literalinclude:: ../../generators/chipyard/src/main/scala/example/TutorialTile.scala
:language: scala
class WithNMyCores(n: Int, hartidOffset: Int) extends Config((site, here, up) => { :lines: 162-179
case TilesLocated(InSubsystem) => up(TilesLocated(InSubsystem)) :++ List.tabulate(n)(i => MyTileParams(hartId = i + hartidOffset))
})
Chipyard looks up the tile parameters in the field ``TilesLocated(InSubsystem)``, whose type is a list of ``InstantiableTileParams``. Chipyard looks up the tile parameters in the field ``TilesLocated(InSubsystem)``, whose type is a list of ``InstantiableTileParams``.
This config fragment simply appends new tile parameters to the end of this list. This config fragment simply appends new tile parameters to the end of this list.
@@ -308,6 +250,9 @@ Now you have finished all the steps to prepare your cores for Chipyard! To gener
in :ref:`custom_chisel` to add your project to the build system, then create a config by following the steps in :ref:`hetero_socs_`. in :ref:`custom_chisel` to add your project to the build system, then create a config by following the steps in :ref:`hetero_socs_`.
You can now run any desired workflow for the new config just as you do for the built-in cores. You can now run any desired workflow for the new config just as you do for the built-in cores.
If you would like to see how an actual core are integrated into Chipyard, ``generators/ariane/src/main/scala/ArianeTile.scala``
provides a concrete example of integrating a third party Verilog core Ariane.
Appendix: Common Config Keys Appendix: Common Config Keys
---------------------------- ----------------------------

View File

@@ -0,0 +1,179 @@
package chipyard.example
import chisel3._
import chisel3.util._
import freechips.rocketchip.config._
import freechips.rocketchip.subsystem._
import freechips.rocketchip.devices.tilelink._
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.diplomaticobjectmodel.logicaltree.{LogicalTreeNode}
import freechips.rocketchip.rocket._
import freechips.rocketchip.subsystem.{RocketCrossingParams}
import freechips.rocketchip.tilelink._
import freechips.rocketchip.interrupts._
import freechips.rocketchip.util._
import freechips.rocketchip.tile._
import freechips.rocketchip.amba.axi4._
// Example parameter class copied from Ariane, not included in documentation but for compile check only
// If you are here for documentation, DO NOT copy MyCoreParams and MyTileParams directly - always figure
// out what parameters you need before you write the parameter class
case class MyCoreParams(
bootFreqHz: BigInt = BigInt(1700000000),
rasEntries: Int = 4,
btbEntries: Int = 16,
bhtEntries: Int = 16,
enableToFromHostCaching: Boolean = false,
) extends CoreParams {
val useVM: Boolean = true
val useUser: Boolean = true
val useSupervisor: Boolean = false
val useDebug: Boolean = true
val useAtomics: Boolean = true
val useAtomicsOnlyForIO: Boolean = false // copied from Rocket
val useCompressed: Boolean = true
override val useVector: Boolean = false
val useSCIE: Boolean = false
val useRVE: Boolean = false
val mulDiv: Option[MulDivParams] = Some(MulDivParams()) // copied from Rocket
val fpu: Option[FPUParams] = Some(FPUParams()) // copied fma latencies from Rocket
val nLocalInterrupts: Int = 0
val nPMPs: Int = 0 // TODO: Check
val pmpGranularity: Int = 4 // copied from Rocket
val nBreakpoints: Int = 0 // TODO: Check
val useBPWatch: Boolean = false
val nPerfCounters: Int = 29
val haveBasicCounters: Boolean = true
val haveFSDirty: Boolean = false
val misaWritable: Boolean = false
val haveCFlush: Boolean = false
val nL2TLBEntries: Int = 512 // copied from Rocket
val mtvecInit: Option[BigInt] = Some(BigInt(0)) // copied from Rocket
val mtvecWritable: Boolean = true // copied from Rocket
val instBits: Int = if (useCompressed) 16 else 32
val lrscCycles: Int = 80 // copied from Rocket
val decodeWidth: Int = 1 // TODO: Check
val fetchWidth: Int = 1 // TODO: Check
val retireWidth: Int = 2
}
case class MyTileAttachParams(
tileParams: MyTileParams,
crossingParams: RocketCrossingParams
) extends CanAttachTile {
type TileType = MyTile
val lookup = PriorityMuxHartIdFromSeq(Seq(tileParams))
}
case class MyTileParams(
name: Option[String] = Some("my_tile"),
hartId: Int = 0,
trace: Boolean = false,
val core: MyCoreParams = MyCoreParams()
) extends InstantiableTileParams[MyTile]
{
val beuAddr: Option[BigInt] = None
val blockerCtrlAddr: Option[BigInt] = None
val btb: Option[BTBParams] = Some(BTBParams())
val boundaryBuffers: Boolean = false
val dcache: Option[DCacheParams] = Some(DCacheParams())
val icache: Option[ICacheParams] = Some(ICacheParams())
def instantiate(crossing: TileCrossingParamsLike, lookup: LookupByHartIdImpl)(implicit p: Parameters): MyTile = {
new MyTile(this, crossing, lookup)
}
}
class MyTile(
val myParams: MyTileParams,
crossing: ClockCrossingType,
lookup: LookupByHartIdImpl,
q: Parameters)
extends BaseTile(myParams, crossing, lookup, q)
with SinksExternalInterrupts
with SourcesExternalNotifications
{
// Private constructor ensures altered LazyModule.p is used implicitly
def this(params: MyTileParams, crossing: TileCrossingParamsLike, lookup: LookupByHartIdImpl)(implicit p: Parameters) =
this(params, crossing.crossingType, lookup, p)
// Require TileLink nodes
val intOutwardNode = IntIdentityNode()
val masterNode = visibilityNode
val slaveNode = TLIdentityNode()
// Implementation class (See below)
override lazy val module = new MyTileModuleImp(this)
// Required entry of CPU device in the device tree for interrupt purpose
val cpuDevice: SimpleDevice = new SimpleDevice("cpu", Seq("my-organization,my-cpu", "riscv")) {
override def parent = Some(ResourceAnchors.cpus)
override def describe(resources: ResourceBindings): Description = {
val Description(name, mapping) = super.describe(resources)
Description(name, mapping ++
cpuProperties ++
nextLevelCacheProperty ++
tileProperties)
}
}
ResourceBinding {
Resource(cpuDevice, "reg").bind(ResourceAddress(hartId))
}
// (Connection to bus, interrupt, etc.)
// # of bits used in TileLink ID for master node. 4 bits can support 16 master nodes, but you can have a longer ID if you need more.
val idBits = 4
val memAXI4Node = AXI4MasterNode(
Seq(AXI4MasterPortParameters(
masters = Seq(AXI4MasterParameters(
name = "myPortName",
id = IdRange(0, 1 << idBits))))))
val memoryTap = TLIdentityNode() // Every bus connection should have their own tap node
(tlMasterXbar.node // tlMasterXbar is the bus crossbar to be used when this core / tile is acting as a master; otherwise, use tlSlaveXBar
:= memoryTap
:= TLBuffer()
:= TLFIFOFixer(TLFIFOFixer.all) // fix FIFO ordering
:= TLWidthWidget(masterPortBeatBytes) // reduce size of TL
:= AXI4ToTL() // convert to TL
:= AXI4UserYanker(Some(2)) // remove user field on AXI interface. need but in reality user intf. not needed
:= AXI4Fragmenter() // deal with multi-beat xacts
:= memAXI4Node) // The custom node, see below
}
class MyTileModuleImp(outer: MyTile) extends BaseTileModuleImp(outer){
// annotate the parameters
Annotated.params(this, outer.myParams)
// TODO: Create the top module of the core and connect it with the ports in "outer"
outer.memAXI4Node.out foreach { case (out, edgeOut) =>
// Connect your module IO port to "out"
// The type of "out" here is AXI4Bundle, which is defined in generators/rocket-chip/src/main/scala/amba/axi4/Bundles.scala
// Please refer to this file for the definition of the ports.
// If you are using APB, check APBBundle in generators/rocket-chip/src/main/scala/amba/apb/Bundles.scala
// If you are using AHB, check AHBSlaveBundle or AHBMasterBundle in generators/rocket-chip/src/main/scala/amba/ahb/Bundles.scala
// (choose one depends on the type of AHB node you create)
// If you are using AXIS, check AXISBundle and AXISBundleBits in generators/rocket-chip/src/main/scala/amba/axis/Bundles.scala
}
}
class WithNMyCores(n: Int = 1, overrideIdOffset: Option[Int] = None) extends Config((site, here, up) => {
case TilesLocated(InSubsystem) => {
// Calculate the next available hart ID (since hart ID cannot be duplicated)
val prev = up(TilesLocated(InSubsystem), site)
val idOffset = overrideIdOffset.getOrElse(prev.size)
// Create TileAttachParams for every core to be instantiated
(0 until n).map { i =>
MyTileAttachParams(
tileParams = MyTileParams(hartId = i + idOffset),
crossingParams = RocketCrossingParams()
)
} ++ prev
}
// Configurate # of bytes in one memory / IO transaction. For RV64, one load/store instruction can transfer 8 bytes at most.
case SystemBusKey => up(SystemBusKey, site).copy(beatBytes = 8)
// The # of instruction bits. Use maximum # of bits if your core supports both 32 and 64 bits.
case XLen => 64
})