Monday, May 8, 2017

Play and learn

In case anyone stumbles on this blog, this image describes what I'm doing ..


As does this ..


How to test a parameterized module in Chisel

Quick update today.  Busy with real work.  This quick post is written because how to test these modules in not adequately covered in the documentation for chisel-testers.

Consider the case for a parameterizable LSFR module.


class LSFR[T <: Bundle](gen: T = UInt(4.W)) extends Module {
  val io = IO(new Bundle {
    val rand = Output(gen)
  })
  val width = gen.getWidth
  val regLFSR = Reg(UInt(width.W), init = UInt(0))
  when(true.B) {
    regLFSR := Cat(regLFSR(0) ^ regLFSR(1), regLFSR(width - 1, 1))
  }
  io.rand := gen.fromBits(regLFSR)
}

Below I show a test containing two examples.  First using PokeTester and the second using PeekPokeTester.  See if you can spot the difference ...


class LSFRPeekPokeTester[T <: Bundle](gen: T, c: LSFR[T]) extends PeekPokeTester(c) {
  println("peekpoke test")
}

class LSFRSpec extends ChiselFlatSpec with PokeTester {
  behavior of "LSFR"

  it should "elaborate with peek test" in {
    val options = new TesterOptionsManager
    options.setTargetDirName("test_run_dir/LSFRSpec")
    test(new LSFR(new DDRCommand), testerBackend = FirrtlInterpreterBackend, options = options) {
      (t, c) => {
        println("poke test")
      }
    }
  }

  it should "elaborate with peekpoke test" in {
    Driver(() => new LSFR(new DDRCommand)) {
      c => new LSFRPeekPokeTester(new DDRCommand, c)
    }
  }
}

In the PokeTester case, the first argument to test is new LSFR(new DDRCommand).  For PeekPokeTester, you pass a function to generate the module () => new LSFR(new DDRCommand).  Not immediately obvious.  Now, look at the function prototypes for PokeTester and PeekPokeTester respectively ...


// PokeTester source
def test[T <: Module](dutGen: => T, testerBackend: TesterBackend, options: TesterOptionsManager)(block: (InnerTester, T) => Unit) { //[..]

// PeekPokeTester source
def apply[T <: Module](
      dutGen: () => T,
      backendType: String = "firrtl",
      verbose: Boolean = false,
      testerSeed: Long = System.currentTimeMillis())(
      testerGen: T => PeekPokeTester[T]): Boolean = { //[..]

The definition of dutGen is different.

Wise to keep in mind that PokeTester is under iotesters.experimental.

Saturday, May 6, 2017

Use of HellaQueue in Chisel, secondary constructor

This post is an expanded update to an entry in my personal "NOTES" file.  At first glance, I found the following code perplxing, therefore I decided to break it down.

class HellaQueue[T <: Data](val entries: Int)(data: => T) extends Module {
  val io = new QueueIO(data, entries)

  val fq = Module(new HellaFlowQueue(entries)(data))
  fq.io.enq <> io.enq
  io.deq <> Queue(fq.io.deq, 1, pipe = true)
}

object HellaQueue {
  def apply[T <: Data](enq: DecoupledIO[T], entries: Int) = {
    val q = Module((new HellaQueue(entries)) { enq.bits })
    q.io.enq.valid := enq.valid // not using <> so that override is allowed
    q.io.enq.bits := enq.bits
    enq.ready := q.io.enq.ready
    q.io.deq
  }
}

What does the :< operator do in [T <: Data>]?

It is obvious (to me at least) that this is for type parameterization.  The <: operator indicates that the type supplied to the class instance must have chisel3.core.Data as an ancestor.

Why does (data: => T) do?

No suprise that (val entries: Int) is the primary constructor.  The fat arrow (=>) implies a function.  The first argument to the constructor lets you specific the number of entries.  The second argument data helps to parameterize the class.


// example usage
val xy: Data
val hq1 = (new HellaQueue(entries = 90)){xy}
// equivalent to above, without (data: => T) in the class constructor
val hq2 = (new HellaQueue[SInt](entries = 90))

Why is a class definion and a object definition of the same name?

The object is known as a companion class, consult stackoverflow.com for more info

What does the apply function do in the object?

The apply function acts as an auxillary constructor.  Allows alternative syntax for creating HellaQueue.

Wednesday, May 3, 2017

Using IDEA Scala debugger for Chisel

This a quick follow up on an older post where I set up IntelliJ IDEA and Scala.  I wrote this to make sure my steps for getting the debugger up and running were reproducable.  The steps assume you have a working Chisel project that compiles using sbt.

Check if sbt compile works from the command line for your Chisel project ...


[plus]:~/gitrepos/plus/arbiterdemo$ sbt compile
[info] Loading global plugins from /home/edc/.sbt/0.13/plugins
[info] Loading project definition from /home/edc/gitrepos/plus/arbiterdemo/project
[info] Set current project to memctrldemo (in build file:/home/edc/gitrepos/plus/arbiterdemo/)
[info] Updating {file:/home/edc/gitrepos/plus/arbiterdemo/}arbiterdemo...
[..]
[info] Done updating.
[info] Compiling 3 Scala sources to /home/edc/gitrepos/plus/arbiterdemo/target/scala-2.11/classes...
[success] Total time: 4 s, completed May 3, 2017 8:26:36 PM

Import project from SBT, select the relevant build.sbt file ...



Use the default project settings ...


Right click on your class that contains call to chisel3.Driver.execute, hit Run ...


Follow up by checking that that Verilog code is generated as expected (yes)

Finally, try setting a breakpoint by right clicking to the left of the source window and hit shift-F9 to begin debug.  I was 100% successfull following only these steps on IDEA 2017.1 with the project-specific IDEA settings (i.e. .idea/ diretory) wiped out prior.

Gentle introduction to diplomacy

In a previous post, I was looking at how to use the diplomacy and uncore libraries (in Rocket Chip Generator) to hook up a simple AHB master module to AHB slave module.  To my knowledge, none of the library code is documented.  IDEA's debugger came in handy to grow my understanding of the code base.  I can confirm the wiring portion now works.  May come back to this in the future.

Master module


class Master()(implicit p: Parameters) extends LazyModule
{
  val ahbMasterParameters = AHBMasterParameters(
    // nothing for now ..
  )

  val ahbMasterPortParameters = AHBMasterPortParameters(
    masters = Seq(ahbMasterParameters)
  )

  val node = AHBMasterNode(
    portParams = Seq(ahbMasterPortParameters)
  )

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val out = node.bundleOut
    }
  }
}

Slave module

class Slave()(implicit p: Parameters) extends LazyModule
{
  val ahbSlaveParameters = AHBSlaveParameters(
    address = Seq(AddressSet(0x0, 0xFFFF)),
    regionType    = RegionType.UNCACHED,
    executable    = true,
    supportsRead  = TransferSizes(1, 4),
    supportsWrite = TransferSizes(1, 4)
  )

  val ahbSlavePortParameters = AHBSlavePortParameters(
    slaves = Seq(ahbSlaveParameters),
    beatBytes = 4
  )

  val node = AHBSlaveNode(
    portParams = Seq(ahbSlavePortParameters)
  )

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val in = node.bundleIn
    }
  }
}

Connect master to slave

class Slave()(implicit p: Parameters) extends LazyModule
{
  val ahbSlaveParameters = AHBSlaveParameters(
    address = Seq(AddressSet(0x0, 0xFFFF)),
    regionType    = RegionType.UNCACHED,
    executable    = true,
    supportsRead  = TransferSizes(1, 4),
    supportsWrite = TransferSizes(1, 4)
  )

  val ahbSlavePortParameters = AHBSlavePortParameters(
    slaves = Seq(ahbSlaveParameters),
    beatBytes = 4
  )

  val node = AHBSlaveNode(
    portParams = Seq(ahbSlavePortParameters)
  )

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val in = node.bundleIn
    }
  }
}

Top level hookup

class ChiselTopTest extends ChiselFlatSpec {

  class ChiselTopTester(c: ChiselTopWrapper) extends PeekPokeTester(c) {
    println("Hello World!")
  }

  // https://github.com/ucb-bar/rocket-chip/issues/359
  class ChiselTopWrapper(p: Parameters) extends Module {
    // must wrap Module into Module() ..
    val top = Module(LazyModule(new ChiselTop()(p)).module)
    // must wrap IO into IO(), thought it wasn't necessary with Chisel3 ..
    val io = IO(top.io.cloneType)
    io <> top.io
  }

  implicit val p: Parameters = new BlankConfig
  chisel3.iotesters.Driver(() => new ChiselTopWrapper(p)) {
    c => new ChiselTopTester(c)
  }
}

Generated verilog (abridged), woo!

module ChiselTop(
  input   clock,
  input   reset,
  input   io_ddrClock
);
  Master_master master (
    .io_out_0_hwdata(master_io_out_0_hwdata),
    .io_out_0_hrdata(master_io_out_0_hrdata) //--TODO: check naming
  );
  Slave_slave slave (
    .io_in_0_hwdata(slave_io_in_0_hwdata),
    .io_in_0_hrdata(slave_io_in_0_hrdata)
  );
  // slave to master for read data
  assign master_io_out_0_hrdata = slave_io_in_0_hrdata;
  // master to slave for write data
  assign slave_io_in_0_hwdata = master_io_out_0_hwdata;
endmodule

... there might be a bug in naming of ports for hrdata.

Tuesday, May 2, 2017

One simple command and voila ...


I have been trying to get IntelliJ IDEA's debugger working with Scala (support via plugin) for the better part of this past week.  Each time the debugger was launched, I hit an issue that presented itself as a runtime exception: ClassNotFoundException for: org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.

My initial guess was "JAR Hell".  The best trick I found for debugging library dependency issues is using sbt-depdency-graph plugin to visualize the dependency tree using sbt.  To make use of the plugin you you need to add the following line to your plugins.sbt file.  Use command sbt dependencyTree to view the output (library dependency tree) of the plugin.

addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2")

Turns it that it wasn't a library dependency issue.  This is the hint that finally unblocked me:

Connected to the target VM, address: '127.0.0.1:36512', transport: 'socket'

Luckily enough, I knew that something was up with my firewall as my Jenkins instance was no longer accepting connections.  I had yet to debug this.  On further inspection, I found my workstation's firewall rules had been completely wiped.  The effect was no local TCP connections werepossible.  The simple solution was to re-add the loopback network interface (lo) to firewalld and apply the trusted firewall zone to it.  The not-so-simple solution, aka. the one that I first tried, was to identify the ports that IDEA's Scala plugin uses for local remote debugging.  It turns out the port choice is random, thus the simple solution.

% firewall-cmd --zone=trusted --add-interface=lo --permanent

The debugger works fine now.