Skip to content

Commit

Permalink
Add Verilog MMIO GCD peripheral example
Browse files Browse the repository at this point in the history
  • Loading branch information
albert-magyar committed Sep 26, 2019
1 parent aa7fd5a commit c2ce173
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.sbt
Expand Up @@ -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/"))
Expand Down
156 changes: 156 additions & 0 deletions 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
1 change: 1 addition & 0 deletions docs/Customization/index.rst
Expand Up @@ -15,5 +15,6 @@ Hit next to get started!

Heterogeneous-SoCs
Adding-An-Accelerator
Incorporating-Verilog-Blocks
Memory-Hierarchy
Boot-Process
48 changes: 48 additions & 0 deletions 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
8 changes: 8 additions & 0 deletions generators/example/src/main/scala/ConfigMixins.scala
Expand Up @@ -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
*/
Expand Down
98 changes: 98 additions & 0 deletions 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
7 changes: 7 additions & 0 deletions generators/example/src/main/scala/RocketConfigs.scala
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions generators/example/src/main/scala/Top.scala
Expand Up @@ -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)
Expand Down

0 comments on commit c2ce173

Please sign in to comment.