Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Verilog MMIO GCD peripheral example
- Loading branch information
1 parent
aa7fd5a
commit c2ce173
Showing
9 changed files
with
371 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
generators/example/src/main/resources/vsrc/GCDMMIOBlackBox.v
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.