Page Numbers: Yes X: 527 Y: 10.5"
ToSmalltalk Microcode UsersFromSidney Marshall
W128 - X29405
SubjectMicrocode in SmalltalkDateNovember 19, 1980
This memo describes the addition of microcoded primitives to smalltalk. I will first describe how to make a smalltalk system with microcode capability and then describe in detail how it works.
This system does not require a 3K CRAM unless this capability is enabled. Also, I have modified the BYTERP.MU code in such a way that future microcode work will not require recompilation of the interpreter and surgeries etc. In this way, development of future interpreters will hopefully be independent of the development of future microcode primitives.
Glen Krasner gave me a copy of the smalltalk sources disk some time ago. I modified novacode.sr to add two primitives and made a new [XM] smalltalk.run. I then modified the microcode file BYTERP.MU to recognize special primitive numbers and jump into another RAM bank. A surgery was then performed on the standard smalltalk release XMSmall.boot with the new smalltalk.run and BYTERP.MB to create a smalltalk with extra microcode "hooks". This version of xmsmall.boot is indistinguishable from the release version except for the addition of two additional primitives for reading and writing the RAM. (In the version I have the two primitives 105 and 106 reference the symbols AUBLT and AUCOLOR and these are undefined.)
To make a microsmall smalltalk system from original sources do the following:
Get a copy of the smalltalk sources disk.
Using this disk run ftp and load the file "[erie]<Smalltalk>microsmall.dm". This file contains all the sources for everything new and a command file "makemicrosmall.cm".
Get the file NOVACODE.SR from this dump file and assemble it. Then load a new version of Smalltalk.run (using xmload.cm if your disk is like mine).
Now transfer Smalltalk.run and Smalltalk.syms to a disk with the new Small.boot and the files "byterp.mu", "microfloatmc.mu", "microchanges.st", and "makemicrosmall.cm" from the dump file. Execute the command file "makemicrosmall.cm" to assemble byterp.mu and microfloatmc.mu, perform the surgery and revive Smalltalk.run. Ted Strollo’s memo on the smalltalk release process is very useful. Look at the command file beforehand.
After reviving smalltalk.run you have a standard system that should run on all altos (probably should check this on an alto I).
As a check, filin microchanges.st to make a 3K CRAM version that won’t work on other machines. You now have a system with microcoded floating-point. The operations +, -, *, /, < etc. now run in microcode for floating-point operands and new and new: are also microcoded. Of course, the release system should not contain these changes but I include them as a check that everything is working.
While these modifications will work equally well for Small.boot and XMSmall.boot I feel that they should not be included in the non-XM version as space is tight enough and few people running the small version have 3K CRAM cards anyway.
How it works - novacode
Two new primitives have been added to read and write the ram. They are #2 for reading and #3 for writing. Basically, they copy a ram image from the ram into a String and vice versa. The primitives actually allow loading substrings into part of the RAM making single word loads and stores easy to do. The methods assume 4 arguments:
ReadLocan address in a RAM bank + 10000 * bank number
vectorLoca double-word offset from the beginning of the string image
lengththe number of microinstructions to transfer
imagea string containing the microinstructions
READRAM transfers length microinstructions starting at RAM location ReadLoc to string image location vectorLoc. The indexing into the string is in microinstructions starting with 0. If class Object has defined
readLoc: readLoc vectorLoc: vectorLoc length: length image: image
 primitive: 2
writeLoc: writeLoc vectorLoc: vectorLoc length: length image: image
 primitive: 3
then the statement
nil readLoc: 010000 vectorLoc: 0 length: 1024 image: image.
will copy RAM1 into string image which better be at least 4096 bytes long and
nil writeLoc: 010000 vectorLoc: 0 length: 1024 image: image.
will load it back again.
With care, it is even possible to load a new version of BYTERP into RAM0. Since the standard release won’t have any methods referencing these primitives they will not be invoked until a user compiles an appropriate method.
The changes are in NOVACODE.SR and define the two primitives READRAM and WRITERAM.
How it works - microcode
The standard microcode checks the first two bytes of a method to determine if a primitive is to be invoked. The first byte is always zero because there are less than 256 primitives and the compiler always forces the first byte to be zero anyway. The compiler must be modified so that the primitive number can be a two-byte quantity to enable microcoded primitives. BYTERP.MU has been modified so that when it checks the primitive number it also checks to see if it is negative. If so, the microcode executes a SWMODE with a BUS dispatch on the entire primitive number ORed with 200. The effect of this is to allow branching to a large number of addresses in any other bank (including the ROM!!). The reason for ORing with 200 is to allow more target addresses with the available constants in the constant ROM.
Once this jump has been taken it is the responsibility of the target microcode to figure out what has happened and do something useful. The environment at this time is similar to that of a nova coded primitive. A jump to PRIMFAIL will cause the smalltalk method to be executed. A jump to PRIMRET with the result oop in AC0 = ARG1 will continue with the oop as the result.
There are many useful routines that other bank microcode needs to reference. All of these labels have had their addresses fixed at the top of the microstore so that other banks can jump to them (see the alto hardware manual sec 8.4). The return address supplied to these routines should be either -1 or -2. This will OR 1’s into the top 9 bits of the next microinstruction dispatch address and jump to 1776 or 1777. A SWMODE jump will then be taken with a target address taken from RAMRETURN (R77) OR 200. A cross-bank call is made by loading RAMRETURN with the actual return address (being careful to assign this address to cause appropriate bank switching and remembering the OR or 200), loading the regular return address with -1 or -2, and jumping to the subroutine. The reason for allowing -1 or -2 is that some routines act differently when returning to an odd or even address.
All of the changes to BYTERP.MU are flagged with ### on the changed or added line. Here is a list of the microcode changes:
$RAMRETURN$R77;RETURN ADDRESS FOR OTHER RAM BANK CALLS ###
Define S-register for returns to other banks (shared with SAVSP)
;********RETURNS TO OTHER RAM BANKS********###
Target locations for returns for other bank calls
;********TARGET FOR RAM BANK SWITCHING********###
Address for ORing into SWMODE jumps for greater flexibility given current contents of constant ROM.
;********RETURNS FROM NOVA********
; AT TOP OF MEMORY SO RAM1, RAM2 CAN GET TO PRIMFAIL AND PRIMRET ###
;********FIX USEFUL ENTRY POINTS SO RAM1, RAM2 CAN JUMP TO THEM ###
;********HASH AND REFLAST MUST BE ODD ###
Fix addresses so all other banks can reference them with SWMODE.
;!3,1,HASH;BECAUSE OF RETURNS FROM TST IN SUBS ###
Commented out because now fixed by previous definition. Still must be odd.
PRIM:TEMP3← L, :DO;[DO,NOOP]
DO:MAR← BCORE+1, SH<0; ALSO SEE IF EXTENDED PRIMITIVE ###
L← STACKP, :DO1; [DO1,RAMCALL] ###
DO1: TEMP2← L;
RAMCALL:SINK ← TEMP3,BUS,SWMODE; DISPATCH ON PRIMITIVE NUMBER ###
X200::X200; AND JUMP (LABEL USED FOR OTHER RAM BANKS) ###
Check for negative primitive and do a SWMODE if so. REFLAST definition now occurs earlier. Still must be odd.
NONINT:NOP; TASK PENDING HERE ###
Fix double TASK bug (see original for bug).
;// RETURNS TO OTHER RAM BANKS ###
X1776:SINK ← RAMRETURN, SWMODE, BUS, :X200; ###
X1777:SINK ← RAMRETURN, SWMODE, BUS, :X200; ###
Returns from subroutines called from other banks come here. Dispatch is made on RAMRETURN and bank changed.
Modifications and Extensions
If it is desired to run another microtask it is necessary to run the interpreter in another bank as no other task can execute in the other ram banks (according to my manual). This can be accomplished by placing forewarding jumps in RAM0 to jump to RAM2 (say) and rearranging some of the addresses in the interpreter. During initialization after a resume the old interpreter is running. Since banks 2 and 1 are loaded before bank 0 and the loading of a bank is an atomic operation, the loading of bank 0 will be instantaneous from the viewpoint of the interpreter.
Filed on: [erie]<marshall>microsmall.memo