diff --git a/docs/Customization/Adding-An-Accelerator.rst b/docs/Customization/Adding-An-Accelerator.rst index 20aea8c0..3c0ea4eb 100644 --- a/docs/Customization/Adding-An-Accelerator.rst +++ b/docs/Customization/Adding-An-Accelerator.rst @@ -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. diff --git a/docs/TileLink-Diplomacy-Reference/Register-Router.rst b/docs/TileLink-Diplomacy-Reference/Register-Router.rst index 4f129e47..cc735578 100644 --- a/docs/TileLink-Diplomacy-Reference/Register-Router.rst +++ b/docs/TileLink-Diplomacy-Reference/Register-Router.rst @@ -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. diff --git a/generators/example/src/main/scala/RegisterNodeExample.scala b/generators/example/src/main/scala/RegisterNodeExample.scala index a4e2cb02..cda91ffe 100644 --- a/generators/example/src/main/scala/RegisterNodeExample.scala +++ b/generators/example/src/main/scala/RegisterNodeExample.scala @@ -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) }