add additional example code as literalincludes

This commit is contained in:
Howard Mao
2019-09-12 18:08:45 -07:00
parent 6ae60b94c6
commit d5bccc0455
3 changed files with 54 additions and 170 deletions

View File

@@ -229,51 +229,33 @@ To add RoCC instructions in your program, use the RoCC C macros provided in ``te
Adding a DMA port
-------------------
IO devices or accelerators (like a disk or network driver), we may want to have the device write directly to the coherent memory system instead.
To add a device like that, you would do the following.
For IO devices or accelerators (like a disk or network driver), instead of
having the CPU poll data from the device, we may want to have the device write
directly to the coherent memory system instead. For example, here is a device
that writes zeros to the memory at a configured address.
.. code-block:: scala
.. literalinclude:: ../../generators/example/src/main/scala/InitZero.scala
:language: scala
class DMADevice(implicit p: Parameters) extends LazyModule {
val node = TLHelper.makeClientNode(
name = "dma-device", sourceId = IdRange(0, 1))
.. literalinclude:: ../../generators/example/src/main/scala/Top.scala
:language: scala
:start-after: DOC include start: TopWithInitZero
:end-before: DOC include end: TopWithInitZero
lazy val module = new DMADeviceModule(this)
}
We use ``TLHelper.makeClientNode`` to create a TileLink client node for us.
We then connect the client node to the memory system through the front bus (fbus).
For more info on creating TileLink client nodes, take a look at :ref:`Client Node`.
class DMADeviceModule(outer: DMADevice) extends LazyModuleImp(outer) {
val io = IO(new Bundle {
val ext = new ExtBundle
})
Once we've created our top-level module including the DMA widget, we can create a configuration for it as we did before.
val (mem, edge) = outer.node.out(0)
.. literalinclude:: ../../generators/example/src/main/scala/ConfigMixins.scala
:language: scala
:start-after: DOC include start: WithInitZero
:end-before: DOC include end: WithInitZero
// ... rest of the code ...
}
trait HasPeripheryDMA { this: BaseSubsystem =>
implicit val p: Parameters
val dma = LazyModule(new DMADevice)
fbus.fromPort(Some(portName))() := dma.node
}
trait HasPeripheryDMAModuleImp extends LazyModuleImp {
val ext = IO(new ExtBundle)
ext <> outer.dma.module.io.ext
}
class TopWithDMA(implicit p: Parameters) extends Top
with HasPeripheryDMA {
override lazy val module = new TopWithDMAModule
}
class TopWithDMAModule(l: TopWithDMA) extends TopModule(l)
with HasPeripheryDMAModuleImp
.. literalinclude:: ../../generators/example/src/main/scala/RocketConfigs.scala
:language: scala
:start-after: DOC include start: InitZeroRocketConfig
:end-before: DOC include end: InitZeroRocketConfig
The ``ExtBundle`` contains the signals we connect off-chip that we get data from.
The DMADevice also has a Tilelink client port that we connect into the L1-L2 crossbar through the frontend bus (fbus).
The sourceId variable given in the ``TLClientNode`` instantiation determines the range of ids that can be used in acquire messages from this device.
Since we specified [0, 1) as our range, only the ID 0 can be used.

View File

@@ -18,39 +18,10 @@ This section will focus on the second method.
Basic Usage
-----------
.. code-block:: scala
import chisel3._
import chisel3.util._
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy.{SimpleDevice, AddressSet}
import freechips.rocketchip.tilelink.TLRegisterNode
class MyDeviceController(implicit p: Parameters) extends LazyModule {
val device = new SimpleDevice("my-device", Seq("tutorial,my-device0"))
val node = TLRegisterNode(
address = Seq(AddressSet(0x10019000, 0xfff)),
device = device,
beatBytes = 8,
concurrency = 1)
lazy val module = new LazyModuleImp(this) {
val bigReg = RegInit(0.U(64.W))
val mediumReg = RegInit(0.U(32.W))
val smallReg = RegInit(0.U(16.W))
val tinyReg0 = RegInit(0.U(4.W))
val tinyReg1 = RegInit(0.U(4.W))
node.regmap(
0x00 -> Seq(RegField(64, bigReg)),
0x08 -> Seq(RegField(32, mediumReg)),
0x0C -> Seq(RegField(16, smallReg)),
0x0E -> Seq(
RegField(4, tinyReg0),
RegField(4, tinyReg1)))
}
}
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
:language: scala
:start-after: DOC include start: MyDeviceController
:end-before: DOC include end: MyDeviceController
The code example above shows a simple lazy module that uses the ``TLRegisterNode``
to memory map hardware registers of different sizes. The constructor has
@@ -85,13 +56,10 @@ register. The ``RegField`` interface also provides support for reading
and writing ``DecoupledIO`` interfaces. For instance, you can implement a
hardware FIFO like so.
.. code-block:: scala
// 4-entry 64-bit queue
val queue = Module(new Queue(UInt(64.W), 4))
node.regmap(
0x00 -> Seq(RegField(64, queue.io.deq, queue.io.enq)))
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
:language: scala
:start-after: DOC include start: MyQueueRegisters
:end-before: DOC include end: MyQueueRegisters
This variant of the ``RegField`` constructor takes three arguments instead of
two. The first argument is still the bit width. The second is the decoupled
@@ -103,11 +71,10 @@ You need not specify both read and write for a register. You can also create
read-only or write-only registers. So for the previous example, if you wanted
enqueue and dequeue to use different addresses, you could write the following.
.. code-block:: scala
node.regmap(
0x00 -> Seq(RegField.r(64, queue.io.deq)),
0x08 -> Seq(RegField.w(64, queue.io.enq)))
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
:language: scala
:start-after: DOC include start: MySeparateQueueRegisters
:end-before: DOC include end: MySeparateQueueRegisters
The read-only register function can also be used to read signals
that aren't registers.
@@ -126,24 +93,10 @@ You can also create registers using functions. Say, for instance, that you
want to create a counter that gets incremented on a write and decremented on
a read.
.. code-block:: scala
val counter = RegInit(0.U(64.W))
def readCounter(ready: Bool): (Bool, UInt) = {
when (ready) { counter := counter - 1.U }
(true.B, counter)
}
def writeCounter(valid: Bool, bits: UInt): Bool = {
when (valid) { counter := counter + 1.U }
// Ignore bits
true.B
}
node.regmap(
0x00 -> Seq(RegField.r(64, readCounter(_))),
0x08 -> Seq(RegField.w(64, writeCounter(_, _))))
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
:language: scala
:start-after: DOC include start: MyCounterRegisters
:end-before: DOC include end: MyCounterRegisters
The functions here are essentially the same as a decoupled interface.
The read function gets passed the ``ready`` signal and returns the
@@ -154,39 +107,10 @@ You can also pass functions that decouple the read/write request and response.
The request will appear as a decoupled input and the response as a decoupled
output. So for instance, if we wanted to do this for the previous example.
.. code-block:: scala
val counter = RegInit(0.U(64.W))
def readCounter(ivalid: Bool, oready: Bool): (Bool, Bool, UInt) = {
val responding = RegInit(false.B)
when (ivalid && !responding) { responding := true.B }
when (responding && oready) {
counter := counter - 1.U
responding := false.B
}
(!responding, responding, counter)
}
def writeCounter(ivalid: Bool, bits: UInt, oready: Bool): (Bool, Bool) = {
val responding = RegInit(false.B)
when (ivalid && !responding) { responding := true.B }
when (responding && oready) {
counter := counter + 1.U
responding := false.B
}
(!responding, responding)
}
node.regmap(
0x00 -> Seq(RegField.r(64, readCounter(_, _))),
0x08 -> Seq(RegField.w(64, writeCounter(_, _, _))))
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
:language: scala
:start-after: DOC include start: MyCounterReqRespRegisters
:end-before: DOC include end: MyCounterReqRespRegisters
In each function, we set up a state variable ``responding``. The function
is ready to take requests when this is false and is sending a response when
@@ -207,37 +131,11 @@ change the protocol being used. For instance, in the first example in
:ref:`Basic Usage`, you could simply change the ``TLRegisterNode`` to
and ``AXI4RegisterNode``.
.. code-block:: scala
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
:language: scala
:start-after: DOC include start: MyAXI4DeviceController
:end-before: DOC include end: MyAXI4DeviceController
import chisel3._
import chisel3.util._
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy.{SimpleDevice, AddressSet}
import freechips.rocketchip.amba.axi4.AXI4RegisterNode
class MyAXI4DeviceController(implicit p: Parameters) extends LazyModule {
val node = AXI4RegisterNode(
address = Seq(AddressSet(0x10019000, 0xfff)),
beatBytes = 8,
concurrency = 1)
lazy val module = new LazyModuleImp(this) {
val bigReg = RegInit(0.U(64.W))
val mediumReg = RegInit(0.U(32.W))
val smallReg = RegInit(0.U(16.W))
val tinyReg0 = RegInit(0.U(4.W))
val tinyReg1 = RegInit(0.U(4.W))
node.regmap(
0x00 -> Seq(RegField(64, bigReg)),
0x08 -> Seq(RegField(32, mediumReg)),
0x0C -> Seq(RegField(16, smallReg)),
0x0E -> Seq(
RegField(4, tinyReg0),
RegField(4, tinyReg1)))
}
}
Other than the fact that AXI4 nodes don't take a ``device`` argument,
everything else is unchanged.
Other than the fact that AXI4 nodes don't take a ``device`` argument, and can
only have a single AddressSet instead of multiple, everything else is
unchanged.

View File

@@ -115,12 +115,14 @@ class MyCounterRegisters(implicit p: Parameters) extends LazyModule {
def readCounter(ready: Bool): (Bool, UInt) = {
when (ready) { counter := counter - 1.U }
// (ready, bits)
(true.B, counter)
}
def writeCounter(valid: Bool, bits: UInt): Bool = {
when (valid) { counter := counter + 1.U }
// Ignore bits
// Return ready
true.B
}
@@ -153,10 +155,11 @@ class MyCounterReqRespRegisters(implicit p: Parameters) extends LazyModule {
responding := false.B
}
// (iready, ovalid, obits)
(!responding, responding, counter)
}
def writeCounter(ivalid: Bool, oready: Bool, bits: UInt): (Bool, Bool) = {
def writeCounter(ivalid: Bool, oready: Bool, ibits: UInt): (Bool, Bool) = {
val responding = RegInit(false.B)
when (ivalid && !responding) { responding := true.B }
@@ -166,6 +169,7 @@ class MyCounterReqRespRegisters(implicit p: Parameters) extends LazyModule {
responding := false.B
}
// (iready, ovalid)
(!responding, responding)
}