1
votes

I was following some examples of adding peripheries to the rocketchip. I used the sifive-blocks as reference.

below is an example from their I2C example (I hope it's ok to post it here)

case object PeripheryI2CKey extends Field[Seq[I2CParams]]

trait HasPeripheryI2C { this: BaseSubsystem =>
  val i2cNodes =  p(PeripheryI2CKey).map { ps =>
    I2C.attach(I2CAttachParams(ps, pbus, ibus.fromAsync)).ioNode.makeSink()
  }
}

trait HasPeripheryI2CBundle {
  val i2c: Seq[I2CPort]
}

trait HasPeripheryI2CModuleImp extends LazyModuleImp with HasPeripheryI2CBundle {
  val outer: HasPeripheryI2C
  val i2c  = outer.i2cNodes.zipWithIndex.map  { case(n,i) => n.makeIO()(ValName(s"i2c_$i")) }
}

I understand the makeIO step which is take a bundle and apply the IO on it, but don't understand the makeSink step. Why do they do this step , isn't makeIO enough ?

1

1 Answers

1
votes

I'm not an expert in rocket-chip's Diplomacy, but from glancing at the code, I think makeIO and makeSink do fundamentally different things.

makeIO takes the BundleBridgeSource and materializes ports in the Chisel Module implementation for driving that source. There is the same method on BundleBrigeSink. I believe this method is the way you take either side of a Bundle bridge and interface with it in the actual Chisel part of the generator (as opposed to the Diplomatic part).

makeSink turns a BundleBridgeSource into a BundleBridgeSink. It doesn't materialize Chisel ports and it stays in Diplomacy world rather than in the Chisel world.

In the example from I2C you included, note how the part with makeSink is a trait to mix into something that extends BaseSubsystem, it's diplomatic. On the other hand, HasPeripheryI2CModuleImp which has the makeIO extends LazyModuleImp which is the Chisel part. One way to think about this is two different "views" of the same thing. Chisel and Diplomacy use different objects, thus i2cNodes (diplomatic) vs. i2c (Chisel).