Joachim Schlosser: Development and Verification of fast C/C++ Simulation Models for the Star12 Microcontroller
We presume that the elaboration session and the reset process have already been finished, and simulation is about to begin. Even before the application triggers, the evaluation of the first cycles there may be pending events in Octopus’ event queue, scheduled in the initialization stage of a model. A model can schedule events only for itself, it avails the motNextEval function with the cycle offset and a TaskTypeT value as parameters. In earlier versions of Octopus there were – additionally to the normal event-driven models – so-called active models, which were evaluated automatically every clock cycle. The concept was used for CPU models, changing their state in each cycle. The active models were discontinued because the administrative and performance expense is basically the same as for self-scheduling models. Especially for CPU models there are many instructions that take more than one clock cycle, e. g. due to memory speed. So it would be a waste of performance to evaluate the model in the meantime. Without active models there is only one type of model execution logic, making the structure and maintenance of Octopus easier.
So if the event queue is empty, Octopus advances the simulation time and returns control to the application. In the other case, the time is set to the point of the first event in the queue and the according models are evaluated, invoking the Eval callback of each model with a pending event. The model now determines the action, it has to perform, by interpreting the bits in the 31-bit TaskTypeT value. This is actually a 4-byte long integer with the MSB being used by Octopus internally. Depending on the action to be performed, the model could now initiate an update of a memory mapped module register by using the base class function motSetReg16 for 2-byte registers. The base class modifies that value in an internal data structure. The next bus read access to the address of the register will receive the new register content.
The callback could update an output port using the motPutPort function of Octopus, or – better – the base class encapsulation SetPin. The latter has the advantage, that only the port index instead of the port handle and time offset has to be passed beside the type-token combination. In the configuration file, a signal is attached to each of the ports by name, Octopus now searches the netlist for this signal name to ascertain to which ports the signal is assigned to on the input side. Now it is known to which port the update has to be delivered to. The offset, specified for the output port, reflects the number of cycles, the port delays its output. After this time Octopus informs the other model, using the Update callback. As usual, the update information is parsed by the base class, which then knows exactly what port has been updated. It raises the handler callback function that has been registered in the base class for receiving updates on that specific port. If the application, respectively the user, had decided to monitor the signal, the update is passed to the signal dumper, too. The signal dumper is the piece of code, that produces signal dump files, e. g. in VCD format, for all signals selected for monitoring. The update handler function for the port addressed receives the type-token pair of information and can easily react on the signal change, possibly with scheduling an event using motNextEval.
Unlike normal signals, which are usually connected to two ports only, a bus signal passes several models, but addresses only a particular one. A “bus communication consists of two successive communication events” ([KLR99, section 3.4.5]):
Generally, there are two versions of Octopus used for simulation. In the model development process, the debug version allows sophisticated debugging, while for model delivery, when the performance is most important, the fast version without any debug code is linked.
The difference between debug and fast version lies not only in the different library files of Octopus, but also in the header files. All debug commands are C macros, like the command for printing debug information:
Of course, when using macros it is not possible to pass a variable number of parameters like one would do with a printf statement. So there are macros for zero to nine parameters. The level parameter specifies the debug level. There are nine levels available for each model, so for execution it is feasible to run different models in different debug levels to gain the level of detail of information needed. If the developer wants to use a whole code block as debug code, he can embed it in model debug brackets:
When compiling and linking for debug, the brackets are substituted with the debug level check code, when compiling for delivery it is replaced with an #ifdef 0 ... endif
Model manager commands are supported in Octopus by allowing models to register their own commands. With this technique, models can e. g. implement debug commands to show internal states, or also manipulate them. All commands have the following syntax:
In later subsections of chapter 3.1 on page 169, examples for debug commands will be presented.