3

I've been studying rocket-chip for utilizing diplomacy and I have a decent grasp on the overall structure of how diplomacy works. (I don't understand it totally, but well enough to create some examples on my own). I would like to develop some IP in which the main objective is to have a regmap through the use of a *RegisterRouter.

If I use/modify one of the RegisterNodeExamples from rocket-chip, I get the following:

class MyDeviceController(implicit p: Parameters) extends LazyModule {
  val device = new SimpleDevice("my-device", Seq("tutorial,my-device0"))
  val node = APBRegisterNode(
    //address = Seq(AddressSet(0x10028000, 0xfff)), (Modified since not in APBRegisterNode)
    address = AddressSet(0x002000, 0xfff),
    //device = device,  (Removed since not in APBRegisterNode)
    beatBytes = 8)

  lazy val module = new LazyModuleImp(this) {
    
    val bigReg = RegInit(0.U(64.W))
    val mediumReg = RegInit(0.U(32.W))
    val smallReg = RegInit(0.U(16.W))

    val tinyReg0 = RegInit(0.U(4.W))
    val tinyReg1 = RegInit(0.U(4.W))

    node.regmap(
      0x00 -> Seq(RegField(64, bigReg)),
      0x08 -> Seq(RegField(32, mediumReg)),
      0x0C -> Seq(RegField(16, smallReg)),
      0x0E -> Seq(
        RegField(4, tinyReg0),
        RegField(4, tinyReg1)))
  }
}

I'm using APB at the moment as I'm very familiar with AMBA protocols and it has the smallest code base under the diplomacy package. And I could make is so either of the AMBA or TL protocols are used later.

My Question

Is there a way to generate verilog just for MyDeviceController as a stand alone component?

I have not been able to figure this out if there is. Obviously if I just try to instantiate MyDeviceController I will get an error for the inward parameters of node not being connected. I'm not sure if you can give a "dummy" node connection? Or if there is some method that can handle that.

Why I want to do this

It is desirable to test the IP standalone in it's own test environment without a full SoC.

My Current Workaround/Solution

To work around this I essentially created a "wrapper" that creates an APBMasterNode and connects to the APBRegisterNode in MyDeviceController.

class APBMaster()(implicit p: Parameters) extends LazyModule {
  val apbMasterParameters = APBMasterParameters(
    name = "apbMaster"
  )

  val apbMasterPortParameters = APBMasterPortParameters(
    masters = Seq(apbMasterParameters)
  )

  val node = APBMasterNode(
    portParams = Seq(apbMasterPortParameters)
  )

  lazy val module = new LazyModuleImp(this) {
    val io = IO(new Bundle {
      val wtf   = Output(Bool())
      val start = Input(Bool())
    })
        
    val myreg = RegInit(0.U(16.W))
    myreg := myreg + 1.U
    
    val prdata = Wire(UInt(64.W))
    prdata := node.out.head._1.prdata
    //seems to need these things to generate the logic
    io.wtf := node.out.head._1.pready && !(node.out.head._1.prdata === 0.U)

    node.out.head._1.pstrb    := 63.U
    node.out.head._1.pprot    := 0.U
        
    when(myreg(3,0) === 8.U && io.start) {
      node.out.head._1.paddr    := myreg
      node.out.head._1.psel     := true.B
      node.out.head._1.penable  := false.B
      node.out.head._1.pwrite   := true.B
      node.out.head._1.pwdata   := myreg + 1.U
    } .elsewhen(myreg(3,0) === 9.U) {
      node.out.head._1.paddr    := myreg
      node.out.head._1.psel     := true.B
      node.out.head._1.penable  := true.B
      node.out.head._1.pwrite   := true.B
      node.out.head._1.pwdata   := myreg
    } otherwise {
      node.out.head._1.paddr    := 0.U
      node.out.head._1.psel     := false.B
      node.out.head._1.penable  := false.B
      node.out.head._1.pwrite   := false.B
      node.out.head._1.pwdata   := 0.U
    }
    
  }
}

One issue with this, was that I had to create some controls for each of the APB signals. If I did not, the Chisel/FIRRTL compiler/generator would not create any Verilog for MyDeviceController. This is what you see above with the myreg counter being used to do some basic APB transaction.

The wrapper would look like the following:

enter image description here

class APBTop()(implicit p: Parameters) extends LazyModule {
  val master = LazyModule(new APBMaster)
  val slave  = LazyModule(new MyDeviceController()(Parameters.empty))

  slave.node := master.node 

  lazy val module = new LazyModuleImp(this) {
    val io = IO(new Bundle {
      val busy = Output(Bool())
      val wtf  = Output(Bool())
      val start = Input(Bool())
    })
    
    io.busy := true.B
    io.wtf  := master.module.io.wtf
    master.module.io.start := io.start
    
  }
}

I can create this wrapper/master as a typical testing component, then in my testenv just instatiate the MyDeviceController RTL, however I was wondering if there was another solution. It appears that diplomacy is fairly holistic (which I understand why), but was looking for suggestions on how IP level development is tackled for a Diplomatic infrastructure flow.

Thanks

l Steveo l
  • 516
  • 3
  • 11
  • 1
    I think your "workaround/solution" is not far off the state of the art. Fully standalone isn't possible because as you mention, you need parameters for your nodes. That being said, one big improvement you could make is use of `dontTouch` rather than having to create actual logic in the `APBMaster` `LazyModuleImp`. Try `dontTouch(node.out.head._1)`. I see what you mean about how this is a bunch of boiler plate, although useful boiler plate if you also wanted to _test_ the IP since you want to be able to test lots of parameterizations. – Jack Koenig Sep 16 '20 at 00:20
  • 1
    I had not thought about a ``dontTouch``, that would work out nicely. I had to ask as I'm terrible about workarounds that are easily replaced by something simple I just never found :). Thanks for the response. – l Steveo l Sep 16 '20 at 11:36
  • 1
    I think some helper constructs to just "get Verilog" with sensible defaults for parameters would be useful. I've been thinking about it and the main issue is that it seems like it would _only_ be useful for simple Verilog inspection and wouldn't be reusable for writing unit tests or integrating into actual systems where you basically need all of the boiler plate you ended up writing. Maybe there's an abstraction I'm missing though. – Jack Koenig Sep 16 '20 at 19:46
  • I tend to agree. I believe the conundrum would also be that the people who want that wrapper just to get the verilog don't understand diplomacy to a point where it would help them much. – l Steveo l Sep 17 '20 at 09:47

1 Answers1

0

Edit: Update March 2021

It's been a few months and I've spent more time with RocketChip/Chipyard/Diplomacy. This is a better solution, but leaving the old one below.

There is a makeIOs method for several Nodes. Using these we can actually punch out the respective AMBA/TL interface. This allows you to not have to use a wrapper that has nothing but connections.

Here is what it would look like compared to the previous version I suggested

class MyWrapper()(implicit p: Parameters) extends LazyModule {
  //val master = LazyModule(new APBMaster)
  
  val ApbPort = APBMasterNode(
    portParams = Seq(APBMasterPortParameters(masters = Seq(APBMasterParameters(name = "ApbPort"))))
  )
  
  val apbport = InModuleBody {ApbPort.makeIOs()}
  

This reduces the need for the APBMaster dummy class as well.


Just to have an answer, I ended up using a combination of what myself and Jack Koenig went back and forth on.

If time permits I'll see if there is a way to make a "template" or LazyModule wrapper that does this for testing purposes (for each of the main protocols) and submit it to the Chisel repo.

class APBMaster()(implicit p: Parameters) extends LazyModule {
  val apbMasterParameters = APBMasterParameters(
    name = "apbMaster"
  )

  val apbMasterPortParameters = APBMasterPortParameters(
    masters = Seq(apbMasterParameters)
  )

  val node = APBMasterNode(
    portParams = Seq(apbMasterPortParameters)
  )

  
  lazy val module = new LazyModuleImp(this) {
    //The dontTouch here preserves the interface so logic is generated
    dontTouch(node.out.head._1)
  }
}


class MyWrapper()(implicit p: Parameters) extends LazyModule {
  val master = LazyModule(new APBMaster)
  val slave  = LazyModule(new MySlave()(Parameters.empty))

  slave.node := master.node 

  lazy val module = new LazyModuleImp(this) {
    //nothing???
  }
}

object MyTestextends App {SSVPllFreqDetect)))
  (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => LazyModule(new MyWrapper()(Parameters.empty)).module)))
}

l Steveo l
  • 516
  • 3
  • 11