Add Verilog MMIO GCD peripheral example
This commit is contained in:
@@ -147,7 +147,7 @@ lazy val sha3 = (project in file("generators/sha3"))
|
|||||||
.settings(commonSettings)
|
.settings(commonSettings)
|
||||||
|
|
||||||
lazy val tapeout = conditionalDependsOn(project in file("./tools/barstools/tapeout/"))
|
lazy val tapeout = conditionalDependsOn(project in file("./tools/barstools/tapeout/"))
|
||||||
.dependsOn(chisel_testers)
|
.dependsOn(chisel_testers, example)
|
||||||
.settings(commonSettings)
|
.settings(commonSettings)
|
||||||
|
|
||||||
lazy val mdf = (project in file("./tools/barstools/mdf/scalalib/"))
|
lazy val mdf = (project in file("./tools/barstools/mdf/scalalib/"))
|
||||||
|
|||||||
156
docs/Customization/Incorporating-Verilog-Blocks.rst
Normal file
156
docs/Customization/Incorporating-Verilog-Blocks.rst
Normal file
@@ -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
|
||||||
@@ -15,5 +15,6 @@ Hit next to get started!
|
|||||||
|
|
||||||
Heterogeneous-SoCs
|
Heterogeneous-SoCs
|
||||||
Adding-An-Accelerator
|
Adding-An-Accelerator
|
||||||
|
Incorporating-Verilog-Blocks
|
||||||
Memory-Hierarchy
|
Memory-Hierarchy
|
||||||
Boot-Process
|
Boot-Process
|
||||||
|
|||||||
48
generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v
Normal file
48
generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v
Normal file
@@ -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
|
||||||
@@ -87,6 +87,14 @@ class WithPWMAXI4Top extends Config((site, here, up) => {
|
|||||||
Module(LazyModule(new TopWithPWMAXI4()(p)).module)
|
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
|
* Class to specify a top level BOOM and/or Rocket system with a block device
|
||||||
*/
|
*/
|
||||||
|
|||||||
98
generators/example/src/main/scala/GCDMMIOBlackBox.scala
Normal file
98
generators/example/src/main/scala/GCDMMIOBlackBox.scala
Normal file
@@ -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
|
||||||
@@ -57,6 +57,13 @@ class PWMAXI4RocketConfig extends Config(
|
|||||||
new freechips.rocketchip.subsystem.WithNBigCores(1) ++
|
new freechips.rocketchip.subsystem.WithNBigCores(1) ++
|
||||||
new freechips.rocketchip.system.BaseConfig)
|
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(
|
class SimBlockDeviceRocketConfig extends Config(
|
||||||
new testchipip.WithBlockDevice ++ // add block-device module to peripherybus
|
new testchipip.WithBlockDevice ++ // add block-device module to peripherybus
|
||||||
new WithSimBlockDeviceTop ++ // use top with block-device IOs and connect to simblockdevice
|
new WithSimBlockDeviceTop ++ // use top with block-device IOs and connect to simblockdevice
|
||||||
|
|||||||
@@ -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
|
class TopWithBlockDevice(implicit p: Parameters) extends Top
|
||||||
with HasPeripheryBlockDevice {
|
with HasPeripheryBlockDevice {
|
||||||
override lazy val module = new TopWithBlockDeviceModule(this)
|
override lazy val module = new TopWithBlockDeviceModule(this)
|
||||||
|
|||||||
42
tests/gcd.c
Normal file
42
tests/gcd.c
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user