![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
OliverThe hardware platform for the Oliver truck terminal includes:
Here is the project log (in Portuguese only, sorry): Desenvolvimento do Oliver And the previous one: Teste do Oliver Another page has an example showing how the instruction set is used with blocks that share read/write data with their enclosing method. Objects and InstructionsThe processor has 16 bit wide data paths, but virtual addresses for memory are two groups of 16 bits: the object number (normally from the self register, see below) and the field number or offset. The memory controller includes a virtually addressed cache. On a cache miss, a 32 bit physical address is generated and four words are transferred to/from main memory.Small integer objects have a special representation and are not stored in the cache. The top 15 bits of the integer reference are the value itself, while the lowest bit is the "tag". Since for small integers that tag's value is zero, all 16 bits can be used in additions and subtractions and the result will automatically be the correct small integer. Some other operations, like shift, are more complicated.
The first four words of each object are found in an Object Table which takes up the first 128Kwords of memory. Just multiply the object ID by four (only bits 15:1) and add the word number. The first two words contain the high and low 16 bits of the physical address of the rest of the object (if it is more than four words long). Given an object ID, it is easy to find the corresponding "code object" by just masking some bits. If the bottom bit of the self register is zero, then the object ID is also zero (this is used for integer objects), but if it is one then the mask depends on bits 15 and 14 of the self register:
Instructions in code objects are simply a sequence of 16 bit words with a mix of pairs of bytecodes and object references. The garbage collector can't distinguish between the two, but since these same object references are also present embedded in the source code there is no problem. The main instruction format for a bytecode is a four bit operation field followed by a four bit operand field:
Here is a description of each instruction (with operation codes in hex):
When expanding the operand from 4 to 16 bits in the constant instruction, bit 3 of the operand goes into bits 15 through 5 of the top of stack when bit 0 is zero, and bits 3 to 1 of the operand replace bits 5 to 3 of 0000000000abc0d1 (where d = b or c) and the result is pushed on the stack when bit 0 is one. This allows us to push the following 16 objects: 0, code for smallIntegers, 1, nil, 2, false, 3, true, -4, code for code objects, -3, localMem, -2, specialA, -1, specialB. The notation pc#x used in the above table means the the least significant bits of pc are replaced by the value of x. The number of bits actually replaced depends of how wide x is, which is normally 4 bits but can be extended with one or more prefix instructions. X is shifted relative to pc in order to address words. The notation pc$x is the same thing but without the shift, so bytes are addressed instead. The explanations for indirect and indirect: say that word 0 in the array is affected. That is true if x is only 4 bits wide, but if it has been extended with one or more prefix instructions then the lowest four bits select the array in the stack but the higher bits select a word other than 0 in the array. Those familiar with stack machines might think that the instruction set described so far is not sufficient for fully manipulating the stack. On one hand full manipulation is not necessary for code generated from Smalltalk sources and on the other special cases of the above instructions have exactly the same effect as the "missing" instructions:
While these basic instructions do the needed control flow and data manipulation, all actual processing is done by sending a special message to a hardware object. There are two special messages, called "a" and "b", which combined with sixteen possible hardware objects make for a total of 32 processing instructions. Note that with one prefix instruction we can have an additional 480 processing instructions (16 bits wide), but we only use 8 of them here: the first four of each special message type are "raw" equivalents of their corresponding one byte instruction. So if 20 is add then 0120 is rawAdd which operates on all 16 bits instead of just valid 15 bit integers. All of these instructions take any operands they need from the stack and push their result back to the stack. When a hardware object is absent or if it can't execute the requested instruction for some reason (incompatible operands, overflow, etc.) then the instruction is interpreted as a normal send to either the specialA or specialB software object with the object number as the message selector. These are the one byte stack instructions (where T means the element on the top of the stack and N is the element right under T):
A feature of the translator is that it will hardcode popular control structures, such as "ifTrue:" and "whileFalse:", using the jump instructions. This is used in all Smalltalk implementations except for Self and is limiting compared to what is achieved by Self's famous adaptive compilation technology, but it will have to be enough for this simple implementation. Local Memory and I/O PortsThe internal registers and memory of the processor are mapped into a Local Memory address space which is accessed as a special object.
In addition, each task is associated with a particular i/o port which is used implicitly by the test, set, clear and toggle instructions. A task still has full access to the i/o ports for other tasks, but that requires a longer sequence of instructions. BlocksThe code for a block object will be included in the same code object as the method in which it appears (as is traditional for Smalltalk implementations). Some blocks end with a non local return and are created with the retblk instruction while the rest are created with the block instruction instead. Any read-only state needed by the block is copied to it so that it doesn't have to reference its lexically scoped external context. For the case of read/write state shared between one or more contexts, that is placed in an indirection array. The array itself can be seen as read-only state so the reference to it can be copied as before.Input and OutputBesides the processor, the FPGA implements a set of I/O devices:
Links to this Page
|