dietSTThis project was created to fit a whole Smalltalk computer into an FPGA with only 15 thousand gates. The original design for Oliver was like a rather extended version of the MISC Forth chips and it seems that 16 bit versions of these barely fit in this FPGA and certainly donīt leave enough room for the peripherals.
So space is traded off for time whenever possible. Though the architecture is 16 bits wide, all of the data paths are only 4 bits even though that means simple operations take at least four clocks to execute. In addition, there are as few registers as possible with all data being stored in memory or three special caches (described below).
This is divided into sixteen blocks of 16 words (of 16 bits) each. Each block holds the contents of a Context object. Block zero is reserved for interrupts and traps while the others cache a part of the current stack.
For a context with A arguments and T temporaries, the contents of the corresponding block are:
Methods which need more than 11 words of arguments, temporaries and evaluation stack can't run on this architecture, but the compiler can rewrite such code as nested blocks to get around this restriction.
Object Table Cache
This is divided into 64 blocks of four words each. Each block holds the first four words of some object. These entries are a subset of the system Object Table, which takes up 128KB of main memory at a fixed address. Each entry has the following format:
In the actual Object Table, the first field is redundant since the object ID is the same as the entry's index in the table (but with bit 15 set). But it is needed in the cache to check that we have the right entry.
The code object is like a class in Smalltalk or map in Self in that it is used to indicate an object's "type". It is a vector that includes the compiled code for all of the object's methods (both local and inherited from parent objects). At the begining of each code object there is an entry for each possible selector in the system. This takes up a lot of space since about 95% of these entries just call the "message not understood" method. Each entry is two words long, so very short methods can be entirely contained in there. Longer methods can have a jump instruction to the rest of the code after the selector table. The "method" field in the stack cache is actually a pointer to a code object.
The fields of the objects are not cached, but always read/written directly from memory. That is unfortunate, but there is simply not enough RAM on the chip to deal with this.
This is divided into 128 blocks of four words each. Instructions are addressed on a nibble (4 bit) boundary. Each entry looks like:
The first two words are used to compare with words 0 and 1 of the current context to see if we have the right entry. Instructions have the following format:
where "i" is the instruction opcode (0 = push literal, 1 = send message), "x" is the extra bit that is added to the operand (explained below) and "ss" is the size of the operand in nibbles (but 0 means 4 nibbles, or 16 bits).
For the push literal instruction, the extra bit is used to set bit 15 in the case of operands with 12 bits or less. This allows both small integers (bit 15 = 0) and normal objects (bit 15 = 1) to be stored in the stack.
When a send instruction has an operand of less than 16 bits, then the lower 4 bits are "x 0 0 0" (where "x" is the extra bit from the instruction nibble) and the operand (extended with zeros) specifies the top 12 bits. When the operand is exactly one nibble long then one of these primitive operations is executed instead of an actual message send:
Note that a normal message is sent if the operand is 8 bits or more, even if the bit pattern is one from the above table, so the first 32 selectors can be used for regular messages without confusion.
Leaf MethodsSimple methods that don't invoke any other methods (not counting the primitives in the above table) are called "leaf methods" and account for a large part of all message sends. To optimize the small ones, the "enter" instruction can be omitted so that a new stack frame is not created when executing this method. The temporary PC points to code in the new method, but the PC field of the stack frame remains as it was, so after two words of instructions the execution automatically returns to the sender.
The most common use for leaf methods is as accessors for slots in the objects. If we had something like (in Self notation)
( | x <- 3.4. z = 7 | )
we would have the following inside the code object:
Links to this Page