PortsAndApis
Ports and APIs
Overview
Implementation(2f)PortsAndApis/attachments/APIoverview.png)
Note: The left derivation tree is the deprecated dynamic casts version.
For this example implementation the following choices were made (see below for the options):
- One API for a PCIe device (not divided into Master and Slave part) with one port (not Master and Slave port).
- Provide functions for manipulating TLPs in the access classes (
PCIe[Master|Router|Slave]Access). Creation of tranactions in PCIeAPI. - Receiving and processing of TLPs in the slave: Pass the received transaction directly to the user (3). Convenience methods in the access class may be used for processing. Callbacks may be realized later in the PCIeAPI.
Options
Question: Two APIs (One Master- and one Slave-API) or one combined API?
The combined API owns a PCIeMasterPort and a PCIeSlavePort and hence is able to receive and send PCIe packets. In the PCIe specification the devices are not divided into masters and slaves.
Disadvantage: PCIe devices are not categorized as master or slave. |
or | ImageLink(PortsAndAPI.jpg Advantage: Better representation of PCIe philosophy, which has no masters and slaves. We chose this option. A modification of the basic initiator_ports leads to only one bidirectional port which is needed by the API: With this one port it is again possible to inherit from the port so binding of the API may be done as it is a port. |
The PCIeMasterPort provides blocking communication used by the PCIeMasterAPI.
The PCIeSlavePort provides blocking communication used by the PCIeSlaveAPI.
PCIeMasterAPI (or master part of PCIeAPI)
Derived from PCIeMasterPort.
- Uses blocking communication
Possibilities:
(!) Provide API methods in PCIe(Master)API (classical doing)
- Provides high-level-calls to the user, e.g.
send_Memory_Write(addr, data,...) - Creates transactions with (inline) initialization methods which create the matching transaction.
- Send transaction with blocking
Transactmethod. (May be replaced with non-blocking notify later.) - : Example:
- class PCIeMasterAPI {
- send_Mem_Write(many, many, many, ...) {
- container = init_Mem_Transaction(many, many, many, ...);
- b_transact(container);
- }
- inline container init_Mem_Write_Transaction(many, many, many, ...) {
- ... // create Transaction Container
- }
- };
(!) Provide API methods in PCIeMasterAccess (not in PCIe(Master)API)
- Get (generate) transaction object out of (in) PCIe(Master)API
- Prepare transaction according to the needed TLP type by calling API methods in PCIeMasterAccess
- Send transaction with PCIe(Master)API
- Advantage:
- Decouples TLP data processing API (PCIeMasterAccess) from notification API (PCIe(Master)API).
- Extensibility: Extension of the Transaction Container does not result in a change if the PCIe(Master)API but only a change of PCIeMasterAccess (which has to be adapted anyway). So a slave module need not care about extended transactions (if it does not need the extension) because the API keeps the same. A slave who is interested in the extension simply casts the transacted transaction (received with
b_transact) to the extended type.
Example:
- class MyModule {
- ...
- void action() {
- // Get transaction object
- PCIeMasterAccessHandle ah = myPCIeAPI.create_transaction();
- // Prepare transaction
- std::vector<gs_uint8> data; ...
- ah->init_Memory_Write( 0x1B39A34, *dat, data_size );
- // Send transaction
- myPCIeAPI.send(ah)
- // process reaction
- if (ah->get_Completion_Status() == SuccessfulCompletion)
- ...
- }
- };
PCIeSlaveAPI (or slave part of PCIeAPI)
Derived from PCIeSlavePort.
- The method
b_transactis target of the blocking master call
Possibilities:
(!) '''1)''' Give the slave the opportunity to register callback function(s):<br>
API methods located in PCIe(Slave)API (classical doing)<br>
See section below for different solutions.<br>
''Disadvantage'': One additional method call inside the callback adapter. - Register different functions (with different signatures) for different TLP types. These callback functions will be called on reception of such a transaction. TLP types without a registered callback either will be processed in the b_transact method or will result in a warning.
- Register different functions (with the same signature) for different TLP types.
(!) '''2)''' The slave has to implement an interface so that the API is able to call fixed defined callback functions (e.g. for each TLP type).<br>
API methods located in PCIe(Slave)API (classical doing)<br>
''Advantage'': Faster than variable callback registration (one less method call).<br>
''Disadvantage'': Less flexible user implementation.
(!) '''3)''' Pass the call directly to the user slave (b_transact)<br>
API methods located in PCIeSlaveAccess (not in PCIe(Slave)API)<br>
The user module has to implement tlm_b_if and bind itself to the API (as the API binds itself to the port).<br>
Access methods to different TLP types are in the Transaction Container (or Access methods).<br>
''Advantage'': Matches the concept which provides processing methods in the transaction's PCIeSlaveAccess methods.<br>
''Disadvantage'': One additional method call to call the user's b_transact.
(!) 1) (!) Different Callback Function Alternatives
For registering callback functions in the API we have different alternatives:
- Use consistent method signatures for registration.
- :2. Use different method signatures for registering different TLP types.
Consistent method signatures for registration
- Use a unified adapter class which can be used within a registration macro (see GreenConfig).
- template<class T>
- class CallbAdapt
- : public CallbAdapt_b
- {
- typedef void (T::*callb_func_ptr)(const PCIeSlaveAccessHandle);
- public:
- CallbAdapt(T *ob, callb_func_ptr _ptr);
- /// Makes the callback, called by the base class CallbAdapt_b.
- void call(const PCIeSlaveAccessHandle);
- };
- Select the TLP type with an enumeration, e.g.:
- enum TLPtype {
- MemoryWrite = 0,
- MemoryRead,
- IOWrite,
- IORead,
- ...
- };
- Use a unified macro with a corresponding unified method for registration and specify the call with the TLPtype:
- // Macro for registering callback functions (see method registerCallback).
- #define REGISTER_CALLBACK(class, method, TLPtype) \
- register_Callback(TLPtype, new tlm::gc::config::CallbAdapt<class>(this, &class::method));
- Hold the function pointers in a vector with index TLPtype.
Advantage:
- Simpler code: unified code for all different types of TLPs
- User has all opportunities in processing the transaction (he gets the original AccessHandle).
Disadvantage:
- User has - when processing the transaction - to choose the relevant quarks out of the not filtered transaction container (AccessHandle).
Different method signatures for registering different TLP types
- Use a separate adapter classes one for each TLP type.
- template<class T>
- class MemWriteCallbAdapt
- : public MemWriteCallbAdapt_b
- {
- typedef void (T::*callb_func_ptr)(const type1, const type2, ...);
- public:
- CallbAdapt(T *ob, callb_func_ptr _ptr);
- /// Makes the callback, called by the base class CallbAdapt_b.
- void call(const type1, const type2, ...);
- };
- Use separate macros and separate methods for processing the callback registration.
- // Macro for registering callback functions (see method registerCallback).
- #define REGISTER_MEM_WRITE_CALLBACK(class, method) \
- register_Memory_Write_Callback(new tlm::gc::config::MemWriteCallbAdapt<class>(this, &class::method));
- Use the TLP type enumeration (see above) for holding function pointers in a vector.
Advantage:
- Bigger, more confusing code: separate code (adapter, macro and register function) for all different types of TLPs
- User gets only the quarks, which are relevant for this TLP type.
Disadvantage:
- User is limited to the passed quarks when processing the transaction. Possible solution: in addition the original AccessHandle may be passed.
(!) 2) (!) Fixed interface, which user has to implement
- PCIeAPI gets pointer to user module during instantiation and can call directly the processing method(s)
- Either: One method which processes all kinds of TLP types
- class PCIe_callback_if {
- virtual void receive_TLP(PCIeAccessHandle) = 0;
- };
- Or: different methods for each TLP type, e.g.
- class PCIe_callback_if {
- virtual void receive_Memory_Write(address, length, data, sender) = 0;
- virtual void receive_Memory_Read(address, length, data, sender) = 0;
- virtual void receive_Message(messageCode, data, sender) = 0;
- };
- OR: The user module may implement several interfaces, each one for one callback method for one TLP type. Only implemented interface methods will be called by the API, others are ignored (warning). E.g.:
- class PCIe_Memory_Write_callback_if {
- virtual void receive_Memory_Write(address, length, data, sender) = 0;
- };
- class PCIe_Memory_Read_callback_if {
- virtual void receive_Memory_Read(address, length, data, sender) = 0;
- };
- class PCIe_Message_callback_if {
- virtual void receive_Message(messageCode, data, sender) = 0;
- };
(!) 3) (!) Directly pass into user module
The receiving PCIeAPI calls the b_transact method which is implemented by the user module.
The user has to implement the interface PCIe_recv_if and 'bind' it to the API by giving it to the constructor of the PCIeAPI instance:
- using namespace tlm;
- using namespace tlm::PCIe;
- class MyModule
- : public sc_module,
- public PCIe_recv_if
- {
- public:
- PCIeAPI myAPI;
- MyModule(sc_module_name name)
- : sc_module(name),
- myAPI("PCIeAPI", this)
- {
- }
- virtual void b_transact(PCIeTransactionHandle th) {
- PCIeSlaveAccessHandle ah = _getSlaveAccessHandle(th);
- switch((unsigned int)th->get_TLP_type()) {
- case MemWrite:
- ...
- }
- }
- }
The Transaction Container access methods provide convenience methods to access data according to the TLP type. E.g.:
- class PCIeMasterAccess
- : public virtual GenericMasterAccess
- {
- public :
- void init_Memory_Write( MAddr targetAddr, std::vector<gs_uint8> data, unsigned int data_size );
- ...
- };
- class PCIeSlaveAccess
- : public virtual GenericTargetAccess
- {
- public :
- const MPCIeCmd& get_TLP_type() const;
- const MAddr& get_addr() const;
- };
Callbacks may be added later in the API. Then the API would have to process the incoming transaction instead of just forwarding it to the user module's b_transact method.
Configuration Registers
see Configuration RegistersPosted January 8th, 2008 by MarkBurton