Merge pull request #1757 from ucb-bar/clean-gcd

Improve GCD example
This commit is contained in:
Jerry Zhao
2024-01-26 12:42:37 -08:00
committed by GitHub
2 changed files with 129 additions and 93 deletions

View File

@@ -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. 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``. 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 .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala
:language: 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 .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala
:language: scala :language: scala
:start-after: DOC include start: GCD instance regmap :start-after: DOC include start: GCD router
:end-before: DOC include end: GCD instance regmap :end-before: DOC include end: GCD router
Advanced Features of RegField Entries 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 algorithm is set up by first writing ``x`` and then performing a
triggering write to ``y``. Polling can be used for status checks. 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 Connecting by TileLink
---------------------- ----------------------
Once you have these classes, you can construct the final peripheral by extending the ``TLRegisterRouter`` and passing the proper arguments. The key to connecting to the TileLink Diplomatic graph is the construction of the TileLink node for this peripheral.
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. In this case, since the peripheral acts as a manager of some register-mapped address space, it uses the ``TLRegisterNode`` object.
The second set of arguments is the IO bundle constructor, which we create by extending ``TLRegBundle`` with our bundle trait. The parameters to the ``TLRegisterNode`` object specify the size of the managed space, the base address, and the port width.
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. 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 .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala
:language: scala :language: scala
@@ -62,12 +70,8 @@ Top-level Traits
---------------- ----------------
After creating the module, we need to hook it up to our SoC. 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. 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 .. literalinclude:: ../../generators/chipyard/src/main/scala/example/GCD.scala
:language: 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. 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. 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 Constructing the DigitalTop and Config
-------------------------------------- --------------------------------------

View File

@@ -4,6 +4,7 @@ import chisel3._
import chisel3.util._ import chisel3.util._
import chisel3.experimental.{IntParam, BaseModule} import chisel3.experimental.{IntParam, BaseModule}
import freechips.rocketchip.amba.axi4._ import freechips.rocketchip.amba.axi4._
import freechips.rocketchip.prci._
import freechips.rocketchip.subsystem.BaseSubsystem import freechips.rocketchip.subsystem.BaseSubsystem
import org.chipsalliance.cde.config.{Parameters, Field, Config} import org.chipsalliance.cde.config.{Parameters, Field, Config}
import freechips.rocketchip.diplomacy._ import freechips.rocketchip.diplomacy._
@@ -36,27 +37,24 @@ class GCDIO(val w: Int) extends Bundle {
val busy = Output(Bool()) val busy = Output(Bool())
} }
trait GCDTopIO extends Bundle { class GCDTopIO extends Bundle {
val gcd_busy = Output(Bool()) val gcd_busy = Output(Bool())
} }
trait HasGCDIO extends BaseModule { trait HasGCDTopIO {
val w: Int def io: GCDTopIO
val io = IO(new GCDIO(w))
} }
// DOC include start: GCD blackbox // DOC include start: GCD blackbox
class GCDMMIOBlackBox(val w: Int) extends BlackBox(Map("WIDTH" -> IntParam(w))) with HasBlackBoxResource class GCDMMIOBlackBox(val w: Int) extends BlackBox(Map("WIDTH" -> IntParam(w))) with HasBlackBoxResource {
with HasGCDIO val io = IO(new GCDIO(w))
{
addResource("/vsrc/GCDMMIOBlackBox.v") addResource("/vsrc/GCDMMIOBlackBox.v")
} }
// DOC include end: GCD blackbox // DOC include end: GCD blackbox
// DOC include start: GCD chisel // DOC include start: GCD chisel
class GCDMMIOChiselModule(val w: Int) extends Module class GCDMMIOChiselModule(val w: Int) extends Module {
with HasGCDIO val io = IO(new GCDIO(w))
{
val s_idle :: s_run :: s_done :: Nil = Enum(3) val s_idle :: s_run :: s_done :: Nil = Enum(3)
val state = RegInit(s_idle) val state = RegInit(s_idle)
@@ -90,70 +88,106 @@ class GCDMMIOChiselModule(val w: Int) extends Module
} }
// DOC include end: GCD chisel // 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 // DOC include start: GCD router
class GCDTL(params: GCDParams, beatBytes: Int)(implicit p: Parameters) class GCDTL(params: GCDParams, beatBytes: Int)(implicit p: Parameters) extends ClockSinkDomain(ClockSinkParameters())(p) {
extends TLRegisterRouter( val device = new SimpleDevice("gcd", Seq("ucbbar,gcd"))
params.address, "gcd", Seq("ucbbar,gcd"), val node = TLRegisterNode(Seq(AddressSet(params.address, 4096-1)), device, "reg/control", beatBytes=beatBytes)
beatBytes = beatBytes)(
new TLRegBundle(params, _) with GCDTopIO)(
new TLRegModule(params, _, _) with GCDModule)
class GCDAXI4(params: GCDParams, beatBytes: Int)(implicit p: Parameters) override lazy val module = new GCDImpl
extends AXI4RegisterRouter( class GCDImpl extends Impl with HasGCDTopIO {
params.address, val io = IO(new GCDTopIO)
beatBytes=beatBytes)( withClockAndReset(clock, reset) {
new AXI4RegBundle(params, _) with GCDTopIO)( // How many clock cycles in a PWM cycle?
new AXI4RegModule(params, _, _) with GCDModule) 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 end: GCD router
// DOC include start: GCD lazy trait // DOC include start: GCD lazy trait
@@ -164,7 +198,8 @@ trait CanHavePeripheryGCD { this: BaseSubsystem =>
val gcd_busy = p(GCDKey) match { val gcd_busy = p(GCDKey) match {
case Some(params) => { case Some(params) => {
val gcd = if (params.useAXI4) { 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) { pbus.coupleTo(portName) {
gcd.node := gcd.node :=
AXI4Buffer () := AXI4Buffer () :=
@@ -174,18 +209,14 @@ trait CanHavePeripheryGCD { this: BaseSubsystem =>
} }
gcd gcd
} else { } 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) := _ } pbus.coupleTo(portName) { gcd.node := TLFragmenter(pbus.beatBytes, pbus.blockBytes) := _ }
gcd gcd
} }
val pbus_io = pbus { InModuleBody {
val busy = IO(Output(Bool()))
busy := gcd.module.io.gcd_busy
busy
}}
val gcd_busy = InModuleBody { val gcd_busy = InModuleBody {
val busy = IO(Output(Bool())).suggestName("gcd_busy") val busy = IO(Output(Bool())).suggestName("gcd_busy")
busy := pbus_io busy := gcd.module.io.gcd_busy
busy busy
} }
Some(gcd_busy) Some(gcd_busy)