*-----------------------------------------------------------
Title[DMesaMiscOps.mc...January 27, 1984 1:13 PM...Taft];
* MISC entry point; miscellaneous MISC opcodes -- groups 000, 100, and 240.
*-----------------------------------------------------------
%
CONTENTS, by order of alpha byte value
Defined on both Dorado and Dolphin:
alpha=0 Associate
alpha=1 SetFlags
alpha=2 (read ram unimplemented)
alpha=3 LoadRam
alpha=4 (IOReset unimplemented)
alpha=5 Input
alpha=6 Output
alpha=7 Checksum
alpha=10b Set maintenance panel (PrincOps only)
alpha=11b ReadClock
alpha=12b GenIOin
alpha=13b GenIOout (diablo interface)
alpha=14b (Xfer Long de-implemented)
alpha=15b (TextBlt unimplemented)
(was Write MDS for all tasks -- de-implemented)
alpha=16b GetFlags
alpha=17b Set default disk partition (Alto only)
alpha=100b LocalBlkZ
alpha=102b LongBlkZ
alpha=104b Version (implemented in DMesaVersion.mc)
Special operations defined on Dorado only:
alpha=240b Zero and Enable Event counters
alpha=241b Read Event counters
alpha=242b Stop Event counters
alpha=243b Set PC Histogram Address
alpha=244b Unused
alpha=245b Unused
alpha=246b Read/write muffler/manifold system
alpha=247b Reset Ethernet hardware and tasks (PrincOps only)
alpha=250B Unused (used to be Boot; PrincOps only)
alpha=251B Get size of real and virtual memory
alpha=252B Halt Dorado (for timed power-off)
alpha=253B Set display field rate
alpha=254b Reset disk hardware and task (PrincOps only)
alpha=255b Set interval timer (PrincOps only)
alpha=256b Stable Storage block input
alpha=257b Stable Storage block output
%
TopLevel;
*-----------------------------------------------------------
IFUR[MISC, 2, MDS]; * Do miscellaneous operation alpha
*-----------------------------------------------------------
T← (2(ID)) LSH 1, Branch[MISCM1];
:IfMEP;
T← (2(ID)) LSH 1, Stack← MD, Branch[MISCM1];
T← (2(ID)) LSH 1, StkP+1;
:If[AltoMode]; ********** Alto version **********
T← T+1; * Remember entry point 2
:EndIf; **********************************
:EndIf;
MISCM1:
RTemp1← LDF[T, 4, 6];
BigBDispatch← RTemp1;
:If[AltoMode]; ********** Alto version **********
OTPReg← BigBDispatch← T, Branch[MiscDispatch];
:Else; ******** PrincOps version ********
BigBDispatch← T, Branch[MiscDispatch];
:EndIf; **********************************
* Upon dispatch, T contains TOS (if there is one) and StkP has been advanced
* in preparation for pushing a result. Routines that do not push a result
* must decrement StkP.
* Alto mode: OTPReg contains 4*alpha if entry point 0 or 1 was
* used, 4*alpha+1 if entry point 2 (used by floating point trap handler).
* Spread the entry points 4 apart and put them at odd locations.
* This is so that the first instruction can do a Call if desired, and so that
* call locations and some conditional branch targets are preserved.
* alpha=0, 1, ... 77 => dispatch 1, 5, ... 375 into MiscTable0,
* alpha=100, 101, ... 177 => dispatch 1, 5, ... 375 into MiscTable1, etc.
*-----------------------------------------------------------
* Some definitions for Mesa's record that holds a page number and map flags
*-----------------------------------------------------------
* MapEntry: TYPE = MACHINE DEPENDENT RECORD [
* unused, wProtect, dirty, ref: BOOLEAN, realPage: [0..7777B]];
MC[MesaF&P.wProtect, 40000];
MC[MesaF&P.dirty, 20000];
MC[MesaF&P.ref, 10000];
MC[MesaF&P.wProtect&dirty, MesaF&P.wProtect, MesaF&P.dirty];
MC[MesaF&P.flagsMask, 70000];
MC[MesaF&P.pageMask, 7777];
* Mesa defines vacant as wProtect and dirty and ref'.
* Unfortunately, the Dorado defines vacant as wProtect and dirty,
* without regard to ref.
* To get around this problem, we take over one of the two high-order
* bits of the real page number, which do not participate in storage
* addressing unless 256K storage chips are installed (unlikely
* during the lifetime of the Dorado).
* When Mesa asks us to set the combination wProtect and dirty and ref
* (which is NOT vacant by Mesa's definition), we instead set
* wProtect and dirty' and ref, and we set the extra bit.
* When Mesa asks to read the flags, we or together the hardware dirty
* bit and the extra bit.
*-----------------------------------------------------------
Associate: MiscTable[0],
* m: MapEntry ← Pop[]; vp ← Pop[]; Map[vp] ← m;
*-----------------------------------------------------------
T← MD, StkP-2;
T← Stack&+1, Call[SetBRForPage];
T← Stack&-2, Call[WriteMapPage];
* Restart IFU, since FlushPage (called from WriteMapPage) reset it.
AssocExit:
T← (ID)-(PCX')-1; * T← PCX+IL
IFUReset, Branch[SetPCAndJump0];
*-----------------------------------------------------------
SetFlags: MiscTable[1],
* newM: MapEntry ← Pop[]; vp ← Pop[]; oldM: MapEntry ← Map[vp];
* newM.realPage ← oldM.realPage; Map[vp] ← newM; Push[oldM];
*-----------------------------------------------------------
* Read current real page number from map.
* Note that all we care about are the page number and the vacant indication,
* which cannot be influenced either by concurrent IFU or I/O activity or by
* the contents of the cache flags. Therefore it is not necessary to flush
* the cache or turn tasking off while reading the map.
T← MD, StkP-2;
T← Stack&+1, Call[SetBRForPage]; * Returns RTemp0=0
RMap← RTemp0, SCall[TranslateMapEntry]; * Returns map entry in T
* +1 return: entry is vacant, just return "vacant" and don't set new flags.
Stack-1← T, Branch[AssocExit];
* +2 return: write the map with new flags and old real page number.
T← RCY[T, Stack&-1, 14]; * Combine new flags with old page
T← LCY[T, T, 14], Call[WriteMapPage];
* The pipe now contains the PREVIOUS contents of the map entry just written.
* Tasking is off if came from GetFlags.
SetFExit:
TaskingOn, Call[TranslateMapEntry];
Stack← T, Branch[AssocExit],
DispTable[1, 1, 1]; * Always return here
*-----------------------------------------------------------
GetFlags: MiscTable[16],
* vp ← Pop[]; Push[Map[vp]];
*-----------------------------------------------------------
Nop;
StkP-1, Call[SetBRForPage];
* Flush this page from the cache so that the map flags are updated.
* Note: in principle it should not be necessary to flush twice or to
* turn tasking off; but in fact a single flush with tasking on
* seems insufficient to capture the dirty bit reliably.
Call[FlushPage]; * Returns RTemp0=0, TaskingOff
* Read and translate map entry.
RMap← RTemp0, Branch[SetFExit];
* Map-related subroutines
*-----------------------------------------------------------
SetBRForPage:
* Enter: T=virtual page
* Exit: T unchanged
* MemBase=LPtr, LPtr=VA of virtual page
* RTemp0=0
*-----------------------------------------------------------
Subroutine;
MemBase← LPtr;
RTemp0← LSH[T, 10]; * set up MemBase and BR
BRLo← RTemp0;
RTemp0← RSH[T, 10];
BRHi← RTemp0, RTemp0← A0, Return;
*-----------------------------------------------------------
FlushPage:
* Flushes one page from cache with tasking off. This is done to maintain consistency
* between cache and real memory when the map is about to change.
* Enter: MemBase=LPtr, LPtr=VA of base of page
* Exit: RTemp0=0
* Tasking off, IFU reset (to ensure that activity of the IFU and of the
* other tasks will not bring new munches into the cache)
* Clobbers RTemp0, Cnt
*-----------------------------------------------------------
Subroutine;
* Do the flush twice, once with TaskingOn and again with TaskingOff.
* The idea is that all the actual flushing of dirty munches will occur
* with TaskingOn; however, it is logically necessary to do it again with
* TaskingOff because in the meantime an I/O task might have touched the
* page we are flushing, and subsequent Map operations depend on the page being
* completely flushed.
IFUReset;
RTemp0← A0, Cnt← 17S, B← MD; * Assume 20b munches per page
RTemp0← Flush← RTemp0, Carry20, Branch[., Cnt#0&-1];
RTemp0← A0, Cnt← 16S;
TaskingOff;
RTemp0← Flush← RTemp0, Carry20, Branch[., Cnt#0&-1];
Flush← RTemp0, RTemp0← A0, Return;
*-----------------------------------------------------------
WriteMapPage:
* Enter: T = map flags and real page number in Mesa format
* MemBase = LPtr, LPtr contains virtual address
* Exit: Map written with new value
* Pipe contains PREVIOUS contents of map entry
* Clobbers T, Q, RTemp0, RTemp1, Cnt
*-----------------------------------------------------------
Subroutine;
* Shift wProtect and dirty bits into position for the hardware, and
* test for the combination wProtect & dirty & ref.
* Mesa format is B1=wProtect, B2=dirty, B3=ref.
* Hardware format for writing is B0=wProtect, B1=dirty (ref can't be set).
RTemp1← NOT T, Q← Link;
TopLevel;
T← T AND (MesaF&P.pageMask);
RTemp1← (RTemp1) AND (MesaF&P.flagsMask);
RTemp1← (NOT (RTemp1)) LSH 1, Branch[.+3, ALU#0];
* This is the state wProtect & dirty & ref, which the Dorado hardware
* can't handle (it would mistakenly interpret it as vacant).
* Turn off the dirty bit, and set the sign bit in the real page number,
* which we have taken over as a duplicate dirty bit.
RTemp1← (RTemp1) AND NOT (LShift[MesaF&P.dirty!, 1]C);
T← T OR (100000C);
* Note: RTemp1 was carefully masked (above) so as not to select TIOA values
* in [10..17], which can screw up the disk controller!
TIOA← RTemp1; * Set up flags for Map←
Call[FlushPage]; * Returns with TaskingOff; doesn't clobber T
* Now write the map entry; T contains the new real page number.
Map← 0S, MapBuf← T; * Write map entry (real page & flags)
PD← T-T-1, TaskingOn;
PD← PRef, Branch[., ALU<0]; * Wait for map reference to finish
* Writing the map zeroed the ref bit. If we desire to set ref, do so by
* issuing a PreFetch to the page. Note: this does NOT clobber the pipe,
* because PreFetch goes to the ring buffer part, not the private emulator entry.
PD← (RTemp1) AND (LShift[MesaF&P.ref!, 1]C);
T← A0, Link← Q, Branch[.+2, ALU=0];
Subroutine;
PreFetch← 0S;
TIOA← T, Return; * TIOA=0 required by Mesa emulator
*-----------------------------------------------------------
TranslateMapEntry:
* Translates result of last map reference (Map← or RMap←) to Mesa format.
* This procedure first waits for that map reference to finish.
* Enter: MemBase = LPtr, LPtr contains virtual address
* Call: SCall[TranslateMapEntry]
* Exit: Returns to caller+1 if entry is vacant, caller+2 otherwise.
* T = map flags and real page number in Mesa format
* Clobbers RTemp0
*-----------------------------------------------------------
Subroutine;
PD← PRef, Global; * Subroutine entry instruction
T← NOT (Map'), Branch[.+2, ALU>=0]; * Read previous real page from Pipe
PD← PRef, Branch[.-1];
* Convert hardware flags to Mesa format.
* The hardware returns the flags as B0=ref, B2=wProtect, B3=dirty, but
* Mesa wants to see them in the form B1=WP, B2=dirty, B3=ref.
RTemp0← Errors', Branch[.+2, ALU>=0]; * Previous flags (complemented)
* Transfer the duplicate dirty bit from the real page to the flags
T← T XOR (Or[100000, MesaF&P.dirty!]C), Branch[RealPageInRange];
* Crock for Alto/Mesa and pre-Trinity Pilot:
* If real page > 7777B (the highest that can be represented in a MapEntry)
* then return Vacant. This compensates for the fact that the software's
* initial real memory scan doesn't have an end test!
PD← T AND NOT (MesaF&P.pageMask);
Branch[RealPageInRange, ALU=0];
:If[Not[AltoMode]]; ******** PrincOps version ********
* For Pilot, actually change the map entry to be vacant. This is so that
* references to that page will fault. (Not sure whether this really matters.)
RTemp0← 140000C; * wProtect & dirty
RTemp0← A0, TIOA← RTemp0;
PD← (Map← RTemp0)-1, MapBuf← RTemp0;
PD← PRef, Branch[., ALU<0];
TIOA← RTemp0;
:EndIf; **********************************
T← (Or[MesaF&P.wProtect!, MesaF&P.dirty!]C), Return; * Say it is vacant
* The following instruction zeroes Carry. It must be the last arithmetic
* instruction in ReadMapPage. Note: flags are still complemented.
RealPageInRange:
RTemp0← (A← RTemp0) LSH 1; * Shift wProtect&dirty to Mesa format
RTemp0← (RTemp0) AND (MesaF&P.wProtect&dirty),
Branch[.+2, ALU<0]; * Branch if not ref
* Uncomplement the wProtect and dirty flags, and set ref if appropriate.
RTemp0← (RTemp0) XOR (Or[MesaF&P.wProtect&dirty!, MesaF&P.ref!]C),
DblBranch[MapVacant, MapNotVacant, ALU=0];
RTemp0← (RTemp0) XOR (MesaF&P.wProtect&dirty),
DblBranch[MapVacant, MapNotVacant, ALU=0];
:If[AltoMode]; ********** Alto version **********
* Alto/Mesa wants to see real page = 0 if vacant.
MapVacant:
T← MesaF&P.wProtect&dirty, Return; * Vacant, return +1 with real page = 0
:Else; ******** PrincOps version ********
* PrincOps real page is undefined if vacant; however, the Cedar Nucleus depends
* on being able to read and write the real page number of vacant entries.
MapVacant:
T← T OR (MesaF&P.wProtect&dirty), Return; * Vacant, return +1
:EndIf; **********************************
MapNotVacant:
T← T OR (RTemp0), Return[Carry']; * Not vacant, return +2
TopLevel;
*-----------------------------------------------------------
InputM: MiscTable[5],
* device ← Pop[]; Push[Input[device]];
* Dorado-only feature: if bit 0 of device is on, IOB parity checking is disabled.
*-----------------------------------------------------------
T← LSH[T, 10], StkP-1;
T← A0, TIOA← T, Stack, Branch[.+2, R<0];
Stack← Input, Branch[MesaIOTail];
Stack← InputNoPE, Branch[MesaIOTail];
*-----------------------------------------------------------
OutputM: MiscTable[6],
* device ← Pop[]; data ← Pop; Output[device, data];
*-----------------------------------------------------------
T← LSH[T, 10], StkP-2;
T← A0, TIOA← T;
Output← Stack&-1;
MesaIOTail:
TIOA← T, IFUNext0; * Know T=0 here
:If[Not[AltoMode]]; ******** PrincOps version ********
*-----------------------------------------------------------
SetMaintPanel: MiscTable[10],
* maintPanel ← Pop[];
* Also generate an Opcode trap iff there is a trap handler set up,
* so the software has a chance to look at the MP code.
*-----------------------------------------------------------
MemBase← SD;
Fetch← Add[sUnimplemented!]S;
PD← MD, StkP-2;
MaintPanel← T, Branch[.+2, ALU#0];
IFUNext0;
T← A0, Branch[OpcodeTrap];
:EndIf; **********************************
*-----------------------------------------------------------
RClockM: MiscTable[11],
* PushLong[clock];
*-----------------------------------------------------------
RBase← RBase[RTClock];
T← RTClock, TaskingOff; * Low part -- read atomically!!
Stack&+1← T;
T← RTC430, TaskingOn, Branch[PushT]; * High part
KnowRBase[RTemp0];
*-----------------------------------------------------------
GenIOin: MiscTable[12],
* Push[PrinterIn[]];
*-----------------------------------------------------------
StackT← NOT (EventCntA'), IFUNext2;
*-----------------------------------------------------------
GenIOout: MiscTable[13],
* PrinterOut[Pop[]];
*-----------------------------------------------------------
EventCntB← T, StkP-2, IFUNext0;
*-----------------------------------------------------------
Checksum: MiscTable[7],
* DO
* p: LONG POINTER ← PopLong[]; c: CARDINAL ← Pop[]; s: WORD ← Pop[];
* IF c=0 THEN EXIT;
* s ← OnesComplementAddAndLeftCycle[s, Fetch[p]↑];
* Push[s]; Push[c-1]; PushLong[p+1];
* IF InterruptPending[] THEN GOTO Suspend;
* REPEAT Suspend => PC ← savePC;
* ENDLOOP;
* IF s=-1 THEN s←0; -- turn ones-complement -0 into +0
* Push[s];
*-----------------------------------------------------------
Nop;
RTemp0← A0, MemBase← LPtr;
BRHi← T, StkP-2; * Pop the long pointer into LPtr
BRLo← Stack&-1;
* Come here once per munch. RTemp0 contains LPtr-relative address.
* StkP addresses c (count of words remaining).
* On the first iteration, checksum ((c-1) mod 20b) +1 words; on subsequent
* iterations, checksum 20b words. Note that on subsequent iterations,
* c mod 20b = 0, so ((c-1) mod 20b) +1 = 20b.
CSMunch:
T← (Stack&-1)-1; * A-1 generates carry iff A#0
T← T AND (17C), Branch[CSDone, Carry'];
* Touch the first and last words to be checksummed in this block,
* and issue a PreFetch for the next block. T = word count -1.
T← (Fetch← RTemp0)+(Q← T), Branch[CSInterrupt, Reschedule];
T← (Fetch← T)+(20C);
PreFetch← T, T← Stack&+1, Stack&+1← MD;
* All possible faults have happened by this point.
Stack&+1← (Stack&+1)-(Cnt← Q)-1; * Update word count
RTemp0← (Fetch← RTemp0)+1;
Stack&+1← (Stack&+1)+Q+1; * Update long pointer on stack
Stack&-2← A← Stack&-2, XorSavedCarry;
* Inner loop: 3 instructions per word. T has partial sum, StkP adresses c.
CSWordLoop:
T← T+MD, StkP-1, Branch[CSWordExit, Cnt=0&-1];
RTemp0← (Fetch← RTemp0)+1, Branch[.+2, Carry];
CSAddNoCarry: * ALU=0 iff came from CSWordExit
Stack&+1← T← T LCY 1, DblBranch[CSWordLoop, CSMunch, ALU#0];
CSAddCarry:
Stack&+1← T← (T+1) LCY 1, DblBranch[CSWordLoop, CSMunch, ALU#0];
CSWordExit:
PD← A0, DblBranch[CSAddCarry, CSAddNoCarry, Carry];
* Here when c=0. If result is -0, change it to +0. StkP addresses s.
CSDone:
PD← (Stack)+1; * Carry iff sum=177777
StackT← A← StackT, XorSavedCarry, IFUNext2;
CSInterrupt:
StkP+3, Branch[BLTInterrupt];
% ********* De-implemented code **********
*-----------------------------------------------------------
WMDS: MiscTable[15], * Write MDS
* This opcode sets the high bits of MDS. It supports the execution
* IME system code on the Dorado. The idea is to provide IME with a
* way of switching whole "banks" of memory very quickly. This opcode
* touches all the base registers. The Mesa software must assure that
* IO devices are quiescent. This opcode affects the way IO devices
* work as well as the way the mesa emulator works.
* The MESA instruction, WMDS, pops the new MDSBase from the current top
* of the Stack. Then it calls the subroutine, SetMDS to do the work.
* WMDS does NOT switch the code base.
* The NOVA instruction, WMDS, takes the new MDSBase from AC0 and calls
* SetMDS to do the work. WMDS switches the code base also.
* SetMDS:
* ENTER w/ T= new MDS base, RBase=RBase[EmuBRHiReg].
* CLOBBER T, ETemp1, ETemp2, EmuBRHiReg, ALL BRHi
* registers and MemBase.
*-----------------------------------------------------------
StkP-2, RBase← RBase[EmuBRHiReg], Call[SetMDS];
T← (ID)-(PCX')-1, Branch[SetPCAndJump0];
WMDSNova:
Nop, At[sd400, 22];
T← Stack, Call[SetMDS];
Branch[AEmuNext];
Subroutine;
KnowRBase[EmuBRHiReg];
SetMDS:
* We'll compute a new MDS offset for BRHi based upon (newMDSHi-EmuBRHiReg).
T← T-(EmuBRHiReg); * EmuBRHiReg← new MDS, T← difference
EmuBRHiReg← (EmuBRHiReg)+T;
ETemp2← Lshift[36, 10]C; * set MemBase[0..36B],leave CODE alone
SetMDSL:
MemBase← ETemp2;
DummyRef← 0S, B← MD;
ETemp1← VAHi; * capture hi order bits of current BR
ETemp1← (ETemp1)+T; * this is the new value to use
ETemp2← (ETemp2)-(400C);
BRHi← ETemp1, Branch[SetMDSL, ALU>=0];
Q← Link;
TopLevel;
* Call SetDisplayBRHi; it will return. Note Link← overrides implied Call.
T← EmuBRHiReg, RBase← RBase[RTemp0];
MDSHi← T, Link← Q, Branch[SetDisplayBRHi];
% ********* End of de-implemented code **********
% ********* De-implemented code **********
*-----------------------------------------------------------
XferL: MiscTable[14], * Long Xfer (MDS switch)
* StoreMDS[@LocalBase[L].pc]↑ ← PC;
* newMDS ← Pop[]; destLink ← Pop[]; Push[L]; Push[LOOPHOLE[MDS, Long].high];
* LOOPHOLE[MDS, Long].high ← newMDS;
* XFER[src: L, dst: destLink];
*-----------------------------------------------------------
StkP-2;
DLink← Stack, Call[SavePCInFrameIL]; * DLink← destination link
T← SLink; * SLink=L
Stack&+1← T, RBase← RBase[EmuBRHiReg]; * save L in Stack
T← EmuBRHiReg;
Stack← T, Q← Stack; * Stack← mds, Q← newMDS
T← Q, Call[SetMDS]; * switch MDS bases
RBase← RBase[RTemp0];
MemBase← MDS, Branch[Xfer]; * Now do Xfer in new MDSbase
% ********* End of de-implemented code **********
*-----------------------------------------------------------
LoadRamM: MiscTable[3], * Load Ram and jump
* flag ← Pop[]; itemArray: LONG POINTER ← PopLong[];
* nextItem: LONG POINTER ← LoadRam[itemArray+1];
* IF (flag MOD 2)=1 THEN {jump to the start address in the new Ram image};
* PushLong[nextItem]; [] ← PopLong[]; -- leave pointer to next Item above TOS
*-----------------------------------------------------------
StkP-3, RBase← RBase[LRFlag];
LRFlag← NOT T, MemBase← LPTR; * LoadRam reverses sense of flag
* Note: software passes (pointer to first item)-1; must skip over it!!!
T← (Stack&+1)+1;
BRLo← T;
T← A← Stack&-1, XorSavedCarry;
BRHi← T, Call[LoadRam];
DummyRef← LRItem, B← MD; * Convert ending address to long pointer
Stack&+1← VALo;
Stack&-2← VAHi, Branch[AssocExit]; * IFUReset and restart IFU
:If[AltoMode]; ********** Alto version **********
*-----------------------------------------------------------
SetPartitionM: MiscTable[17], * Set default disk partition number
* p: Partition ← Pop[];
* IF p=Partition[0] THEN Push[currentPartition]
* ELSE IF p IN [Partition[1]..Partition[maxPartition]]
* THEN BEGIN currentPartition ← p; Push[-1]; END ELSE Push[0];
*-----------------------------------------------------------
StkP-1, Branch[SetDefaultDiskA]; * Same as Alto instruction
:EndIf; **********************************
KnowRBase[RTemp0];
* Memory block zeroing opcodes -- added to instruction set for the benefit of Cedar,
* but useful in their own right.
*** Someday integrate these with the BLT logic in DMesaRW.mc. But for now: ***
*-----------------------------------------------------------
LocalBlkZM: MiscTable[100], * Local block zero
* count: CARDINAL ← Pop[];
* FOR offset DECREASING IN [0..count) DO StoreMDS[L+offset]↑ ← 0; ENDLOOP;
* (The implementation must check for interrupts.)
*-----------------------------------------------------------
T← (ID)+T+1, MemBase← L, StkP-1, * (Offset of local 0)-1 (ID=2 here)
Branch[BlkZCommon];
*-----------------------------------------------------------
LongBlkZM: MiscTable[102], * Long block zero
* count: CARDINAL ← Pop[];
* p: LONG POINTER ← PopLong[]; SP ← SP+2; -- leave long pointer on stack
* FOR offset DECREASING IN [0..count) DO Store[p+offset]↑ ← 0; ENDLOOP;
* (The implementation must check for interrupts, and may push the intermediate
* count back on the stack, but must not disturb the pointer. This is why
* the operation is done in descending order of address.)
*-----------------------------------------------------------
T← T-1, MemBase← LPtr, StkP-2;
BRHi← Stack&-1;
BRLo← Stack&+2;
* T = (base-relative) address of last word of block; Stack = count.
BlkZCommon:
RTemp1← T-(20C);
RTemp0← T, PreFetch← RTemp1; * PreFetch 20b words ahead
T← (Stack)-1;
T← T AND (17C), Branch[BlkZDone, Carry']; * Branch if count is zero
* On first iteration, do ((count-1) mod 20b)+1 words;
* on subsequent iterations, do 20b words.
* T = (# words to do this iteration)-1.
BlkZMunchEntry:
RTemp1← (RTemp1)-(20C), Branch[.+2, R<0];
PreFetch← RTemp1; * PreFetch 40b words ahead
Cnt← T, Branch[BlkZInterrupt, Reschedule];
RTemp0← (Store← RTemp0)-1, DBuf← 0C, Branch[.+2, Cnt=0&-1];
BlkZWordLoop:
RTemp0← (Store← RTemp0)-1, DBuf← 0C, Branch[BlkZWordLoop, Cnt#0&-1];
Stack← (Stack)-T-1, T← MD; * Wait for fault, then update count
T← 17C, Branch[BlkZMunchEntry, ALU#0];
BlkZDone:
StkP-1, IFUNext0;
BlkZInterrupt:
Branch[MesaReschedTrap];
* Event counter and PC sampling stuff.
* Also see subroutines in Junk.mc
MC[EnableEventsAB, 6000];
MC[DisableEventsAB, 0];
*-----------------------------------------------------------
StartCountM: MiscTable[240], * Start Event Counters
* control ← Pop[]; ZeroEventCounters[]; InsSetOrEvent[control];
*-----------------------------------------------------------
StkP-2, RBase← RBase[Events], Call[StartCounters];
IFUNext0;
*-----------------------------------------------------------
ReadCountM: MiscTable[241], * Read Event Counters
* CounterValues: TYPE = MACHINE DEPENDENT RECORD
* [
* eventALo, eventAHi1, eventAHi0: CARDINAL, -- event counterA
* eventBLo, eventBHi1, eventBHi0: CARDINAL, -- event counterB
* ]
* p: LONG POINTER TO CounterValues ← PopLong[]; ReadCounters[p];
*-----------------------------------------------------------
MemBase← LPtr, StkP-1;
BRHi← Stack&-1;
BRLo← Stack&-1;
T← A0, RBase← RBase[Events], Call[ReadCounters];
IFUNext0;
*-----------------------------------------------------------
StopCountM: MiscTable[242], * Stop Event Counters
* InsSetOrEvent[disableEventsAB];
*-----------------------------------------------------------
T← DisableEventsAB, StkP-1;
InsSetOrEvent← T, IFUNext0;
*-----------------------------------------------------------
SetPCHistM: MiscTable[243], * Set PC Sampling Histogram
* PCHistogram: TYPE = ARRAY [0..4095] OF LONG CARDINAL;
* p: LONG POINTER TO PCHistogram ← PopLong[];
* IF p#NIL THEN EnablePCSampling[p] ELSE DisablePCSampling[];
*-----------------------------------------------------------
StkP-2, RBase← RBase[Events];
Q← Stack&-1, Call[SetPCHistAddr];
IFUNext0;
:If[Not[AltoMode]]; ******** PrincOps version ********
*-----------------------------------------------------------
SetIntervalTimerM: MiscTable[255], * Set Interval Timer time
*-----------------------------------------------------------
StkP-2, RBase← RBase[WakeupTime];
WakeupTime← T, IFUNext0;
:EndIf; **********************************
* Other Dorado-only instructions
KnowRBase[RTemp0];
*-----------------------------------------------------------
RWMufManM: MiscTable[246], * Read/write muffler/manifold system
* arg: RECORD [useDMD: BOOLEAN, unused: [0..7], dMuxAddr: [0..7777B]];
* result: RECORD [dMuxData: BOOLEAN, unused: [0..77777B]];
* arg ← Pop[]; SetDMuxAddress[arg.dMuxAddr];
* IF arg.useDMD THEN UseDMD[];
* result.dMuxData ← DMuxData[]; Push[result];
*-----------------------------------------------------------
StkP-1, Call[SetDMuxAddress]; * Takes address and returns data in T
Stack, Branch[.+2, R>=0];
UseDMD;
StackT← T, IFUNext2;
:If[Not[AltoMode]]; ******** PrincOps version ********
*-----------------------------------------------------------
ResetEtherM: MiscTable[247], * Reset Ethernet hardware and tasks
*-----------------------------------------------------------
StkP-1, Branch[ResetEther];
:EndIf; **********************************
*-----------------------------------------------------------
GetMemConfM: MiscTable[251], * Get memory configuration
* Push[realPages]; Push[virtualBanks];
*-----------------------------------------------------------
RBase← RBase[RealPages];
T← RealPages;
Stack&+1← T;
T← VirtualBanks, Branch[PushT];
*-----------------------------------------------------------
HaltM: MiscTable[252], * Halt Dorado, leaving specified value on BMux.
* This is useful primarily for power-off: BMux contains time until power-on.
* BMux← Pop; Halt[];
*-----------------------------------------------------------
StkP-1, TaskingOff;
B← StackNoUfl, Breakpoint, Branch[.];
*-----------------------------------------------------------
SetDisplayFieldRateM: MiscTable[253],
* visibleLines ← Pop[]; topBorder ← Pop[]; verticalSync ← Pop[];
* visibleLines is total number of visible lines, including both borders.
* All counts are number of scan lines in the even field.
*-----------------------------------------------------------
StkP-1, Branch[SetDisplayFieldRate];
:If[Not[AltoMode]]; ******** PrincOps version ********
*-----------------------------------------------------------
ResetDiskM: MiscTable[254], * Reset disk hardware and tasks
*-----------------------------------------------------------
StkP-1, Branch[ResetDisk];
:EndIf; **********************************
* Stable Storage block I/O instructions (using GenIn/Out)
KnowRBase[RTemp0];
MC[ssbInput, 100000];
MC[ssbClock, 40000];
MC[ssbPowerOn, 10000];
*-----------------------------------------------------------
SSBlockIn: MiscTable[256], * Stable Storage block input
* DO
* c: CARDINAL ← Pop[]; p: LONG POINTER ← PopLong[];
* IF c=0 THEN EXIT;
* GenOut[ssbInput+ssbPowerOn+ssbClock]; GenOut[ssbInput+ssbPowerOn];
* Store[p]↑ ← GenIn[];
* PushLong[p+1]; Push[c-1];
* IF InterruptPending[] THEN {PC ← savedPC; EXIT};
* ENDLOOP;
*-----------------------------------------------------------
RTemp0← A0;
RTemp1← Or[ssbInput!, ssbClock!, ssbPowerOn!]C;
RTemp2← Or[ssbInput!, ssbPowerOn!]C, Call[SSBBlockSetup];
* Control returns here for each munch (or partial munch) to process
EventCntB← RTemp1; * Send ssbClock=1 for first word
EventCntB← RTemp2; * Send ssbClock=0 for first word
Nop; * 1-cycle delay required before reading
Nop;
Nop;
Nop;
Nop;
* The inner loop reads one word from the SSB, issues the clock for the next word,
* and exits one word early. This is to ensure adequate delay between sending
* the clock and reading the data.
SSBInWordLoop:
T← NOT (EventCntA'), * Read current word
Branch[SSBInWordExit, Cnt=0&-1]; * Exit if this is the last word
EventCntB← RTemp1; * Send ssbClock=1 for next word
EventCntB← RTemp2; * Send ssbClock=0 for next word
Nop;
Nop;
Nop;
Nop;
RTemp0← (Store← RTemp0)+1, DBuf← T, * Store current word
Branch[SSBInWordLoop];
SSBInWordExit:
RTemp0← (Store← RTemp0)+1, DBuf← T, Branch[SSBNextMunch];
*-----------------------------------------------------------
SSBlockOut: MiscTable[257], * Stable Storage block output
* DO
* c: CARDINAL ← Pop[]; p: LONG POINTER ← PopLong[]; word: CARDINAL;
* IF c=0 THEN EXIT;
* word ← Fetch[p]↑;
* GenOut[BITOR[word, ssbClock]]; GenOut[word];
* PushLong[p+1]; Push[c-1];
* IF InterruptPending[] THEN {PC ← savedPC; EXIT};
* ENDLOOP;
*-----------------------------------------------------------
RTemp0← A0;
RTemp1← NOT (ssbClock);
T← 7000C; * ShC← [SHA=R, SHB=R, count=16, LMask=1, RMask=16]
T← T OR (341C);
ShC← T, Call[SSBBlockSetup];
* Control returns here for each munch (or partial munch) to process
RTemp0← (Fetch← RTemp0)+1; * Fetch first word of munch
T← SHMDBothMasks[RTemp1], * Pick up first word and set ssbClock=1
DblBranch[SSBOutWordLoop, SSBOutWordExit, Cnt#0&-1];
* The inner loop fetches one word ahead and exits one word early.
* T contains next word to be sent, OR'ed with ssbClock.
SSBOutWordLoop:
RTemp0← (Fetch← RTemp0)+1; * Fetch next word
T← (RTemp1) AND (EventCntB← T); * Send current word with ssbClock=1, and
* set ssbClock to zero
T← SHMDBothMasks[RTemp1], * Pick up next word and set ssbClock=1
EventCntB← T, * Send current word with ssbClock=0
DblBranch[SSBOutWordLoop, SSBOutWordExit, Cnt#0&-1];
SSBOutWordExit:
T← (RTemp1) AND (EventCntB← T); * Send final word with ssbClock=1
EventCntB← T, Branch[SSBNextMunch]; * Send final word with ssbClock=0
*-----------------------------------------------------------
SSBBlockSetup:
* Enter: RTemp0 = 0
* STK[StkP-1] = word count
* STK[StkP-2],,STK[StkP-3] = base pointer
* Exit: Exits opcode if no (more) words to transfer or interrupt pending; otherwise:
* MemBase = LPtr, containing base pointer
* Cnt = number of words to transfer this iteration -1
* Pointer and count updated on stack to account for that number of words
* First and last words touched to ensure no faults
* Caller should transfer (Cnt)+1 words and increment RTemp0 by that amount,
* then branch to SSBNextMunch. Control will return at the instruction after
* the initial call to SSBBlockSetup with parameters set up for another munch.
* Clobbers T, Q; uses RTemp6 for return link, which must not be clobbered by caller.
*-----------------------------------------------------------
Subroutine;
RTemp6← Link;
TopLevel;
MemBase← LPtr, StkP-2;
BRHi← Stack&-1; * Pop the long pointer into LPtr
BRLo← Stack&+2;
* Come here once per munch. RTemp0 contains LPtr-relative address.
* StkP addresses c (count of words remaining).
* On the first iteration, process ((c-1) mod 20b) +1 words; on subsequent
* iterations, process 20b words. Note that on subsequent iterations,
* c mod 20b = 0, so ((c-1) mod 20b) +1 = 20b.
SSBNextMunch:
T← (Stack)-1; * A-1 generates carry iff A#0
T← T AND (17C), Branch[SSBDone, Carry'];
* Touch the first and last words to be processed in this block,
* and issue a PreFetch for the next block. (Reason for touching words is
* to ensure that a fault won't occur in the middle of the word loop
* while we are wiggling the GenOut clock and such.) T = word count -1.
T← (Fetch← RTemp0)+(Q← T), Branch[SSBInterrupt, Reschedule];
T← (Fetch← T)+(20C);
PreFetch← T, T← MD;
* All possible faults have happened by this point.
Stack&-2← (Stack&-2)-(Cnt← Q)-1; * Update word count
Stack&+1← (Stack&+1)+Q+1; * Update long pointer on stack
Link← RTemp6;
Subroutine;
Stack&+1← A← Stack&+1, XorSavedCarry, Return;
TopLevel;
SSBDone:
StkP-3, IFUNext0;
SSBInterrupt:
Branch[BLTInterrupt];