From c2ce173195a03868c5a33fc40129b35e31d41d0f Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Wed, 25 Sep 2019 19:32:34 -0700 Subject: [PATCH] Add Verilog MMIO GCD peripheral example --- build.sbt | 2 +- .../Incorporating-Verilog-Blocks.rst | 156 ++++++++++++++++++ docs/Customization/index.rst | 1 + .../src/main/resources/vsrc/GCDMMIOBlackBox.v | 48 ++++++ .../example/src/main/scala/ConfigMixins.scala | 8 + .../src/main/scala/GCDMMIOBlackBox.scala | 98 +++++++++++ .../src/main/scala/RocketConfigs.scala | 7 + generators/example/src/main/scala/Top.scala | 10 ++ tests/gcd.c | 42 +++++ 9 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 docs/Customization/Incorporating-Verilog-Blocks.rst create mode 100644 generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v create mode 100644 generators/example/src/main/scala/GCDMMIOBlackBox.scala create mode 100644 tests/gcd.c diff --git a/build.sbt b/build.sbt index 8083dc3d02..05a80bba0b 100644 --- a/build.sbt +++ b/build.sbt @@ -147,7 +147,7 @@ lazy val sha3 = (project in file("generators/sha3")) .settings(commonSettings) lazy val tapeout = conditionalDependsOn(project in file("./tools/barstools/tapeout/")) - .dependsOn(chisel_testers) + .dependsOn(chisel_testers, example) .settings(commonSettings) lazy val mdf = (project in file("./tools/barstools/mdf/scalalib/")) diff --git a/docs/Customization/Incorporating-Verilog-Blocks.rst b/docs/Customization/Incorporating-Verilog-Blocks.rst new file mode 100644 index 0000000000..424b4c7e34 --- /dev/null +++ b/docs/Customization/Incorporating-Verilog-Blocks.rst @@ -0,0 +1,156 @@ +.. _incorporating-verilog-blocks: + +Incorporating Verilog Blocks +============================ + +Working with existing Verilog IP is an integral part of many chip +design flows. Fortunately, both Chisel and Chipyard provide extensive +support for Verilog integration. + +Here, we will examine the process of incorporating an MMIO peripheral +(similar to the PWM example from the previous section) that uses a +Verilog implementation of Greatest Common Denominator (GCD) +algorithm. There are a few steps to adding a Verilog peripheral: + +* Adding a Verilog resource file to the project +* Defining a Chisel ``BlackBox`` representing the Verilog module +* Instantiating the ``BlackBox`` and interfacing ``RegField`` entries +* Setting up a chip ``Top`` and ``Config`` that use the peripheral + +Adding a Verilog blackbox resource file +--------------------------------------- + +As before, it is possible to incorporate peripherals as part of your +own generator project. However, Verilog resource files must go in a +different directory from Chisel (Scala) sources. + +.. code-block:: none + + generators/yourproject/ + build.sbt + src/main/ + scala/ + resources/ + vsrc/ + YourFile.v + +In addition to the steps outlined in the previous section on adding a +project to the ``build.sbt`` at the top level, it is also necessary to +add any projects that contain Verilog IP as dependencies to the +``tapeout`` project. + +.. code-block:: scala + + lazy val tapeout = conditionalDependsOn(project in file("./tools/barstools/tapeout/")) + .dependsOn(chisel_testers, example, yourproject) + .settings(commonSettings) + +For this concrete GCD example, we will be using a ``GCDMMIOBlackBox`` +Verilog module that is defined in the ``example`` project. The Scala +and Verilog sources follow the prescribed directory layout. + +.. code-block:: none + + generators/example/ + build.sbt + src/main/ + scala/ + GCDMMIOBlackBox.scala + resources/ + vsrc/ + GCDMMIOBlackBox.v + +Defining a Chisel BlackBox +-------------------------- + +A Chisel ``BlackBox`` module provides a way of instantiating a module +defined by an external Verilog source. The definition of the blackbox +includes several aspects that allow it to be translated to an instance +of the Verilog module: + +* An ``io`` field: a bundle with fields corresponding to the portlist of the Verilog module. +* A constructor parameter that takes a ``Map`` from Verilog parameter name to elaborated value +* One or more resources added to indicate Verilog source dependencies + +Of particular interest is the fact that parameterized Verilog modules +can be passed the full space of possible parameter values. These +values may depend on elaboration-time values in the Chisel generator, +as the bitwidth of the GCD calculation does in this example. + +**Verilog GCD port list and parameters** + +.. literalinclude:: ../../generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v + :language: verilog + :start-after: DOC include start: GCD portlist + :end-before: DOC include end: GCD portlist + +**Chisel BlackBox Definition** + +.. literalinclude:: ../../generators/example/src/main/scala/GCDMMIOBlackBox.scala + :language: scala + :start-after: DOC include start: GCD blackbox + :end-before: DOC include end: GCD blackbox + +Instantiating the BlackBox and Defining MMIO +-------------------------------------------- + +Next, we must instantiate the blackbox. In order to take advantage of +diplomatic memory mapping on the system bus, we still have to +integrate the peripheral at the Chisel level by mixing +peripheral-specific traits into a ``TLRegisterRouter``. The ``params`` +member and ``HasRegMap`` base trait should look familiar from the +previous memory-mapped PWM device example. + +.. literalinclude:: ../../generators/example/src/main/scala/GCDMMIOBlackBox.scala + :language: scala + :start-after: DOC include start: GCD instance regmap + :end-before: DOC include end: GCD instance regmap + +Advanced Features of RegField Entries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One signficant difference from the PWM example is in the peripheral's +memory map. ``RegField`` exposes polymorphic ``r`` and ``w`` methods +that allow read- and write-only memory-mapped registers to be +interfaced to hardware in multiple ways. + +* ``RegField.r(2, status)`` is used to create a 2-bit, read-only register that captures the current value of the ``status`` signal when read. +* ``RegField.r(params.width, gcd)`` "connects" the decoupled handshaking interface ``gcd`` to a read-only memory-mapped register. When this register is read via MMIO, the ``ready`` signal is asserted. This is in turn connected to ``output_ready`` on the Verilog blackbox through the glue logic. +* ``RegField.w(params.width, x)`` exposes a plain register (much like those in the PWM example) via MMIO, but makes it write-only. +* ``RegField.w(params.width, y)`` associates the decoupled interface signal ``y`` with a write-only memory-mapped register, causing ``y.valid`` to be asserted when the register is written. + +Since the ready/valid signals of ``y`` are connected to the +``input_ready`` and ``input_valid`` signals of the blackbox, +respectively, this register map and glue logic has the effect of +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. + +Defining a Chip with a GCD Peripheral +--------------------------------------- + +As with the PWM example, a few more pieces are needed to tie the system together. + +**Composing traits into a complete cake pattern peripheral** + +.. literalinclude:: ../../generators/example/src/main/scala/GCDMMIOBlackBox.scala + :language: scala + :start-after: DOC include start: GCD cake + :end-before: DOC include end: GCD cake + +Note the differences arising due to the fact that this peripheral has +no top-level IO. To build a complete system, a new ``Top`` and new +``Config`` objects are added in a manner exactly analogous to the PWM +example. + +Software Testing +---------------- + +The GCD module has a slightly more complex interface, so polling is +used to check the status of the device before each triggering read or +write. + +.. literalinclude:: ../../tests/gcd.c + :language: scala + :start-after: DOC include start: GCD test + :end-before: DOC include end: GCD test diff --git a/docs/Customization/index.rst b/docs/Customization/index.rst index c0432e7386..73d6806846 100644 --- a/docs/Customization/index.rst +++ b/docs/Customization/index.rst @@ -15,5 +15,6 @@ Hit next to get started! Heterogeneous-SoCs Adding-An-Accelerator + Incorporating-Verilog-Blocks Memory-Hierarchy Boot-Process diff --git a/generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v b/generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v new file mode 100644 index 0000000000..9104ae878c --- /dev/null +++ b/generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v @@ -0,0 +1,48 @@ +// DOC include start: GCD portlist +module GCDMMIOBlackBox + #(parameter WIDTH) + ( + input clock, + input reset, + output input_ready, + input input_valid, + input [WIDTH-1:0] x, + input [WIDTH-1:0] y, + input output_ready, + output output_valid, + output reg [WIDTH-1:0] gcd + ); +// DOC include end: GCD portlist + + localparam S_IDLE = 2'b00, S_RUN = 2'b01, S_DONE = 2'b10; + + reg [1:0] state; + reg [WIDTH-1:0] tmp; + + assign input_ready = state == S_IDLE; + assign output_valid = state == S_DONE; + + always @(posedge clock) begin + if (reset) + state <= S_IDLE; + else if (state == S_IDLE && input_valid) + state <= S_RUN; + else if (state == S_RUN && tmp == 0) + state <= S_DONE; + else if (state == S_DONE && output_ready) + state <= S_IDLE; + end + + always @(posedge clock) begin + if (state == S_IDLE && input_valid) begin + gcd <= x; + tmp <= y; + end else if (state == S_RUN) begin + if (gcd > tmp) + gcd <= gcd - tmp; + else + tmp <= tmp - gcd; + end + end + +endmodule // GCDMMIOBlackBox diff --git a/generators/example/src/main/scala/ConfigMixins.scala b/generators/example/src/main/scala/ConfigMixins.scala index 9d92d896ce..7d7e74af03 100644 --- a/generators/example/src/main/scala/ConfigMixins.scala +++ b/generators/example/src/main/scala/ConfigMixins.scala @@ -87,6 +87,14 @@ class WithPWMAXI4Top extends Config((site, here, up) => { Module(LazyModule(new TopWithPWMAXI4()(p)).module) }) +/** + * Class to specify a top level BOOM and/or Rocket system with a TL-attached GCD device + */ +class WithGCDTop extends Config((site, here, up) => { + case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => + Module(LazyModule(new TopWithGCD()(p)).module) +}) + /** * Class to specify a top level BOOM and/or Rocket system with a block device */ diff --git a/generators/example/src/main/scala/GCDMMIOBlackBox.scala b/generators/example/src/main/scala/GCDMMIOBlackBox.scala new file mode 100644 index 0000000000..891fe1c9fe --- /dev/null +++ b/generators/example/src/main/scala/GCDMMIOBlackBox.scala @@ -0,0 +1,98 @@ +package example + +import chisel3._ +import chisel3.util._ +import chisel3.core.{IntParam, Reset} +import freechips.rocketchip.amba.axi4._ +import freechips.rocketchip.subsystem.BaseSubsystem +import freechips.rocketchip.config.{Parameters, Field} +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.regmapper.{HasRegMap, RegField} +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.util.UIntIsOneOf + +// DOC include start: GCD blackbox +class GCDMMIOBlackBox(w: Int) extends BlackBox(Map("WIDTH" -> IntParam(w))) with HasBlackBoxResource { + val io = IO(new Bundle { + val clock = Input(Clock()) + val reset = Input(Bool()) + val input_ready = Output(Bool()) + val input_valid = Input(Bool()) + val x = Input(UInt(w.W)) + val y = Input(UInt(w.W)) + val output_ready = Input(Bool()) + val output_valid = Output(Bool()) + val gcd = Output(UInt(w.W)) + }) + + addResource("/vsrc/GCDMMIOBlackBox.v") +} +// DOC include end: GCD blackbox + +// DOC include start: GCD instance regmap +case class GCDParams(address: BigInt, beatBytes: Int, width: Int) + +trait GCDModule extends HasRegMap { + implicit val p: Parameters + def params: GCDParams + val clock: Clock + val reset: Reset + + val impl = Module(new GCDMMIOBlackBox(params.width)) + + // How many clock cycles in a PWM cycle? + val x = Reg(UInt(params.width.W)) + val y = Wire(new DecoupledIO(impl.io.y)) + val gcd = Wire(new DecoupledIO(impl.io.gcd)) + val status = Cat(impl.io.input_ready, impl.io.output_valid) + + 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 + + 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 cake +class GCD(c: GCDParams)(implicit p: Parameters) + extends TLRegisterRouter( + c.address, "gcd", Seq("ucbbar,gcd"), + beatBytes = c.beatBytes)( + new TLRegBundle(c, _))( + new TLRegModule(c, _, _) with GCDModule) + +trait HasPeripheryGCD { this: BaseSubsystem => + implicit val p: Parameters + + private val address = 0x2000 + private val portName = "gcd" + private val gcdWidth = 32 + + val gcd = LazyModule(new GCD( + GCDParams(address, pbus.beatBytes, gcdWidth))(p)) + + pbus.toVariableWidthSlave(Some(portName)) { gcd.node } +} + +trait HasPeripheryGCDModuleImp extends LazyModuleImp { + implicit val p: Parameters + val outer: HasPeripheryGCD +} + +// DOC include end: GCD cake diff --git a/generators/example/src/main/scala/RocketConfigs.scala b/generators/example/src/main/scala/RocketConfigs.scala index 0acf7284f6..5384240e45 100644 --- a/generators/example/src/main/scala/RocketConfigs.scala +++ b/generators/example/src/main/scala/RocketConfigs.scala @@ -57,6 +57,13 @@ class PWMAXI4RocketConfig extends Config( new freechips.rocketchip.subsystem.WithNBigCores(1) ++ new freechips.rocketchip.system.BaseConfig) +class GCDRocketConfig extends Config( // add MMIO GCD module + new WithGCDTop ++ + new WithBootROM ++ + new freechips.rocketchip.subsystem.WithInclusiveCache ++ + new freechips.rocketchip.subsystem.WithNBigCores(1) ++ + new freechips.rocketchip.system.BaseConfig) + class SimBlockDeviceRocketConfig extends Config( new testchipip.WithBlockDevice ++ // add block-device module to peripherybus new WithSimBlockDeviceTop ++ // use top with block-device IOs and connect to simblockdevice diff --git a/generators/example/src/main/scala/Top.scala b/generators/example/src/main/scala/Top.scala index 94bed0de12..b7f1a5002d 100644 --- a/generators/example/src/main/scala/Top.scala +++ b/generators/example/src/main/scala/Top.scala @@ -53,6 +53,16 @@ class TopWithPWMAXI4Module(l: TopWithPWMAXI4) extends TopModule(l) //--------------------------------------------------------------------------------------------------------- +class TopWithGCD(implicit p: Parameters) extends Top + with HasPeripheryGCD { + override lazy val module = new TopWithGCDModule(this) +} + +class TopWithGCDModule(l: TopWithGCD) extends TopModule(l) + with HasPeripheryGCDModuleImp + +//--------------------------------------------------------------------------------------------------------- + class TopWithBlockDevice(implicit p: Parameters) extends Top with HasPeripheryBlockDevice { override lazy val module = new TopWithBlockDeviceModule(this) diff --git a/tests/gcd.c b/tests/gcd.c new file mode 100644 index 0000000000..a89abf6597 --- /dev/null +++ b/tests/gcd.c @@ -0,0 +1,42 @@ +#include "mmio.h" + +#define GCD_STATUS 0x2000 +#define GCD_X 0x2004 +#define GCD_Y 0x2008 +#define GCD_GCD 0x200C + +unsigned int gcd_ref(unsigned int x, unsigned int y) { + while (y != 0) { + if (x > y) + x = x - y; + else + y = y - x; + } + return x; +} + +// DOC include start: GCD test +int main(void) +{ + uint32_t result, ref, x = 20, y = 15; + + // wait for peripheral to be ready + while ((reg_read8(GCD_STATUS) & 0x2) == 0) ; + + reg_write32(GCD_X, x); + reg_write32(GCD_Y, y); + + + // wait for peripheral to complete + while ((reg_read8(GCD_STATUS) & 0x1) == 0) ; + + result = reg_read32(GCD_GCD); + ref = gcd_ref(x, y); + + if (result != ref) { + printf("Hardware result %d does not match reference value %d\n", result, ref); + return 1; + } + return 0; +} +// DOC include end: GCD test