Connecting Devices to Bus

SoC Mixin Traits

Now that we have finished designing our peripheral device, we need to hook it up into the SoC. To do this, we first need to create two traits: one for the lazy module and one for the module implementation. The lazy module trait is the following.

trait HasPeripheryInputStream { this: BaseSubsystem =>
  private val portName = "input-stream"
  val streamWidth = pbus.beatBytes * 8
  val inputstream = LazyModule(new InputStream(0x10017000, pbus.beatBytes))
  pbus.toVariableWidthSlave(Some(portName)) { inputstream.regnode }
  sbus.fromPort(Some(portName))() := inputstream.dmanode
  ibus.fromSync := inputstream.intnode

We add the line this: BaseSubsystem => to indicate that this trait will eventually be mixed into a class that extends BaseSubsystem, which contains the definition of the system bus sbus, peripheral bus pbus, and interrupt bus ibus. We instantiate the InputStream lazy module and give it the base address 0x10017000. We connect the pbus into the register node, DMA node to the sbus, and interrupt node to the ibus.

The module implementation trait is as follows:

trait HasPeripheryInputStreamModuleImp extends LazyModuleImp {
  val outer: HasPeripheryInputStream

  val stream_in = IO(Flipped(Decoupled(UInt(outer.streamWidth.W)))) <> stream_in

  def connectFixedInput(data: Seq[BigInt]) {
    val fixed = Module(new FixedInputStream(data, outer.streamWidth))
    stream_in <>

Since the interrupts and memory ports have already been connected in the lazy module trait, the module implementation trait only needs to create the external decoupled interface and connect that to the InputStream module implementation.

The connectFixedInput method will be used by the test harness to connect an input stream model that just sends a pre-specified stream of data.

Top-Level Design and Configuration

We can now mix these traits into the SoC design. Open up src/main/scala/example/Top.scala and add the following:

class ExampleTopWithInputStream(implicit p: Parameters) extends ExampleTop
    with HasPeripheryInputStream {
  override lazy val module = new ExampleTopWithInputStreamModule(this)

class ExampleTopWithInputStreamModule(outer: ExampleTopWithInputStream)
  extends ExampleTopModuleImp(outer)
  with HasPeripheryInputStreamModuleImp

We can then build a simulation using our new SoC by adding a configuration to src/main/scala/example/Configs.scala. This configuration will cause the test harness to instantiate an SoC with the InputStream device and then connect a fixed input stream model to it.

class WithFixedInputStream extends Config((site, here, up) => {
  case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => {
    val top = Module(LazyModule(new ExampleTopWithInputStream()(p)).module)
      BigInt("1002abcd", 16),
      BigInt("34510204", 16),
      BigInt("10329999", 16),
      BigInt("92101222", 16)))

class FixedInputStreamConfig extends Config(
  new WithFixedInputStream ++ new BaseExampleConfig)

We can now compile the simulation using VCS.

cd vsim
make CONFIG=FixedInputStreamConfig

This will produce a simv-example-FixedInputStreamConfig executable that can be used to run tests. We will discuss how to write and run those tests in the next section.

If you don’t have VCS installed and want to use verilator instead, the commands are similar.

cd verisim
make CONFIG=FixedInputStreamConfig

This creates an executable called simulator-example-FixedInputStreamConfig.