From 5a553741f03ce78fba68e8e88d0d35f4214a9ac2 Mon Sep 17 00:00:00 2001 From: Jerry Zhao Date: Mon, 22 Jan 2024 18:32:48 -0800 Subject: [PATCH 1/3] Improve GCD example --- .../chipyard/src/main/scala/example/GCD.scala | 196 +++++++++++------- 1 file changed, 116 insertions(+), 80 deletions(-) diff --git a/generators/chipyard/src/main/scala/example/GCD.scala b/generators/chipyard/src/main/scala/example/GCD.scala index 24c99187..3ed202ac 100644 --- a/generators/chipyard/src/main/scala/example/GCD.scala +++ b/generators/chipyard/src/main/scala/example/GCD.scala @@ -4,6 +4,7 @@ import chisel3._ import chisel3.util._ import chisel3.experimental.{IntParam, BaseModule} import freechips.rocketchip.amba.axi4._ +import freechips.rocketchip.prci._ import freechips.rocketchip.subsystem.BaseSubsystem import org.chipsalliance.cde.config.{Parameters, Field, Config} import freechips.rocketchip.diplomacy._ @@ -36,27 +37,24 @@ class GCDIO(val w: Int) extends Bundle { val busy = Output(Bool()) } -trait GCDTopIO extends Bundle { +class GCDTopIO extends Bundle { val gcd_busy = Output(Bool()) } -trait HasGCDIO extends BaseModule { - val w: Int - val io = IO(new GCDIO(w)) +trait HasGCDTopIO { + def io: GCDTopIO } // DOC include start: GCD blackbox -class GCDMMIOBlackBox(val w: Int) extends BlackBox(Map("WIDTH" -> IntParam(w))) with HasBlackBoxResource - with HasGCDIO -{ +class GCDMMIOBlackBox(val w: Int) extends BlackBox(Map("WIDTH" -> IntParam(w))) with HasBlackBoxResource { + val io = IO(new GCDIO(w)) addResource("/vsrc/GCDMMIOBlackBox.v") } // DOC include end: GCD blackbox // DOC include start: GCD chisel -class GCDMMIOChiselModule(val w: Int) extends Module - with HasGCDIO -{ +class GCDMMIOChiselModule(val w: Int) extends Module { + val io = IO(new GCDIO(w)) val s_idle :: s_run :: s_done :: Nil = Enum(3) val state = RegInit(s_idle) @@ -90,70 +88,111 @@ class GCDMMIOChiselModule(val w: Int) extends Module } // DOC include end: GCD chisel -// DOC include start: GCD instance regmap - -trait GCDModule extends HasRegMap { - val io: GCDTopIO - - implicit val p: Parameters - def params: GCDParams - val clock: Clock - val reset: Reset - - - // How many clock cycles in a PWM cycle? - val x = Reg(UInt(params.width.W)) - val y = Wire(new DecoupledIO(UInt(params.width.W))) - val gcd = Wire(new DecoupledIO(UInt(params.width.W))) - val status = Wire(UInt(2.W)) - - val impl = if (params.useBlackBox) { - Module(new GCDMMIOBlackBox(params.width)) - } else { - Module(new GCDMMIOChiselModule(params.width)) - } - - impl.io.clock := clock - impl.io.reset := reset.asBool - - impl.io.x := x - impl.io.y := y.bits - impl.io.input_valid := y.valid - y.ready := impl.io.input_ready - - gcd.bits := impl.io.gcd - gcd.valid := impl.io.output_valid - impl.io.output_ready := gcd.ready - - status := Cat(impl.io.input_ready, impl.io.output_valid) - io.gcd_busy := impl.io.busy - - regmap( - 0x00 -> Seq( - RegField.r(2, status)), // a read-only register capturing current status - 0x04 -> Seq( - RegField.w(params.width, x)), // a plain, write-only register - 0x08 -> Seq( - RegField.w(params.width, y)), // write-only, y.valid is set on write - 0x0C -> Seq( - RegField.r(params.width, gcd))) // read-only, gcd.ready is set on read -} -// DOC include end: GCD instance regmap - // DOC include start: GCD router -class GCDTL(params: GCDParams, beatBytes: Int)(implicit p: Parameters) - extends TLRegisterRouter( - params.address, "gcd", Seq("ucbbar,gcd"), - beatBytes = beatBytes)( - new TLRegBundle(params, _) with GCDTopIO)( - new TLRegModule(params, _, _) with GCDModule) +class GCDTL(params: GCDParams, beatBytes: Int)(implicit p: Parameters) extends ClockSinkDomain(ClockSinkParameters())(p) { + val device = new SimpleDevice("gcd", Seq("ucbbar,gcd")) { + override def describe(resources: ResourceBindings): Description = { + val Description(name, mapping) = super.describe(resources) + Description(name, mapping) + } + } + val node = TLRegisterNode(Seq(AddressSet(params.address, 4096-1)), device, "reg/control", beatBytes=beatBytes) -class GCDAXI4(params: GCDParams, beatBytes: Int)(implicit p: Parameters) - extends AXI4RegisterRouter( - params.address, - beatBytes=beatBytes)( - new AXI4RegBundle(params, _) with GCDTopIO)( - new AXI4RegModule(params, _, _) with GCDModule) + override lazy val module = new GCDImpl + class GCDImpl extends Impl with HasGCDTopIO { + val io = IO(new GCDTopIO) + withClockAndReset(clock, reset) { + // How many clock cycles in a PWM cycle? + val x = Reg(UInt(params.width.W)) + val y = Wire(new DecoupledIO(UInt(params.width.W))) + val gcd = Wire(new DecoupledIO(UInt(params.width.W))) + val status = Wire(UInt(2.W)) + + val impl_io = if (params.useBlackBox) { + val impl = Module(new GCDMMIOBlackBox(params.width)) + impl.io + } else { + val impl = Module(new GCDMMIOChiselModule(params.width)) + impl.io + } + + impl_io.clock := clock + impl_io.reset := reset.asBool + + impl_io.x := x + impl_io.y := y.bits + impl_io.input_valid := y.valid + y.ready := impl_io.input_ready + + gcd.bits := impl_io.gcd + gcd.valid := impl_io.output_valid + impl_io.output_ready := gcd.ready + + status := Cat(impl_io.input_ready, impl_io.output_valid) + io.gcd_busy := impl_io.busy + +// DOC include start: GCD instance regmap + node.regmap( + 0x00 -> Seq( + RegField.r(2, status)), // a read-only register capturing current status + 0x04 -> Seq( + RegField.w(params.width, x)), // a plain, write-only register + 0x08 -> Seq( + RegField.w(params.width, y)), // write-only, y.valid is set on write + 0x0C -> Seq( + RegField.r(params.width, gcd))) // read-only, gcd.ready is set on read +// DOC include end: GCD instance regmap + } + } +} + +class GCDAXI4(params: GCDParams, beatBytes: Int)(implicit p: Parameters) extends ClockSinkDomain(ClockSinkParameters())(p) { + val node = AXI4RegisterNode(AddressSet(params.address, 4096-1), beatBytes=beatBytes) + override lazy val module = new GCDImpl + class GCDImpl extends Impl with HasGCDTopIO { + val io = IO(new GCDTopIO) + withClockAndReset(clock, reset) { + // How many clock cycles in a PWM cycle? + val x = Reg(UInt(params.width.W)) + val y = Wire(new DecoupledIO(UInt(params.width.W))) + val gcd = Wire(new DecoupledIO(UInt(params.width.W))) + val status = Wire(UInt(2.W)) + + val impl_io = if (params.useBlackBox) { + val impl = Module(new GCDMMIOBlackBox(params.width)) + impl.io + } else { + val impl = Module(new GCDMMIOChiselModule(params.width)) + impl.io + } + + impl_io.clock := clock + impl_io.reset := reset.asBool + + impl_io.x := x + impl_io.y := y.bits + impl_io.input_valid := y.valid + y.ready := impl_io.input_ready + + gcd.bits := impl_io.gcd + gcd.valid := impl_io.output_valid + impl_io.output_ready := gcd.ready + + status := Cat(impl_io.input_ready, impl_io.output_valid) + io.gcd_busy := impl_io.busy + + node.regmap( + 0x00 -> Seq( + RegField.r(2, status)), // a read-only register capturing current status + 0x04 -> Seq( + RegField.w(params.width, x)), // a plain, write-only register + 0x08 -> Seq( + RegField.w(params.width, y)), // write-only, y.valid is set on write + 0x0C -> Seq( + RegField.r(params.width, gcd))) // read-only, gcd.ready is set on read + } + } +} // DOC include end: GCD router // DOC include start: GCD lazy trait @@ -164,7 +203,8 @@ trait CanHavePeripheryGCD { this: BaseSubsystem => val gcd_busy = p(GCDKey) match { case Some(params) => { val gcd = if (params.useAXI4) { - val gcd = pbus { LazyModule(new GCDAXI4(params, pbus.beatBytes)(p)) } + val gcd = LazyModule(new GCDAXI4(params, pbus.beatBytes)(p)) + gcd.clockNode := pbus.fixedClockNode pbus.coupleTo(portName) { gcd.node := AXI4Buffer () := @@ -174,18 +214,14 @@ trait CanHavePeripheryGCD { this: BaseSubsystem => } gcd } else { - val gcd = pbus { LazyModule(new GCDTL(params, pbus.beatBytes)(p)) } + val gcd = LazyModule(new GCDTL(params, pbus.beatBytes)(p)) + gcd.clockNode := pbus.fixedClockNode pbus.coupleTo(portName) { gcd.node := TLFragmenter(pbus.beatBytes, pbus.blockBytes) := _ } gcd } - val pbus_io = pbus { InModuleBody { - val busy = IO(Output(Bool())) - busy := gcd.module.io.gcd_busy - busy - }} val gcd_busy = InModuleBody { val busy = IO(Output(Bool())).suggestName("gcd_busy") - busy := pbus_io + busy := gcd.module.io.gcd_busy busy } Some(gcd_busy) From 5aee69c17a4e1b9288bcee9a5a239d9549f7be9c Mon Sep 17 00:00:00 2001 From: Jerry Zhao Date: Mon, 22 Jan 2024 22:24:48 -0800 Subject: [PATCH 2/3] update docs on GCD MMIO --- docs/Customization/MMIO-Peripherals.rst | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/Customization/MMIO-Peripherals.rst b/docs/Customization/MMIO-Peripherals.rst index f3f2bc8d..82a9f27b 100644 --- a/docs/Customization/MMIO-Peripherals.rst +++ b/docs/Customization/MMIO-Peripherals.rst @@ -10,7 +10,8 @@ To create a RegisterRouter-based peripheral, you will need to specify a paramete For this example, we will show how to connect a MMIO peripheral which computes the GCD. The full code can be found in ``generators/chipyard/src/main/scala/example/GCD.scala``. -In this case we use a submodule ``GCDMMIOChiselModule`` to actually perform the GCD. The ``GCDModule`` class only creates the registers and hooks them up using ``regmap``. +In this case we use a submodule ``GCDMMIOChiselModule`` to actually perform the GCD. The ``GCDTL`` and ``GCDAXI4`` classes are the ``LazyModule`` classes which construct the TileLink or AXI4 ports, wrapping the inner ``GCDMMIOChiselModule``. +The ``node`` object is a Diplomacy node, which connects the peripheral to the Diplomacy interconnect graph. .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala :language: scala @@ -19,8 +20,9 @@ In this case we use a submodule ``GCDMMIOChiselModule`` to actually perform the .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala :language: scala - :start-after: DOC include start: GCD instance regmap - :end-before: DOC include end: GCD instance regmap + :start-after: DOC include start: GCD router + :end-before: DOC include end: GCD router + Advanced Features of RegField Entries ------------------------------------- @@ -41,15 +43,21 @@ triggering the GCD algorithm when ``y`` is written. Therefore, the algorithm is set up by first writing ``x`` and then performing a triggering write to ``y``. Polling can be used for status checks. +.. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala + :language: scala + :start-after: DOC include start: GCD instance regmap + :end-before: DOC include end: GCD instance regmap + Connecting by TileLink ---------------------- -Once you have these classes, you can construct the final peripheral by extending the ``TLRegisterRouter`` and passing the proper arguments. -The first set of arguments determines where the register router will be placed in the global address map and what information will be put in its device tree entry. -The second set of arguments is the IO bundle constructor, which we create by extending ``TLRegBundle`` with our bundle trait. -The final set of arguments is the module constructor, which we create by extends ``TLRegModule`` with our module trait. -Notice how we can create an analogous AXI4 version of our peripheral. +The key to connecting to the TileLink Diplomatic graph is the construction of the TileLink node for this peripheral. +In this case, since the peripheral acts as a manager of some register-mapped address space, it uses the ``TLRegisterNode`` object. +The parameters to the ``TLRegisterNode`` object specify the size of the managed space, the base address, and the port width. + +Within the register-mapped peripheral, the control registers can be mapped using the ``node.regmap`` function, as described above. +A similar procedure is followed for both AXI4 and TileLin peripherals. .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala :language: scala @@ -62,12 +70,8 @@ Top-level Traits ---------------- After creating the module, we need to hook it up to our SoC. -Rocket Chip accomplishes this using the cake pattern. -This basically involves placing code inside traits. -In the Rocket Chip cake, there are two kinds of traits: a ``LazyModule`` trait and a module implementation trait. - The ``LazyModule`` trait runs setup code that must execute before all the hardware gets elaborated. -For a simple memory-mapped peripheral, this just involves connecting the peripheral's TileLink node to the MMIO crossbar. +For a simple memory-mapped peripheral, this just involves connecting the peripheral's TileLink node to the relevant bus. .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala :language: scala @@ -80,6 +84,7 @@ This will automatically add address map and device tree entries for the peripher Also observe how we have to place additional AXI4 buffers and converters for the AXI4 version of this peripheral. Peripherals which expose I/O can use `InModuleBody` to punch their I/O to the `DigitalTop` module. +In this example, the GCD module's ``gcd_busy`` signal is exposed as a I/O of DigitalTop. Constructing the DigitalTop and Config -------------------------------------- From aef7cda172874b758bd906e85a001b62c66525d5 Mon Sep 17 00:00:00 2001 From: Lux Date: Wed, 24 Jan 2024 17:24:59 -0800 Subject: [PATCH 3/3] removing seemingly useless override --- generators/chipyard/src/main/scala/example/GCD.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/generators/chipyard/src/main/scala/example/GCD.scala b/generators/chipyard/src/main/scala/example/GCD.scala index 3ed202ac..ef3edc1f 100644 --- a/generators/chipyard/src/main/scala/example/GCD.scala +++ b/generators/chipyard/src/main/scala/example/GCD.scala @@ -90,12 +90,7 @@ class GCDMMIOChiselModule(val w: Int) extends Module { // DOC include start: GCD router class GCDTL(params: GCDParams, beatBytes: Int)(implicit p: Parameters) extends ClockSinkDomain(ClockSinkParameters())(p) { - val device = new SimpleDevice("gcd", Seq("ucbbar,gcd")) { - override def describe(resources: ResourceBindings): Description = { - val Description(name, mapping) = super.describe(resources) - Description(name, mapping) - } - } + val device = new SimpleDevice("gcd", Seq("ucbbar,gcd")) val node = TLRegisterNode(Seq(AddressSet(params.address, 4096-1)), device, "reg/control", beatBytes=beatBytes) override lazy val module = new GCDImpl