6.10. 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 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 interfacingRegField
entries - Setting up a chip
Top
andConfig
that use the peripheral
6.10.1. 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.
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. This ensures that the Verilog sources are visible
to the downstream FIRRTL passes that provide utilities for integrating
Verilog files into the build process, which are part of the
tapeout
package in barstools/tapeout
.
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 chipyard
project. The Scala
and Verilog sources follow the prescribed directory layout.
generators/chipyard/
build.sbt
src/main/
scala/
example/
GCD.scala
resources/
vsrc/
GCDMMIOBlackBox.v
6.10.2. 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
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,
output busy
);
Chisel BlackBox Definition
class GCDMMIOBlackBox(val w: Int) extends BlackBox(Map("WIDTH" -> IntParam(w))) with HasBlackBoxResource
with HasGCDIO
{
addResource("/vsrc/GCDMMIOBlackBox.v")
}
6.10.3. 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 GCD device example.
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
}
6.10.4. Defining a Chip with a BlackBox¶
Since we’ve parameterized the GCD instantiation to choose between the Chisel and the Verilog module, creating a config is easy.
class GCDAXI4BlackBoxRocketConfig extends Config(
new chipyard.example.WithGCD(useAXI4=true, useBlackBox=true) ++ // Use GCD blackboxed verilog, connect by AXI4->Tilelink
new freechips.rocketchip.subsystem.WithNBigCores(1) ++
new chipyard.config.AbstractConfig)
You can play with the parameterization of the mixin to choose a TL/AXI4, BlackBox/Chisel version of the GCD.
6.10.5. Software Testing¶
The GCD module has a more complex interface, so polling is used to check the status of the device before each triggering read or write.
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;
}
6.10.6. Support for Verilog Within Chipyard Tool Flows¶
There are important differences in how Verilog blackboxes are treated by various flows within the Chipyard framework. Some flows within Chipyard rely on FIRRTL in order to provide robust, non-invasive transformations of source code. Since Verilog blackboxes remain blackboxes in FIRRTL, their ability to be processed by FIRRTL transforms is limited, and some advanced features of Chipyard may provide weaker support for blackboxes. Note that the remainder of the design (the “non-Verilog” part of the design) may still generally be transformed or augmented by any Chipyard FIRRTL transform.
- Verilog blackboxes are fully supported for generating tapeout-ready RTL
- HAMMER workflows offer robust support for integrating Verilog blackboxes
- FireSim relies on FIRRTL transformations to generate a decoupled FPGA simulator. Therefore, support for Verilog blackboxes in FireSim is currently limited but rapidly evolving. Stay tuned!
- Custom FIRRTL transformations and analyses may sometimes be able to handle blackbox Verilog, depending on the mechanism of the particular transform
As mentioned earlier in this section, BlackBox
resource files must
be integrated into the build process, so any project providing
BlackBox
resources must be made visible to the tapeout
project
in build.sbt