CopyAtSlave
Copy At Slave
GreenBus uses "Copy At Slave" this page explains more
Memory management and thread safety
The MASTER is responsible for allocating a "transaction" structure. This structure must be kept "live" till the end of the transaction. Nobody else need allocate anything. This can be simply achieved in the Master port - more below.
Conceptually, there is a "send" call (in our implementation, we re-used the TLM 1 calls, but I dont think thats right, because we are not using their "intent") This is NON BLOCKING void send(<Transaction type> &t) This is used to "propagate" the reference to the transaction structure to all those interested (potentially routers, slaves etc).
Subsequently, models on the transactions path (master, slave, routers, whatever) can update the transaction, but when they do so, they must indicate that they have done so using "events". The other models need to listen for those events before reading the data. This guarantees deterministic behaviour across threads.
We have been "round the houses" on how those events should be implemented. Initially, we use SystemC events, which we happened to know related to the thing we were interested in... Another plan is to use put/get to "transmit" the atom at the right time. However, events have the added advantage that (if you wish) you can simply delay them. This makes the model writers task very nice and easy.
But events don't pass values, so our conclusion right now is that we could put sc_events into the transaction (they then represent the timing annotations - i.e. the start and end point of the atoms). in this case, an atom would be a container referencing the parts of the transaction that have been updated. Of course this container would be pre-set up at compile, as it would only be protocol dependent.
Right now, we have a hybrid scheme where we use essentially "put" on the master side, and events on the slave side. In fact, our version of "send" (put like) takes a transaction reference AND an "atom". In this way we "optimise away" the need to call the initial "send".
[BTW, this is the same argument we are having in the "TLM1 compliant world" about how best to deal with timing annotations. whether to make this part of the transaction or not. Our view is that the timing values should not be part of the data structure, but timing should be passed via events. My preference would be either:
- a put that took a timing element as a delay
- an event that had a payload
For me, putting the sc_events into the transaction is a "hack" to make it so that the events carry a payload (and the object on which the event is generated then needs to point back at the transaction, which is pretty gross) a or b would clean things up so that in principle the PV and PVT data structures could be the same, and CLEAN]
PV/PVT
The beauty of this scheme is that it is totally transaction orientated, while allowing as much timing "granularity" as you like. Only the amount of "timing information" that needs to be "transmitted" need be transmitted. So, in principle, this should work for PV and (all levels of) PVT.
However, there is a problem. As it stands, to implement a "PV" master model would look something like
- mport->send(t);
- wait(t.finished);
- ....
That wait is going to kill models (probably). So - it can be removed. Simply by re-introducing something like transport(). Calling transport means that when it returns the transaction IS complete (and it is blocking in this case).
GreenBus proposes that ALL IP models implement transport, as well as the "send" mechanism. This means that the IP model itself is responsible for deciding how to respond at a PV level. If it can not, or will not, it can raise an error. However, simply assuming the bus (for instance) can do this is wrong, as the bus does not know what complications exist in the model.
So, a simple PVT slave may implement transport (PV interface) as
- void transport(<transaction> &t)
- {
- // Oh, bum, they want a pv interface
- mysend(t);
- // I guess the request data is in there.....
- wait(my-finished-event);
- return cheers;
- }
- void transport(<transaction> &t) { crash_and_burn("My model can't do that");}
Equally, a PV slave may implement send (PVT interface) as
- void send(<transactoin> &t);
- {
- // Geeze what sort of luddite wants timing info from me!
- my_transport(t);
- t.finished.notify();
- }
So, this is how, and where we would implement "PV" to be as efficient as possible - as a separate function call.
Speed and efficiency
In the majority of systems, the master knows how many outstanding requests it is permitted - and therefore, transaction structures can be pre-allocated. Further more, some of the masters request data can be pre-setup (for instance, by pre-allocating n*read transactions and n*write transactions).
In the path, the master WILL have to copy data into the transaction (for the remaining request control signals, and potentially write data). Likewise, the slave will have to copy data from a write transaction structure to it's internal memory (if that is appropriate), and for a read, the reverse is true.
Hence, in the majority of cases, this form of transaction passing will STILL require 2 data copies.
This can be reduced to one allowing the data itself to be a pointer back to the masters data area. This is fine so long as the master has a suitable data area, otherwise, the master will still need to perform the copy. The requirement of bridges below makes me think this is an unhelpful optimisation.
In the case that there is a "bridge" that can manipulate the transaction in some way (for instance, it may be a bridge between the generic protocol, and a specific protocol), then there will need to be a full transaction structure "copy" (or re-write). (A common bridge would split up the data payload into a number of smaller transaction.)
It's for this reason that GreenBus includes the notion of "quarks" to make sure that the elements of a transaction remain the same, such that the copy can be "just" a copy, and not involve complex data manipulation (because the byte enable encoding is different for instance) - Wherever possible (again, clearly, there will be some reason for the bridge, and therefore there will have to be something in there that manipulates the data from one form to another).
It is also for this reason that we believe strongly in the notion of a "generic" protocol, that will "cover" as many busses as possible, as it will minimise the need to do this copy in the first place - a bridge would then "do nothing".
[Notice that in the GreenBus proposal, we do not use "bridges" to convert between the generic protocol and a specific protocol that a IP might use - that is handled by the User convenience layer - more below]
The only possible optimisation that I can see to reduce the cost of bridge copies would be to use CoW vectors. This MAY be appropriate for protocols that carry heavy data payloads, which are then (typically?) split and sent via bridges on protocols that carry smaller payloads (But a bridge like that can be implemented without copies, simply using the atom mechanism, so it's not clear how useful this would be)
Convenience and safety
The KEY to GreenBus is the user API "convenience" layer. The GreenBus "native" layer is not an inconvenient API, but in and of itself it is not necessarily "safe" for users. Further, it may not be easy to re-write legacy code.
So, GreenBus already has "convenience API's" that match the Aztalan API, and TAC. OCP-IP TL1 TL2 and TL3 are in progress.
The important point is about safety. If the entire transaction is being passed as a reference, then potentially a rogue slave could write to the masters request data. That is not (in general) desirable. Hence the transaction class structure holds it's data privately. The right interfaces are provided through initiator and target ports such that (unless you instantiate a target port in your master or whatever) you can't make a mistake. In principle this means that for specific protocols, the "visibility" of the data can, if the protocol designer wishes, be more fine grained.
BUT, this puts a lot of "emphasis" on the protocol designer "doing the right job" - to this end, GreenSocs is undertaking a project right now to attempt to show that it is possible to "automate" that process, given a (Spirit XML) description of a bus, the idea is to generate the correct initiator/target ports and bus protocol classes.
What should be standardised
Only things that provide for interoperability.... so
- transport and "send" (since transport changes it's signature, and send is a rubbish name, these names should be changed) (Both take a <transaction>)
- the put(time) or event(data) thing (or additional structures in the data structure) (Both take, or use somehow, an <atom>)
- the data structures (for quarks, atoms and transactions) (transactions built of quarks, atoms built of references to quarks - if they are needed)
So, the question is, should the initiator/target ports and convenience API's be standard or not. My view is not.
GreenBus implements SOME - and maybe they are good, and maybe not. In my mind they are "proof of concept" Others might implement much better ones.The important thing is that they CAN be built, safely, and efficiently. We should (and will) provide nice clean examples, but I dont think they need to be standardised because they do not, of themselves, provide interoperability. If you wish to, you can malloc a new transaction each time, etc etc etc... we should not prevent you from doing so... maybe your model requires it.
PageComment2(commentfirst=1,nosmiley=1,articleview=1)
Posted January 8th, 2008 by WolfgangKlingauf