A DMI example

Let’s go with the Direct Memory Inteface (DMI) defined in TLM-2.0. DMI mechanism in roughly speaking is to pass over transactions, protocols and so on. In short, the Target sends a pointer to its memory region (o part of it) to the Initiator to allow him to that memory region directly, without using transaction. This way, we speed up the simulation, because everything is more easy.  DMI is focsed on memory type devices connected to modules that are accessing very frequently to that memory devices, like CPUs or DMAs. But the mechanism is there, and we can use it in any of our modules.

And what we should do?

Target

In the target side we need to do two things: to register the function that publishes the pointer and fill the structure to publish the pointer.
To register the function:

target_socket.register_get_direct_mem_ptr(this, &Target::my_get_direct_mem_ptr);

and then write the function:

bool Target::my_get_direct_mem_ptr(tlm::tlm_generic_payload&, tlm::tlm_dmi& dmi_data)
{
dmi_data.allow_read_write();
dmi_data.set_start_address( 0 );
dmi_data.set_end_address( 10 );
dmi_data.set_read_latency( sc_time(10, SC_NS) );
dmi_data.set_write_latency( sc_time(10, SC_NS) );
 
 dmi_data.set_dmi_ptr( reinterpret_cast ( &data[0]) );
return true;
}

Note that in this function we only fill the dmi_data structure, in this case telling that the memory region to use can be used to write and to read, what is its start and end address, and what read and write latency has. Those times can be used by the Initiator to simulate the access time if it like to. Lastly, we put the pointer to our memory region (in the example our data array), doing a C++ cast to unsiged char, that is the standard data type for all TLM-2.0

Later, to tell to Initiator that our module is DMI capable, we may mark a field in the transaction:

void Target::b_transport( tlm::tlm_generic_payload& transaction, sc_time& delay )
...
transaction.set_dmi_allowed( true );
...

This way, when a Initiator talks to our Target using a normal transaction (blocking or no-blocking) it can check that this Target is DMI capable.

Initiator

First step to use DMI in our Initiator is to write the function that the Target can use to tell to Initiator that the DMI pointer is not longer valid (for any reason)

So we add a new method to our Initiator class:

void Initiator::invalidate_direct_mem_ptr(sc_dt::uint64 start, sc_dt::uint64 end)
{
dmi_ptr_valid = false;
}

And we register it in the socket:

Initiator::Initiator(sc_module_name name_):
...
initiator_socket.register_invalidate_direct_mem_ptr(this, &Initiator::invalidate_direct_mem_ptr);
dmi_ptr_valid = false;
...
}

Later, the Initiator should first find if the Target device allows DMI (remember, it's always optional). For that, Initiator may check a field of a transaction already responded by the Target. If Target allows DMI, would set to true that field.

...
if (transaction.is_dmi_allowed() ) {
dmi_ptr_valid = initiator_socket->get_direct_mem_ptr( transaction, dmi_data);
}
...

Note that in the previous two lines I made two different things: ask to the previous transactoin if Target allows DMI using is_dmi_allowed() method from transaction. Then, if Target allows it, we ask for the pointer, passing to Target the transaction with the address we want to access, and getting a true or false depending if DMI pointer is valid or not.

Following the example, if the Target allows DMI:

...
// El target acepta DMI, usamos el puntero directamente
unsigned char* dmi_ptr = dmi_data.get_dmi_ptr();
memcpy(dmi_ptr + (i*sizeof(data)), &data, sizeof(data) );
std::cout << "Initiator: " << sc_time_stamp() << "Escribiendo " << data << ". DMI OK" << std::endl;
...

It's time to get the pointer to the Target data using the method dmi_data.get_dmi_ptr() and later we can use that pointer usually.

Although I've left as is, it is not necessary to get the DMI pointer each time we are going to use it. We may ask for it once, and only check that it is still valid (in the example using the global variable dmi_ptr_valid) before we use it.

And that's all for that example. It lacks the option to invalidate the pointer time to time in the Target. But it's your homework ;). (If target wants to invalidate the DMI pointer, it only may call invalidate_direct_mem_ptr method of its socket).

Full example is in this link.

Leave a Reply