Review Board 2.0.15


Port: Separate the port and the interface protocol

Review Request #1301 - Created July 9, 2012 and updated

Information
Andreas Hansson
gem5
default
Reviewers
Default
Changeset 9131:ae59c0189c96
---------------------------
Port: Separate the port and the interface protocol

This patch splits the port and its interface into a very basic stack.
The port itself dictates how things are connected and governs the
physical topology of the system. On top of this, the port interfaces
(implemented by handlers) are responsible for the transport functions,
e.g. send/recv for functional, atomic and timing, and also for
functions like querying of address ranges and block size.

Why are we doing this? This patch enables us to gradually introduce
the 4-phase ports as an alternative interface to the classic send/recv
interface. In theory, it would also enable Ruby to rely on the normal
ports for structurally assembling the system, and then use a custom
Ruby interface. If we want different protocols at different locations
in the system it is thus possible.

So what does this patch actually change? Brace yourself, as it
is quite extensive. First and foremost each port now has two template
parameters, one for the exposed interface of the port itself (i.e. the
sending and receiving functionality) and one for the interface
required from the peer port (the receiving end of the port's own
sending interface). For example, for a conventional master port, this
would now be IfaceMasterPort<MasterInterface, SlaveInterface>, where
the parameters can be left out as they are the default values. This
corresponds to a port on which we can use the MasterInterface, and
bind to a peer port with a SlaveInterface.

For convenience, the name MasterPort and SlavePort are type defined to
be a master and slave with the classic MasterInterface and
SlaveInterface, as exemplified above. This is sufficient in most
typical cases, where no additional functionality is required from the
sending interface. In the case of e.g. a DMA port, additional sending
functions like dmaAction require the port to expose a DMA-specific
interface, which extends the MasterInterface. This is now implemented
in a DmaHandler (the name could be discussed with options including
Handler, Interface, Iface, IF), which inherits from the
MasterInterface, and besides implementing e.g. recvTimingResp also
adds the desired DMA functions. By declaring a
IfaceMasterPort<DmaHandler> we get a master port with the desired DMA
interface (which still binds to a SlaveInterface).

The implementation of the interfaces is, as already mentioned, done in
a handler. Where there previously used to be e.g. a WalkerPort, there
is now a WalkerHandler and a MasterPort that uses the handler. We
could consider naming the Handlers Interface or Iface instead as
already mentioned. The name handler was chosen as they actually
implement the interface functions.

To enable any arbitrary specialisation of the interfaces, the
protcol-agnostic base ports have bind functions that call a virtual
bind function on the interface base class. It is then up to the
specific implementation to overload this function and verify the
compatibility. Thus, the ports pass the binding responsibility to the
interface, and the master interface tries to cast the slave interface
to the right class.

The separation of the port and the interface is done in a similar
manner to TLM-2.0, with the interface of the port being accessible by
overloading the pointer operator. Although this might seem like C++
abuse, it makes for a far-less verbose way of accessing the interface
compared to e.g. a member function. As a result of this change, where
it used to say port.send, it will now say port->send, and similarly a
port->send becomes (*port)->send.

As can be seen e.g. in the CPU, the separation of the port and the
interface also enable the BaseCPU to declare the instPort and
dataPort, and then leave it up to the specific CPU model to provide
the interface handlers. Thus, the atomic, timing, inorder and O3 CPU
can all implement their own independent handlers, and merely pass them
on to the BaseCPU. A similar approach is used in the cache where a
handler is created in the subclass and passed to the base cache
parent that holds the ports themselves.

An additional benefit of this separation is that the module itself can
implement the interface. This is done in the SimpleMemory, where the
port handler was a trivial class that only added a level of
indirection (port.recvAtomic calls memory.recvAtomic). Instead of
having this handler, the port is now assigned the SimpleMemory itself
as the handler, and the SimpleMemory is both a MemObject and a
SlaveInterface.
util/regress all passing (disregarding t1000 and eio)

Issue Summary

2 0 1 1
Description From Last Updated Status
Review request changed
Updated (July 24, 2012, 5:18 p.m.)

Description:

~  

Changeset 9126:11e228ae7ed4

  ~

Changeset 9131:ae59c0189c96

   
   

Port: Separate the port and the interface protocol

   
   

This patch splits the port and its interface into a very basic stack.

    The port itself dictates how things are connected and governs the
    physical topology of the system. On top of this, the port interfaces
    (implemented by handlers) are responsible for the transport functions,
    e.g. send/recv for functional, atomic and timing, and also for
    functions like querying of address ranges and block size.

   
   

Why are we doing this? This patch enables us to gradually introduce

    the 4-phase ports as an alternative interface to the classic send/recv
    interface. In theory, it would also enable Ruby to rely on the normal
    ports for structurally assembling the system, and then use a custom
    Ruby interface. If we want different protocols at different locations
    in the system it is thus possible.

   
   

So what does this patch actually change? Brace yourself, as it

    is quite extensive. First and foremost each port now has two template
    parameters, one for the exposed interface of the port itself (i.e. the
    sending and receiving functionality) and one for the interface
    required from the peer port (the receiving end of the port's own
    sending interface). For example, for a conventional master port, this
    would now be IfaceMasterPort<MasterInterface, SlaveInterface>, where
    the parameters can be left out as they are the default values. This
    corresponds to a port on which we can use the MasterInterface, and
    bind to a peer port with a SlaveInterface.

   
   

For convenience, the name MasterPort and SlavePort are type defined to

    be a master and slave with the classic MasterInterface and
    SlaveInterface, as exemplified above. This is sufficient in most
    typical cases, where no additional functionality is required from the
    sending interface. In the case of e.g. a DMA port, additional sending
    functions like dmaAction require the port to expose a DMA-specific
    interface, which extends the MasterInterface. This is now implemented
    in a DmaHandler (the name could be discussed with options including
    Handler, Interface, Iface, IF), which inherits from the
    MasterInterface, and besides implementing e.g. recvTimingResp also
    adds the desired DMA functions. By declaring a
    IfaceMasterPort<DmaHandler> we get a master port with the desired DMA
    interface (which still binds to a SlaveInterface).

   
   

The implementation of the interfaces is, as already mentioned, done in

    a handler. Where there previously used to be e.g. a WalkerPort, there
    is now a WalkerHandler and a MasterPort that uses the handler. We
    could consider naming the Handlers Interface or Iface instead as
    already mentioned. The name handler was chosen as they actually
    implement the interface functions.

   
   

To enable any arbitrary specialisation of the interfaces, the

    protcol-agnostic base ports have bind functions that call a virtual
    bind function on the interface base class. It is then up to the
    specific implementation to overload this function and verify the
    compatibility. Thus, the ports pass the binding responsibility to the
    interface, and the master interface tries to cast the slave interface
    to the right class.

   
   

The separation of the port and the interface is done in a similar

    manner to TLM-2.0, with the interface of the port being accessible by
    overloading the pointer operator. Although this might seem like C++
    abuse, it makes for a far-less verbose way of accessing the interface
    compared to e.g. a member function. As a result of this change, where
    it used to say port.send, it will now say port->send, and similarly a
    port->send becomes (*port)->send.

   
   

As can be seen e.g. in the CPU, the separation of the port and the

    interface also enable the BaseCPU to declare the instPort and
    dataPort, and then leave it up to the specific CPU model to provide
    the interface handlers. Thus, the atomic, timing, inorder and O3 CPU
    can all implement their own independent handlers, and merely pass them
    on to the BaseCPU. A similar approach is used in the cache where a
    handler is created in the subclass and passed to the base cache
    parent that holds the ports themselves.

   
   

An additional benefit of this separation is that the module itself can

    implement the interface. This is done in the SimpleMemory, where the
    port handler was a trivial class that only added a level of
    indirection (port.recvAtomic calls memory.recvAtomic). Instead of
    having this handler, the port is now assigned the SimpleMemory itself
    as the handler, and the SimpleMemory is both a MemObject and a
    SlaveInterface.

Diff:

Revision 3 (+1702 -1243)

Show changes

Posted (July 29, 2012, 6:42 p.m.)
Any additional comments before I push this?
Ship it!
Posted (Sept. 7, 2012, 7:16 a.m.)
Unleash the chaos!

I think this deserves a message to gem5-users and gem5-dev when it gets pushed (along with all the downstream changes), just to give people a heads up before they unsuspectingly do a pull and get overwhelmed.
  1. Will do