How to add another Bus Protocol?

The Wisbone protocol implementation example gives a brief overview regarding what is needed to be done for adding a new protocol support in Caravan.

It will be further discussed in the following guide.

Note

We are using MyBus as the protocol name in this guide for the sake of explanation. This name can be replaced by APB, AHB, AXI, TileLink and any other protocol the developer wants to add support for in the Caravel.

Creating a Configuration Class

First of all, a configuration case class is needed that provides certain parameters to be used by the bus implementation logic, such as address width, data width, and anything other that is protocol specific which extends the BusConfig trait.

case class MyBusConfig
(
    // protocol specific requirements
) extends BusConfig

Implementing the Request/Response Bundle

Secondly, the abstract AbstrRequest and AbstrResponse needs to be concretely implemented to provide the bus adapter with an interface to communicate with the user’s IP.

class MyRequest(implicit val config: MyBusConfig) extends AbstrRequest {
    override val addrRequest: UInt = UInt(config.addressWidth.W)
    override val dataRequest: UInt = UInt(config.dataWidth.W)
    override val activeByteLane: UInt = UInt((config.dataWidth/config.granularity).W)
    override val isWrite: Bool = Bool()
}

class MyResponse(implicit val config: MyBusConfig) extends AbstrResponse {
    override val dataResponse: UInt = UInt(config.dataWidth.W)
    override val error: Bool = Bool()
}

Creating the Master/Slave Bundles

Thirdly, the master/slave bundles are needed to be created which will then be used by the host and slave adapters to communicate with each other using the bus protocol.

For this purpose, first it is required to extend the BusHost and BusDevice classes.

case class MyBusHost() extends BusHost
case class MyBusDevice() extends BusDevice

then create the master/slave bundles which extend from these classes, like the following:

class MyBusMaster(implicit val config: MyBusConfig) extends MyBusHost {
    // protocol specific signals
}

class MyBusSlave(implicit val config: MyBusConfig) extends MyBusDevice {
    // protocol specific signals
}

Creating the Host/Device Adapters

Finally, now it is the time to make a Host Adapter or a Master of the bus, which usually initiates the bus transaction and a Device Adapter or Slave, which responds to the requests.

Note

It is important to extend these adapters from HostAdapter/DeviceAdapter rather than a Module. So, in our case MyBusHost must extend HostAdapter and MyBusDevice must extend DeviceAdapter.

The logic of these adapters solely depend on the developer and the bus protocol they are trying to implement. Though the interface of the adapters should be somewhat similar as the following:

class MyBusHost(implicit val config: MyBusConfig) extends HostAdapter {
    val io = IO(new Bundle {
        val myBusMasterTransmitter = Decoupled(new MyBusMaster())
        val myBusSlaveReceiver  = Flipped(Decoupled(new MyBusSlave()))
        val reqIn = Flipped(Decoupled(new MyRequest()))
        val rspOut = Decoupled(new MyResponse())
    })

    // protocol specific implementation
}

class MyBusDevice(implicit val config: MyBusConfig) extends DeviceAdapter {
    val io = IO(new Bundle {
        val myBusSlaveTransmitter = Decoupled(new MyBusSlave())
        val myBusMasterReceiver = Flipped(Decoupled(new MyBusMaster()))
        val reqOut = Decoupled(new MyRequest())
        val rspIn = Flipped(Decoupled(new MyResponse()))
    })

    // protocol specific implementation
}

Finally, the Harness

The harness is created to test the adapters connected with each other, in order to verify the correct functionality of the protocol conformance.