=========================================== = FAUHdlc - intermediate code description = =========================================== Code containers --------------- Code containers are the basic containers in which all intermediate code resides. A code container can be seen as a function container, defining one function. Inside a code container, an arbitrary number of code containers can be nested. A code container can have four segments, all of which are optional: * type definitions segment: Types, e.g. arrays and records are stored here. These ar valid in the entire code container and subcontainers. * transfer segment: The transfer segment defines function call parameters, that can be passed into the code container (or out of it as well, e.g. for OUT parameters). * stack segment: The stack segment defines local variables of the code container. * text segment: The text segment contains the function definition, i.e. a number of opcodes defining the behaviour of the container. The text segment must either end with a return opcode, or must be implemented as an infinite loop. Visibility ---------- In a code container, the definitions of each surrounding code container are visible. This means that types and data from surrounding code container can be accessed in the text segment, and call statements may call surrounding code containers. Furthermore, the direct child containers are visible for calls as well, hence allowing to call one layer downwards. Data/transfer segments of nested containers are however not visible to the surrounding container. As an exception, the transfer segment may be made visible via BEGINTRANS, ENDTRANS and SETPARAM opcodes, in order to pass parameters to function calls. Labels found in text segment are only visible within this text segment, as jump/branch statements must not cross the boundary of one text segment. Basic simulation structure -------------------------- The basic simulation structure should define one top container, which doesn't have a transfer segment (i.e. no parameters) and the text segment of this top container should initialize the simulation by creating signals, connecting drivers and creating processes. Usually, sub-containers holding initialization functionality for sub-components will be called in this phase in order to create a hierarchy. The call to the top container's text segment must always return, and may not be an infinite loop. This allows that process handling needn't be present during this initialization phase. As an additional restriction, no process affecting statements (SUSPEND, WAKEON, WAKEAT) may be present in this phase. The entity that should get simulated, must be a direct child of the top container, and must not need any parameters from outside, i.e. it must not have a transfer segment. Opcodes ------- The definition of opcodes can be found in intermediate/opcodes/*, where the behaviour of the opcodes is described as well. An example implementation is interpreter/*. In case of doubt, the example implementation denotes the desired effect. Operands -------- All operands denote values of at least 64 bit, or at least the size of a pointer on the desired platform, whichever is more. Specifications of all operands can be found in intermediate/operands. Operands in the form of References to code containers (SETPARAM, CALL, etc.), labels and type elements must be resolved according to the visibility rules above. It is an error, if any ambiguity arises from resolution, or if the resolution fails. The types universal_int, and universal_real must be predefined to denote basic signed integer/basic floating point types. Other names may be predefined as well, as long as this will not result in ambiguity. (TODO: simulation_abort?) Annotations ----------- Annotations are basically comments, and can be placed at any place where whitespace is allowed in the intermediate code. An annotation starts with a "{" character, and ends at the next closing curly bracket "}" (unless it is specified in a specification string, see below). Annotations in the form of name="string" or name=123 (cf. scanner of example implementation for lexical definition) denotes a annotation specification, that can have an implementation dependant effect. An implementation may restrict annotation specifications to be present only at limited places inside the intermediate code. =================== = Foreign signals = =================== The treatment of foreign signals is implementation spefic. However, since I constantly forget the reason why fauhdli does it that way, here are some notes how it is done: For a foreign signal that is being read by VHDL, a driver is created which will get connected via glue_vhdl. For a foreign signal that is written to from VHDL, a foreign driver is created. The value of this driver is only passed later on via glue-vhdl, after all non-foreign signals have been handled. The reason is the following: Propagating the value via a glue-vhdl means to basically execute a foreign process. This must happen at a discrete step in the simulation cycle to guarantuee correctness: First, all drivers update signal values. Next, all processes run, where the previously changed signals can result in driver updates. In the same discrete point in time, a signal update may never result in a signal update, and a driver update may never result in a driver update. Resolution functions: It's not possible to use VHDL resolution functions for foreign signals, as the aforementioned semantics imply that actually resolution is performed by the foreign signal (Consider a foreign signal that is read from and written to in VHDL, it has one or more foreign drivers, which forwards VHDL changes to glue_vhdl and one hidden driver, which actually represents the signal value). If resolution was possible by taking all drivers, resolve them to the signal value and forward the result, then a lock to a dominant value could occur: As an example, both the foreign signal and the VHDL counterpart have all driving values set to 'Z', so all signal values are also 'Z'. If now VHDL sets one driver to '0', this one will be dominant. It's passed on to the foreign signal which will also set it's value to '0', like the VHDL value. As a result the hidden foreign input driver for the VHDL signal will have the driving value '0'. Setting the driver back to 'Z' which caused the transition to '0' will still locally resolve to '0', due to the other "segment" of the bus having been forced to '0'. And that's why it all comes down to using only the foreign side for resolutions.