Memory Hub Example

The memory Hub interfaces between an upstream memory Master object and a downstream Memory slave object. The Hub can be used to group Masters together with a common offset, as is done in the pyrogue.Device class. It can also be used to manipulate a memory bus transaction.

Most users will not need to modify or sub-class a memory hub. Instead the Device python class, which is a sub-class of the Hub, will be used to organize the memory map. In some special cases complicated hardware interface layers will need to be supported and a special Hub will need to be created.

In this example we service incoming read and write requests and forward them to a memory slave which implements a paged memory space. This means there is a write register for the address (0x100), a write register for the write data (0x104), and a read register for read data (0x108). This example is not very efficient in that it only allows a single transaction to be executed at a time.

See Hub for more detail on the Hub class.

Python Raw Hub Example

Below is an example of creating a raw Hub device which translates memory transactions in Python.

import pyrogue
import rogue.interfaces.memory

# Create a subclass of a memory Hub
# This hub has an offset of 0
class MyHub(rogue.interfaces.memory.Hub):

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

        # Here we set the offset and a new root min and
        # max transaction size
        super().__init__(0,4,4)

        # Create a lock
        self._lock = threading.Lock()

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

        # First lock the memory space to avoid
        # overlapping paged transactions
        with self._lock:

           # Next we lock the transaction data
           # Here it is held until the downstream transaction completes.
           with transaction.lock():

               # Put address into byte array, we do this because we will
               # need to pass it as the data field of a transaction later
               # The address the HUB sees is always relative, not absolute
               addr = transaction.address().to_bytes(4, 'little', signed=False)

               # Clear any existing errors
               self._setError(0)

               # Create transaction setting address register (offset 0x100)
               id = self._reqTransaction(self._getAddress() | 0x100, addr, 4, 0, rogue.interfaces.memory.Write)

               # Wait for transaction to complete
               self._waitTransaction(id)

               # Check transaction result, forward error to incoming transaction
               if self._getError() != "":
                   transaction.error(self._getError())
                   return False

               # 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)

                  # Create transaction setting write data register (offset 0x104)
                  id = self._reqTransaction(self._getAddress() | 0x104, data, 4, 0, rogue.interfaces.memory.Write)

                  # Wait for transaction to complete
                  self._waitTransaction(id)

                  # Check transaction result, forward error to incoming transaction
                  if self._getError() != "":
                      transaction.error(self._getError())
                      return False

                  # Success
                  transaction.done()

               # Handle read or verify read
               else:

                  # Create read data byte array
                  data = bytearray(transaction.size())

                  # Create transaction reading read data register (offset 0x108)
                  id = self._reqTransaction(self._getAddress() | 0x108, data, 4, 0, rogue.interfaces.memory.Read)

                  # Wait for transaction to complete
                  self._waitTransaction(id)

                  # Check transaction result, forward error to incoming transaction
                  if self._getError() != "":
                      transaction.error(self._getError())
                      return False

                  # Copy data into original transaction and complete
                  transaction.setData(data,0)
                  transaction.done()

Python Device Hub Example

Below is an example of implementing the above example in a Device subclass. This allows the Hub to interact in a standard PyRogue tree. It will have its own base address and size in the downstream address map, but expose a separate upstream address map for translated transactions. More information about the Device class is included at TBD.

import pyrogue
import rogue.interfaces.memory

# Create a subclass of a Device
class MyTranslationDevice(pyrogue.Device):

    # Init method with the same signature as a Device
    def __init__(self, *,
                 name=None,
                 description='',
                 memBase=None,
                 offset=0,
                 hidden=False,
                 expand=True,
                 enabled=True,
                 enableDeps=None):

        # Setup base class with size of 3*8 bytes for our local 3 registers and a
        # upstream min and max transaction size of 4 bytes.
        super().__init__(name=name, description=description, memBase=memBase,
                         offset=offset, hidden=hidden, expand=expand, enabled=enabled,
                         enableDeps=enableDeps, size=12, hubMin=4, hubMax=4)

    # Same code from previous section with the exception that the existing Device
    # lock is used instead of a separate lock as above.
    def _doTransaction(self,transaction):

        # First lock the memory space to avoid
        # overlapping paged transactions
        with self._memLock:

Using Python Device Hub

Below is an example of how the above Device would be used in a PyRogue tree. More information about the PyRogue Root class is included at TBD.

import pyrogue

class ExampleRoot(pyrogue.Root):

    def __init__(self):
        super().__init__(name="MyRoot")

        # Add FPGA device at 0x1000 which hosts paged master
        self.add(SomeFpgaDevice(name="Fpga", offset=0x1000))

        # Add our translation device to the FPGA with relative offset 0x10
        # its new address becomes 0x1010 and it owns a new address space
        self.Fpga.add(MyTranslationDevice(name="TranBase", offset=0x10))

        # Add sub device which exists in paged address space
        # its address is 0x200 in the spaced mastered by TranBase
        self.TranBase.add(SomeDevice(name="devA", offset=0x200))

C++ Raw Hub Example

Below is an example of creating a raw Hub device which translates memory transactions in C++.

#include <rogue/interfaces/memory/Constants.h>
#include <rogue/interfaces/memory/Hub.h>
#include <boost/thread.hpp>

// Create a subclass of a memory Hub
class MyHub : public rogue::interfaces::memory::Hub {

      // Mutex
      boost::mutex mtx_;

   public:

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

      // Standard class creator which is called by create
      // Here we set offset
      MyHub() : rogue::interfaces::memory::Hub(0) {}

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

         // First lock the memory space to avoid overlapping paged transactions
         boost::lock_guard<boost::mutex> lock(slaveMtx_);

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

         // Clear any existing errors
         this->setError(0)

         // Create transaction setting address register (offset 0x100)
         // The address the HUB sees is always relative, not absolute
         id = this->reqTransaction(this->getAddress() | 0x100, 4, transaction->address(),
                                   rogue::interfaces::memory::Write);

         // Wait for transaction to complete
         this->waitTransaction(id);

         // Check transaction result, forward error to incoming transaction
         if ( this->getError() != "" ) {
            transaction->error(this->getError());
            return false
         }

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

            // Create transaction setting write data register (offset 0x104)
            // Forward data pointer from original transaction
            id = this->reqTransaction(this->getAddress() | 0x104, 4, transaction->begin(),
                                      rogue::interfaces::memory::Write);

            // Wait for transaction to complete
            this->waitTransaction(id);

            // Check transaction result, forward error to incoming transaction
            if ( this->getError() != "" ) {
               transaction->error(this->getError());
               return false
            }
            else transaction->done();
         }

         // Handle read or verify read
         else {

            // Create transaction getting read data register (offset 0x108)
            // Forward data pointer from original transaction
            id = this->reqTransaction(this->getAddress() | 0x104, 4, transaction->begin(),
                                      rogue::interfaces::memory::Write);

            // Wait for transaction to complete
            this->waitTransaction(id);

            // Check transaction result, forward error to incoming transaction
            if ( this->getError() != "" ) {
               transaction->error(this->getError());
               return false
            }
            else transaction->done();
         }

};

A few notes on the above examples.

The incoming transaction source thread will be stalled as we wait on the downstream transaction to complete. It may be better to queue the transaction and service it with a separate thread. Also in the C++ example the original data buffer is passed to the new transaction. This requires that the lock be held on the transaction until the downstream transaction is complete. Instead it may be better to create a new buffer and copy the data as is done in the Python example. See the Memory Slave Example example for ways to store and later retrieve the Transaction record while the downstream transaction is in progress.