MonkeyHeartImpl.mesa
Copyright Ó 1987 by Xerox Corporation. All rights reserved.
Pilfered by Ross January 28, 1987 11:57:47 am PST
Originally by Russ Atkinson (RRA) September 16, 1986 6:25:55 am PDT
Last Edited by: Ross February 3, 1987 11:58:43 am PST
Interface for Basic Tamarin machine. Borrowed heavily from LizardHeartImpl.mesa
(notice even the name similarities).
DIRECTORY
Basics USING [BITAND],
TamarinOps USING [Byte, bytesPerWord, FieldDescriptor, FourBytes, Inst, OnesWord, ProcessorRegister, TrapIndex, Word, ZerosWord],
TamarinOpsUtils USING [BytePCToWordAddress, ByteToCard, BytesToHalf, BytesToWord, CardToWord, CardToByte, DoubleWordShiftLeft, TamAnd, TamNot, TamOr, TamXor, HalfToCard, IntToWord, SingleWordShiftLeft, SingleWordShiftRight, TrapIndexToBytePC, VanillaAdd, VanillaSub, WordAddressToBytePC, WordToBytes, WordToCard, WordToInt],
MonkeyHeart USING [ALUHelper, ALUOps, ChangeLogger, Control, InstBuffer, InstBufferRep, MicroInstruction, NoFault, NoTLBmiss, Processor, ProcessorRep, TrapPC],
MonkeyLiver USING [InstructionExecute],
PrincOpsUtils USING [LongCopy],
Rope USING [ROPE];
MonkeyHeartImpl: CEDAR PROGRAM
IMPORTS Basics, TamarinOpsUtils, MonkeyHeart, PrincOpsUtils
EXPORTS MonkeyHeart
= BEGIN OPEN TamarinOps, TamarinOpsUtils, MonkeyHeart;
CurrentProcVersion: [0..255] ← 0;
The current version # of the processor, stored in StackedStatusWord.version
InstBufferIndex: TYPE = [0..64);
max # of words allowed
WordsInBuffer: NAT ← 4;
# of Tamarin words in the instruction buffer (power of two)
MaxMask: CARDINAL ← WordsInBuffer*bytesPerWord - 1;
Mask for valid bytes in the buffer
MaskInstBufferIndex: PROC [index: CARDINAL] RETURNS [InstBufferIndex] = INLINE {
RETURN [LOOPHOLE[Basics.BITAND[index, MaxMask]]];
};
NewProcessor: PUBLIC PROC [logger: ChangeLogger] RETURNS [p: Processor] = {
p ← NEW[ProcessorRep];
p.logger ← logger;
InitProcessor[p];
};
InitProcessor: PROC [p: Processor] = {
Performs initialization common to NewProcessor and the reset handling.
p.trapsEnabled ← FALSE;
p.resetRequested ← FALSE;
p.ccResult ← FALSE;
p.instBuffer ← NEW[InstBufferRep[WordsInBuffer]];
p.version ← CurrentProcVersion;
p.stats ← [];
};
FlushInstBuffer: PUBLIC PROC [processor: Processor] = {
This routine flushes the instruction buffer. This occurs on a jump (forced or conditional),
a trap, or call.
instBuffer: InstBuffer = processor.instBuffer;
delta: INT = WordToInt[instBuffer.nextPC] - WordToInt[instBuffer.basePC];
IF delta > 0 THEN {
validBytes: INT = instBuffer.validWords*bytesPerWord;
IF validBytes > delta THEN
instBuffer.bytesDiscarded ← instBuffer.bytesDiscarded + validBytes-delta;
};
instBuffer.validWords ← 0;
instBuffer.forcedEmpty ← instBuffer.forcedEmpty + 1;
};
InstructionFetch: PUBLIC PROC [control: Control, processor: Processor]
RETURNS [inst: Inst, rest: Word] = {
thisPC: Word ← processor.regs[nextPC].data;
newPC: Word ← thisPC;
rtnPC: Word;
p: Processor = processor; -- for a short name
cycles: CARDINAL ← 0;
rCycles: CARDINAL ← 0;
initCycles: INT ← processor.stats.cycles;
nBytes: CARDINAL ← 0;
instBuffer: InstBuffer ← p.instBuffer;
instBufferPtr: LONG POINTER;
max: CARDINAL = instBuffer.max * bytesPerWord; -- max bytes in buffer
word: Word;
wordAddr: Word;
valid: InstBufferIndex ← 0; -- valid bytes in inst buffer AFTER newPC
rbi: [0..bytesPerWord);
tx: TrapIndex ← NoFault;
CauseTrap: PROC [code: TrapIndex] = {
All traps do trapsEnabled ← userMode ← FALSE.
This routine was cribbed from LizardHeartImpl and simplified.
-- **** Should add section for Stack UnderFlow (i.e., not present in machine)
rtnPC ← thisPC;
IF WillPushOverflow[p] THEN
StackOverflow has precedence over other traps
code ← StackOverflowTrap;
newPC ← TrapPC[code];
control ← doAbort;
cycles ← cycles + 4; -- a rough guess
tx ← code;
IF code = StackOverflowTrap THEN p.stats.stackOver ← p.stats.stackOver + 1;
};
ForceBufferEmpty: PROC = INLINE {
ForceBufferEmpty forces the instruction buffer to be empty. It is a faster local version of FlushInstBuffer.
instBuffer.validWords ← 0;
instBuffer.forcedEmpty ← instBuffer.forcedEmpty + 1;
IF valid # 0 THEN {
instBuffer.bytesDiscarded ← instBuffer.bytesDiscarded + valid;
valid ← 0;
};
};
FlushInstWord: PROC = INLINE {
Flushes one word from the inst buffer, shifting the words down so that instBuffer[0] corresponds to inst.basePC. We also update instBuffer.basePC forward one word, and instBuffer.validWords backwards one word.
vw: CARDINAL ← instBuffer.validWords - 1;
instBuffer.basePC ← IntToWord[WordToInt[instBuffer.basePC] + bytesPerWord];
instBuffer.validWords ← instBuffer.validWords - 1;
IF vw # 0 THEN TRUSTED {
PrincOpsUtils.LongCopy[from: @instBuffer[1], nwords: instBuffer.validWords*SIZE[Word], to: @instBuffer[0]];
};
instBuffer.validWords ← vw;
};
-- ** Start of Instruction Fetch (only trap which is a uInst trap is reset). All other traps
occur at the opcode boundary.
IF p.resetRequested THEN {
Force the machine to its reset state. The state we force it to here is highly speculative.
thisPC ← newPC ← TrapPC[ResetTrap];
InitProcessor[p];
ForceBufferEmpty[];
p.instBuffer.forcedEmpty ← instBuffer.forcedEmpty;
instBuffer ← p.instBuffer;
initCycles ← cycles ← 0;
control ← nextOpcode;
};
IF control = nextuInst THEN RETURN [LOOPHOLE[0, Inst], ZerosWord];
-- am inside of a uInst sequence and already have uPCa & uPCb so bug out.
TRUSTED {
instBufferPtr ← @instBuffer[0];
};
IF newPC = instBuffer.nextPC THEN {
Compute the number of valid bytes in the buffer.
used: INT ← WordToInt[newPC] - WordToInt[instBuffer.basePC];
valid ← instBuffer.validWords*bytesPerWord;
IF used > 0 AND used < valid THEN valid ← valid - used ELSE valid ← 0;
};
{
Fetch the next instruction byte in the instruction buffer, causing a refill if the instruction buffer is empty.
start: InstBufferIndex ← 0; -- byte index in buffer of newPC
Between instructions we have the following priority:
EUStackOverflowTrap > RescheduleTrap > IFUPageFaultTrap
-- **** Trap Handler should check OverFlow and UnderFlow
IF p.trapsEnabled THEN
SELECT TRUE FROM
IsEUStackOverflow[WordToReg[p.regs[ifuS]]] => {
There was an EU stack overflow caused by the previous instruction. This may be because a previous SIP or RETK instruction enabled traps. Should this ever really happen?
tx ← EUStackOverflowTrap;
GO TO trapEarly;
};
p.rescheduleRequested => {
Take a reschedule trap (which clears rescheduleRequested).
p.rescheduleRequested ← FALSE;
tx ← RescheduleTrap;
GO TO trapEarly;
};
ENDCASE;
IF valid = 0 THEN {
need to fill the inst buffer from scratch
[wordAddr, rbi] ← BytePCToWordAddress[[newPC]];
[word, tx, rCycles] ← p.memory.fetch[p, wordAddr, ZerosWord, ZerosWord];
cycles ← cycles + 2;
The memory takes two cycles to come back with the answer
IF tx # NoTLBmiss THEN {tx ← TLBmissTrap; GO TO trapEarly};
A TLB miss has occurred during an instruction fetch. Go freak out.
IF tx # NoFault THEN {tx ← PageFaultTrap; GO TO trapEarly};
A page fault has occurred during an instruction fetch. Go freak out.
IF rCycles # 0 THEN {
Account for the number of reject cycles. Also keep track of the next time that the ifu cache will be available.
cycles ← cycles + rCycles;
};
valid ← bytesPerWord - rbi;
instBuffer.validWords ← 1;
instBuffer.basePC ← WordAddressToBytePC[wordAddr];
instBuffer[0] ← word;
};
At this point we have at least one valid byte in the buffer. This is enough to determine the instruction and how many bytes must follow.
start ← WordToInt[newPC] - WordToInt[instBuffer.basePC];
IF start >= max THEN ERROR;
word ← instBuffer[start/bytesPerWord];
inst ← LOOPHOLE[WordToBytes[word][start MOD bytesPerWord]];
nBytes ← p.mi[LOOPHOLE[inst, NAT] MOD 256].opLength;
nBytes ← p.opCodeMap[LOOPHOLE[inst, NAT]].size;
IF valid < nBytes THEN {
We must make sure that we have enough bytes in the instruction buffer to execute the whole instruction. This simplifies things later, to be sure. Things are a little simpler because we know we are fetching a full word that will be appended onto the current valid stuff. We won't execute the next instruction unless we have a one line
inst. buffer.
IF instBuffer.validWords = instBuffer.max THEN FlushInstWord[];
[wordAddr, rbi] ← BytePCToWordAddress[[IntToWord[WordToInt[newPC] + valid]]];
[word, tx, rCycles] ← p.memory.fetch[p, wordAddr, ZerosWord, ZerosWord];
cycles ← cycles + 1;
Account for cache access time
IF rCycles # 0 THEN {
Account for the number of reject cycles. Also keep track of the next time that the ifu cache will be available.
cycles ← cycles + rCycles;
rCycles ← 0;
instBuffer.busyUntil ← p.ifuCache.sharedBase.busyUntil;
};
IF tx # NoTLBmiss THEN {tx ← TLBmissTrap; GO TO trapEarly};
A TLB miss has occurred during an instruction fetch. Go freak out.
IF tx # NoFault THEN {tx ← PageFaultTrap; GO TO trapEarly};
A page fault has occurred during an operand byte fetch. Go freak out.
Move the four bytes from the fetched word into the instruction buffer.
start ← WordToInt[newPC] - WordToInt[instBuffer.basePC];
instBuffer[start/bytesPerWord + 1] ← word;
instBuffer.validWords ← instBuffer.validWords + 1;
valid ← valid + bytesPerWord;
};
Move the bytes from the instruction buffer to rest.
IF nBytes > 1 THEN TRUSTED {
RRA: highest byte is first in stream, and so on
instBufferPtr: LONG POINTER TO PACKED ARRAY InstBufferIndex OF Byte ← LOOPHOLE[@instBuffer[0]];
SELECT nBytes FROM
5 => {
rest ← BytesToWord[[
instBufferPtr[MaskInstBufferIndex[start+1]],
instBufferPtr[MaskInstBufferIndex[start+2]],
instBufferPtr[MaskInstBufferIndex[start+3]],
instBufferPtr[MaskInstBufferIndex[start+4]]
]];
};
4 => {
rest ← BytesToWord[[
CardToByte[0],
instBufferPtr[MaskInstBufferIndex[start+1]],
instBufferPtr[MaskInstBufferIndex[start+2]],
instBufferPtr[MaskInstBufferIndex[start+3]]
]];
};
3 => {
rest ← CardToWord[HalfToCard[BytesToHalf[[
instBufferPtr[MaskInstBufferIndex[start+1]],
instBufferPtr[MaskInstBufferIndex[start+2]]
]]]];
};
2 => {
rest ← CardToWord[ByteToCard[
instBufferPtr[MaskInstBufferIndex[start+1]]
]];
};
ENDCASE => ERROR;
};
newPC ← IntToWord[WordToInt[newPC] + nBytes];
valid ← valid - nBytes;
instBuffer.nextPC ← newPC;
Update the cycle count to allow EU cache to delay properly for busy bus
p.stats.cycles ← p.stats.cycles+cycles;
p.stats.instBufferCycles ← p.stats.instBufferCycles+cycles;
cycles ← 0;
Finally, we get to execute the instruction we fetched.
[newPC, rtnPC, control] ← MonkeyLiver.Execute[p, thisPC, inst, rest];
EXITS
trapEarly => CauseTrap[tx];
};
RETURN [inst, rest];
};
InstructionDecode: PUBLIC PROC [control: Control, processor: Processor, inst: Inst]
RETURNS [mi: MicroInstruction] = {
p: Processor = processor; -- for a short name
upc: NAT;
SELECT control FROM
nextOpcode, doReturn => {
RETURN [p.mi[LOOPHOLE[inst, NAT] MOD 256]];
};
nextuInst => {
IF p.ccResult THEN upc ← p.nextuPCa ELSE upc ← p.nextuPCb;
RETURN [p.mi[upc]];
};
doCall => {
p.stats.calls ← p.stats.calls + 1;
RETURN [p.mi[LOOPHOLE[inst, NAT] MOD 256]];
};
ENDCASE => ERROR;
};
Utility Routines for manipulating data (incomplete)
DoALUOp: PUBLIC PROC [processor: Processor, wordA,wordB: Word, op: ALUOps, trap: TrapIndex] RETURNS [res: Word, resCode: TrapIndex ← NoFault] = {
carryOut, overflow, boundsCheck, illegalLisp: BOOLFALSE;
carryIn: BOOL ← processor.euCarry;
Calculate the result word and carryOut
SELECT op FROM
SAdd => {
[res, ] ← WordCarryAdd[wordA, wordB, carryIn];
SELECT ALUHelper[wordA[0], wordB[0], res[0]] FROM
a0b0c1, a1b1c0 => {overflow ← TRUE};
ENDCASE;
};
UAdd =>
[res, carryOut] ← WordCarryAdd[wordA, wordB, carryIn];
SSub => {
[res, ] ← WordCarryAdd[wordA, TamNot[wordB], NOT carryIn];
SELECT ALUHelper[wordA[0], wordB[0], res[0]] FROM
a0b1c1, a1b0c0 => overflow ← TRUE;
ENDCASE;
};
USub => {
[res, carryOut] ← WordCarryAdd[wordA, TamNot[wordB], NOT carryIn];
carryOut ← NOT carryOut;
};
LAdd, LSub => {
res ← IF op = LAdd THEN VanillaAdd[wordA, wordB] ELSE VanillaSub[wordA, wordB];
IF res[0] # res[1] OR res[1] # res[2] OR wordA[0] # wordA[1] OR wordA[1] # wordA[2] OR wordB[0] # wordB[1] OR wordB[1] # wordB[2] THEN
illegalLisp ← TRUE
};
VAdd => {res ← VanillaAdd[wordA, wordB]; carryOut ← carryIn};
VSub => {res ← VanillaSub[wordA, wordB]; carryOut ← carryIn};
And => {res ← TamAnd[wordA, wordB]; RETURN};
Or => {res ← TamOr[wordA, wordB]; RETURN};
Xor => {res ← TamXor[wordA, wordB]; RETURN};
BndChk => {
boundsCheck ← WordCarryAdd[wordA, TamNot[wordB], TRUE].carryOut;
res ← wordA;
carryOut ← carryIn;
};
ENDCASE => ERROR;
Detarmine the returned condition code
resCode ← (IF (SELECT trap FROM
ALUCondFalse => FALSE,
ALUCondEZ => res = ZerosWord,
ALUCondLZ => WordToInt[res] < 0,
ALUCondLE => WordToInt[res] <= 0,
ModeFault => TRUE,
ALUCondNE => res = ZerosWord,
ALUCondGE => WordToInt[res] >= 0,
ALUCondGZ => WordToInt[res] > 0,
ALUCondOver => overflow,
ALUCondBC => boundsCheck,
ALUCondIL => illegalLisp,
ALUCondDO => FALSE, -- not yet implemented
ALUCondNotOver => NOT overflow,
ALUCondNB => NOT boundsCheck,
ALUCondNI => NOT illegalLisp,
ENDCASE => ERROR) THEN trap ELSE NoFault);
IF resCode = NoFault THEN processor.euCarry ← carryOut;
There is the possibility that the carry out should NOT be set if the returned code # ALUCondFalse. One of these days we should find out if this is true.
};
WordCarryAdd: PROC [wordA, wordB: Word, carryIn: BOOL] RETURNS [wordC: Word, carryOut: BOOLFALSE] = {
cardA: CARD ← WordToCard[wordA];
cardB: CARD ← WordToCard[wordB];
cardC: CARD ← cardA+cardB;
SELECT cardC FROM
< cardA, < cardB => carryOut ← TRUE;
ENDCASE;
IF carryIn THEN {cardC ← cardC+1; IF cardC = 0 THEN carryOut ← TRUE};
wordC ← CardToWord[cardC];
};
FieldUnit: PUBLIC PROC [Left,Right: Word, fd: FieldDescriptor] RETURNS [out: Word] = {
shifter: Word = DoubleWordShiftLeft[Left, Right, fd.shift];
The shifter output has the input double word shifted left by fd.shift bits
mask: Word ← IF fd.mask >= 32 THEN OnesWord ELSE SingleWordShiftRight[OnesWord, 32-fd.mask];
The default mask has fd.mask 1s right-justified in the word
IF fd.insert
THEN {
mask ← TamAnd[mask, SingleWordShiftLeft[OnesWord, fd.shift]];
fd.insert => clear rightmost fd.shift bits of the mask
out ← TamOr[TamAnd[mask, shifter], TamAnd[TamNot[mask], Right]];
1 bits in the mask select the shifter output
0 bits in the mask select bits from Right
}
ELSE
out ← TamAnd[mask, shifter];
1 bits in the mask select the shifter output
};
WillPushOverflow: PUBLIC PROC [processor: Processor] RETURNS [BOOL] = {
IF processor.stackEntries < IFUOverflow-1 THEN RETURN [FALSE] ELSE {
SELECT processor.stackEntries FROM
= IFUOverflow-1 => {};
< IFUStackSize =>
IF processor.trapsEnabled THEN
SIGNAL OutsideEnvelope["IFU control stack too full with traps enabled"];
ENDCASE =>
SIGNAL OutsideEnvelope["IFU control stack has more than IFUStackSize entries"];
RETURN [processor.trapsEnabled];
};
};
OutsideEnvelope: PUBLIC SIGNAL [explanation: Rope.ROPE] = CODE;
END.