Memory Slave Example

Unlike the Memory Master Example, it is more common for the user to implement a custom memory Slave to interface with hardware that uses a proprietary protocol.

Python and C++ subclasses of the Slave class can be used interchangeably, allowing c++ subclasses to service memory transactions from python masters and python subclasses to receive service memory transactions initiated by c++ masters.

In the below example assume there is a a protocol that has the following functions to initiate a read and write transaction:

  • protocolRead(id, address, size)

  • protocolWrite(id, address, size, data)

And executes the following callbacks when the transaction completes:

  • protocolReadDone(id, data, result)

  • protocolWriteDone(id, result)

See the SrpV3 protocol implementation in Rogue for a more detailed example.

See Slave for more detail on the Slave class.

import pyrogue
import rogue.interfaces.memory

# Create a subclass of a memory Slave
# Assume our min size = 4 bytes and our max size is 1024 bytes
class MyMemSlave(rogue.interfaces.memory.Slave):

    # Init method must call the parent class init
    def __init__(self):

        # Here we set min and max size
        super().__init__(4,1024)

    # Entry point for incoming transaction
    def _doTransaction(self,transaction):

        # First we lock the transaction data
        with transaction.lock():

            # Next store the transaction in the local map so we
            # can retrieve it when the protocol returns
            self._addTransaction(transaction)

            # Handle write or post
            if transaction.type() == rogue.interfaces.memory.Write or
               transaction.type() == rogue.interfaces.memory.Post:

               # Copy data into byte array
               data = bytearray(transaction.size())
               transaction.getData(data,0)

               # Next forward the transaction data to our protocol
               protocolWrite(transaction.id(),
                             transaction.address(),
                             transaction.size(), data)

            # Handle read or verify read
            else:

               # Next forward the transaction data to our protocol
               protocolRead(transaction.id,
                            transaction.address(),
                            transaction.size())

            # We are done for now until protocol returns below

    # Protocol callback for write complete
    def protocolWriteDone(self,id,result):

        # First attempt to retrieve transaction
        tran = self._getTransaction(id)

        # Transaction is no longer valid
        if tran is None:
            return

        # Lock transaction
        with tran.lock():

            # make sure transaction is not stale
            if tran.expired():
                return

            # If an error occurred, complete result with bus fail
            if not result:
                tran.error(f"Got a bad result: {result}")

            # Otherwise complete transaction with success
            else:
                tran.done()

    # Protocol callback for read complete
    def protocolReadDone(self,id,data,result):

        # First attempt to retrieve transaction
        tran = self._getTransaction(id)

        # Transaction is no longer valid
        if tran is None:
            return

        # Lock transaction
        with tran.lock():

            # make sure transaction is not stale
            if tran.expired():
                return

            # If an error occurred, complete result with bus fail
            if not result:
                tran.error(f"Got a bad result: {result}")

            # Otherwise set the read data back to the transaction
            # and complete without error
            else:
                tran.setData(data,0)
                tran.done()

The equivalent code in C++ is show below:

#include <rogue/interfaces/memory/Constants.h>
#include <rogue/interfaces/memory/Slave.h>

// Create a subclass of a memory Slave
// Assume our min size = 4 bytes and our max size is 1024 bytes
class MyMemSlave : public rogue::interfaces::memory::Slave {
   public:

      // Create a static class creator to return our custom class
      // wrapped with a shared pointer
      static std::shared_ptr<MyMemSlave> create() {
         static std::shared_ptr<MyMemSlave> ret =
            std::make_shared<MyMemSlave>();
         return(ret);
      }

      // Standard class creator which is called by create
      // Here we set min and max size
      MyMemSlave() : rogue::interfaces::memory::Slave(4,1024) {}

      // Entry point for incoming transaction
      void doTransaction(rogue::interfaces::memory::TransactionPtr tran) {

         // First we lock the transaction data with a scoped lock
         rogue::interfaces::memory::TransactionLockPtr lock = tran->lock();

         // Next store the transaction in the local map so we
         // can retrieve it when the protocol returns
         this->addTransaction(tran);

         // Handle write or post
         if ( tran->type() == rogue::interfaces::memory::Write ||
              tran->type() == rogue::interfaces::memory::Post ) {

            // Next forward the transaction data to our protocol
            // Use pointer directly from transaction
            protocolWrite(tran->id(),
                          tran->address(),
                          tran->size(), tran->begin());

         }

         // Handle read or verify read
         else {

            // Next forward the transaction data to our protocol
            protocolRead(tran->id(),
                         tran->address(),
                         tran->size());
         }

         // We are done for now until protocol returns below

      // Protocol callback for write complete
      void protocolWriteDone(uint32_t id, bool result) {
         rogue::interfaces::memory::TransactionPtr tran;

         // First attempt to retrieve transaction
         if ( (tran = this->getTransaction(id)) == NULL ) return;

         // Lock the transaction data with a scoped lock
         rogue::interfaces::memory::TransactionLockPtr lock = tran->lock();

         // make sure transaction is not stale
         if ( tran->expired() ) return;

         // If an error occured, complete result with bus fail
         if ( ! result ) tran->error("Got a bad protocol result %i",result);

         // Otherwise complete transaction with success
         else tran->done();
      }

      // Protocol callback for read complete
      void protocolReadDone(uint32_t id, uint8_t *data, bool result) {
         rogue::interfaces::memory::TransactionPtr tran;

         // First attempt to retrieve transaction
         if ( (tran = this->getTransaction(id)) == NULL ) return;

         // Lock the transaction data with a scoped lock
         rogue::interfaces::memory::TransactionLockPtr lock = tran->lock();

         // make sure transaction is not stale
         if ( tran->expired() ) return;

         // If an error occurred, complete result with bus fail
         if ( ! result ) tran->error("Got a bad protocol result %i",result);

         // Otherwise set the read data back to the transaction
         // and complete without error
         else {
            std::copy(data,data+tran->size(),tran->begin());
            tran->done();
         }
      }
};

// Shared pointer alias
typedef std::shared_ptr<MyMemSlave> MyMemSlavePtr;