1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
===========================================
= 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.
|