@@ -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
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user