0

My goal is to add counters to the rocket class of the Rocket Core module of the fpga-zynq repository. I want to count parameters like ctrl_stalld, id_stall_fpu ... while the chip is running on a FPGA.

I successfully generated Verilog code and a Vivado project for the default rocket configuration (ZynqConfig). I loaded it onto the ZedBoard's FPGA and got it running. I know how to implement counters in the core, but i'm not sure how to retrieve them from outside.

I figured, that a connection between fpga-zynq/rocket-chip/src/main/scala/rocket/rocket.scala and fpga-zynq/common/src/main/scala/Top.scala probably has to be established, since i'm able to access and further connect the Top modules IO-Ports inside Xilinx Vivado 2016.2. I'd assume that the projects hierarchy has to be backtraced from rocket module to Top module and all IO-Ports of all modules in between connected.

However, i do not quite understand the projects hierarchy. I can't find a connection over the many many modules between rocket and Top. I hope the image clarifies what i' trying to say.

NOT an actual representation of the fpga-zynq projects hierarchy The arrows represent IO connections between modules.

The black dots and "?" represent unknown hierarchy (may be more complex).

This is NOT an actual representation of the fpga-zynq projects hierarchy.

mtosch
  • 351
  • 4
  • 18

1 Answers1

3

As predicted, all the I/O Ports of all the modules between Top and rocket had to be connected. NOTE: The following solution is meant to work only for a Single Core Rocket!

I'm gonna go through the steps necessary to implement a 32-bit counter in the Rocket core and connecting it with the Top module.


Where to find the following classes:

Rocket -- fpga-zynq/rocket-chip/src/main/scala/rocket/rocket.scala

RocketTile -- fpga-zynq/rocket-chip/src/main/scala/rocket/tile.scala

BaseCorePlexModule -- fpga-zynq/rocket-chip/src/main/scala/coreplex/BaseCoreplexModule.scala

BaseTop -- fpga-zynq/rocket-chip/src/main/scala/rocketchip/BaseTop.scala

Top -- fpga-zynq/common/src/main/scala/Top.scala


1. Implementing a counter in the Rocket class

First, we create a new Bundle for all our counters, outside the Rocket class (for this example, it will be just one counter, but you can add more counters and other parameters to the bundle as you go)

class CounterBundle extends Bundle{
   val counter = UInt(OUTPUT, width = 32)
}

We define an instance of this Bundle in the I/O Bundle of the Rocket class.

class Rocket(implicit p: Parameters) extends CoreModule()(p) {
    val io = new Bundle {
      val interrupts = new TileInterrupts().asInput
      val hartid = UInt(INPUT, xLen)
      val imem = new FrontendIO()(p.alterPartial({ case CacheName => "L1I" }))
      val dmem = new HellaCacheIO()(p.alterPartial({ case CacheName => "L1D" }))
      val ptw = new DatapathPTWIO().flip
      val fpu = new FPUIO().flip
      val rocc = new RoCCInterface().flip
      val cBundle = new CounterBundle
    }
// ...
}

Since our "counter" parameter is essentially a wire and we can't assign a wire to itself, we'll use a register to count and transfer its value to our counter. The counter will be incremented if some condition is true and reset back to 0 if reset is active. We put it inside the Rocket class.

val counter_reg = Reg(init = UInt(0, width = 32))
when(/*some condition*/){
  counter_reg := counter_reg + 1.U
}
io.cBundle.counter := counter_reg

That's it for the Rocket class.


2. Connecting the Rocket class CounterBundle with the Top class

2.1 Rocket → RocketTile

A new Rocket class instance is created in the RocketTile class.

Simply add

val cBundle = new CounterBundle

to the RocketTile I/O's. Now let's go ahead and connect the two bundles inside the RocketTile class.

class RocketTile(clockSignal: Clock = null, resetSignal: Bool = null)
(implicit p: Parameters) extends Tile(clockSignal, resetSignal)(p) {
  val buildRocc = p(BuildRoCC)
  val usingRocc = !buildRocc.isEmpty
  val nRocc = buildRocc.size
  val nFPUPorts = buildRocc.filter(_.useFPU).size

  val core = Module(new Rocket)
  io.cBundle := core.io.cBundle
  // ...
}

If you're wondering where the RocketTile I/O's are defined, look for the TileIO class within the same file (tile.scala).


2.2 RocketTile → BaseCorePlexModule

The BaseCorePlexModule holds a set of tiles. But since we are only creating a counter for a single core rocket, we can just connect the Bundle of the first RocketTile in the set.

First, add

val cBundle = new CounterBundle

to the "abstract class BaseCoreplexBundle" right above the BaseCorePlexModule. As you probably figured, the BaseCorePlexBundle holds all the I/O's for the BaseCorePlexModule.

Then, connect this Bundle with the RocketTile Bundle inside the BaseCorePlexModule.

abstract class BaseCoreplexModule[+L <: BaseCoreplex, +B <: BaseCoreplexBundle](
c: CoreplexConfig, l: L, b: => B)(implicit val p: Parameters) extends LazyModuleImp(l) with HasCoreplexParameters {
   val outer: L = l
   val io: B = b

   // Build a set of Tiles
   val tiles = p(BuildTiles) map { _(reset, p) }
   io.cBundle := tiles.head.io.cBundle
   // ...
 }


2.3 BaseCorePlexModule → BaseTop

This is the last connection to be made before going to the Top class, as the Top class holds a parameter "target" that is a child of BaseTop.

Again, add a new instance of the CounterBundle to the I/O's of this class. The "abstract class BaseTopBundle" holds all the I/O's for BaseTop.

Connect the two.

abstract class BaseTopModule[+L <: BaseTop, +B <: BaseTopBundle](
val p: Parameters, l: L, b: => B) extends LazyModuleImp(l) {
  val outer: L = l
  val io: B = b

  val coreplex = p(BuildCoreplex)(outer.c, p)
  io.cBundle := coreplex.io.cBundle
  // ... 
}


2.4 BaseTop → Top

The Top class holds a parameter "target" of type FPGAZynqTop

val target = LazyModule(new FPGAZynqTop(p)).module

At the bottom of the file, FPGAZynqTop can be found as a child of BaseTop. Therefore, we can access the BaseTop I/O's through the "target" parameter.

For the Top I/O, we are not going to add the CounterBundle, but the 32-bit "counter" parameter from the Bundle. This way, the counter can be accessed in Vivado, as the generated Verilog code will hold a 32-bit wire.

Adding the a 32-bit UInt parameter to the Top I/O

val counter = UInt(OUTPUT, width = 32)

Connecting it with the counter from the CounterBundle.

class Top(implicit val p: Parameters) extends Module {
   val io = new Bundle {
     val ps_axi_slave = new NastiIO()(AdapterParams(p)).flip
     val mem_axi = new NastiIO
     val counter = UInt(OUTPUT, width = 32)
   }

   val target = LazyModule(new FPGAZynqTop(p)).module
   val slave = Module(new ZynqAXISlave(1)(AdapterParams(p)))
   io.counter := target.io.cBundle.counter
   // ...
}


Conclusion

Counting Rocket Core parameters while the chip runs on a FPGA is possible with this configuration. I tested it on a ZedBoard.

With this setup you can simply add more counters / parameters to the CounterBundle without having to go through all the modules between Rocket and Top again. Of course, you will still have to also alter the Top module.


EDIT

After adding more and more parameter to the Bundle, i realised that also adding all of these to the Top module every time becomes annoying. You can add the CounterBundle directly to the Top module I/O. In the generated Verilog code, the CounterBundle will be extracted into all it's containing signals.

My new Top module looks like this. Be sure to import the CounterBundle from the Rocket class

import rocket.CounterBundle

class Top(implicit val p: Parameters) extends Module {
   val io = new Bundle {
     val ps_axi_slave = new NastiIO()(AdapterParams(p)).flip
     val mem_axi = new NastiIO
     val cBundle = new CounterBundle
   }

   val target = LazyModule(new FPGAZynqTop(p)).module
   val slave = Module(new ZynqAXISlave(1)(AdapterParams(p)))
   io.cBundle := target.io.cBundle
   // ...
}

The passage in the generated Verilog code for the Top module then looks like this.

module Top(
  input clock,
  input reset,
  // ...
  output [31:0] io_cBundle_counter
);
// ...
endmodule
mtosch
  • 351
  • 4
  • 18