; ****************************************************************************************** ; Programme: NEWMD.TXT version PEP813 sous Windows ; ; Programme modifie l'installation du système d'exploitation de PEP813. ; ; Les instructions NOP0, NOP1, NOP2, NOP3 et NOP sont remplacées. ; NOP0 - affiche le message "Bonjour". (nom suggéré: BONJOUR) ; NOP1 - affiche le message "Fin normale du programme." (nom suggéré: FINAL) ; NOP2 - effectue la multiplication (nom suggéré: MULT) ; NOP3 - effectue la division (nom suggéré: DIV) ; NOP - affiche la lettre de l'alphabet à la position demandée. (nom suggéré: LETTRE) ; ; L'instruction DECI ne plante plus. Elle émet un message d'erreur lors d'une ; donnée non numérique et redemande une nouvelle entrée à l'utilisateur. ; ; auteur: Bernard Martin ; code permanent: non applicable ; courriel: martin.bernard@uqam.ca ; date: été 2020 ; cours: INF2171 ; ; Remarque: Pour l'installation, on doit utiliser l'option "Assemble/Install New OS" ; sous l'onglet "System" de Pep/8. ; Cette installation est temporaire et doit être refaite après l'appel à PEP/8. ; Pour tracer une interruption avec le débogueur, il faut cocher la case "Trace Traps". ; ******************************************************************************************* ; Provient de l'option "Pep/8 Operating System" sous l'onglet "Help" de PEP/8 puis choisir ; la fonction "Copy to Source". ; ;******* Pep/8 Operating System, 2004/08/30 ; TRUE: .EQUATE 1 FALSE: .EQUATE 0 ; ;******* Operating system RAM osRAM: .BLOCK 128 ;System stack area wordBuff:.BLOCK 1 ;Input/output buffer byteBuff:.BLOCK 1 ;Least significant byte of wordBuff wordTemp:.BLOCK 1 ;Temporary word storage byteTemp:.BLOCK 1 ;Least significant byte of tempWord addrMask:.BLOCK 2 ;Addressing mode mask opAddr: .BLOCK 2 ;Trap instruction operand address ; ;******* Operating system ROM .BURN 0xFFFF ; ;******* System Loader ;Data must be in the following format: ;Each hex number representing a byte must contain exactly two ;characters. Each character must be in 0..9, A..F, or a..f and ;must be followed by exactly one space. There must be no ;leading spaces at the beginning of a line and no trailing ;spaces at the end of a line. The last two characters in the ;file must be lowercase zz, which is used as the terminating ;sentinel by the loader. ; loader: LDX 0,i ;X := 0 STX wordBuff,d ;Clear input buffer word ; getChar: CHARI byteBuff,d ;Get first hex character LDA wordBuff,d ;Put ASCII into low byte of A CPA 'z',i ;If end of file sentinel 'z' BREQ stopLoad ;then exit loader routine CPA '9',i ;If characer <= '9', assume decimal BRLE shift ;and right nybble is correct digit ADDA 9,i ;else convert nybble to correct digit shift: ASLA ;Shift left by four bits to send ASLA ;the digit to the most significant ASLA ;position in the byte ASLA STBYTEA byteTemp,d ;Save the most significant nybble CHARI byteBuff,d ;Get second hex character LDA wordBuff,d ;Put ASCII into low byte of A CPA '9',i ;If characer <= '9', assume decimal BRLE combine ;and right nybble is correct digit ADDA 9,i ;else convert nybble to correct digit combine: ANDA 0x000F,i ;Mask out the left nybble ORA wordTemp,d ;Combine both hex digits in binary STBYTEA 0,x ;Store in Mem[X] ADDX 1,i ;X := X + 1 CHARI byteBuff,d ;Skip blank or BR getChar ; ; stopLoad:STOP ; ; ;******* Trap handler oldIR: .EQUATE 9 ;Stack address of IR on trap ; trap: LDX 0,i ;Clear X for a byte compare LDBYTEX oldIR,s ;X := trapped IR CPX 0x0028,i ;If X >= first nonunary trap opcode BRGE nonUnary ;trap opcode is nonunary ; unary: ANDX 0x0003,i ;Mask out all but rightmost two bits ASLX ;An address is two bytes CALL unaryJT,x ;Call unary trap routine RETTR ;Return from trap ; unaryJT: .ADDRSS opcode24 ;Address of NOP0 subroutine .ADDRSS opcode25 ;Address of NOP1 subroutine .ADDRSS opcode26 ;Address of NOP2 subroutine .ADDRSS opcode27 ;Address of NOP3 subroutine ; nonUnary:ASRX ;Trap opcode is nonunary ASRX ;Discard addressing mode bits ASRX SUBX 5,i ;Adjust so that NOP opcode = 0 ASLX ;An address is two bytes CALL nonUnJT,x ;Call nonunary trap routine return: RETTR ;Return from trap ; nonUnJT: .ADDRSS opcode28 ;Address of NOP subroutine .ADDRSS opcode30 ;Address of DECI subroutine .ADDRSS opcode38 ;Address of DECO subroutine .ADDRSS opcode40 ;Address of STRO subroutine ; ;******* Assert valid trap addressing mode oldIR4: .EQUATE 13 ;oldIR + 4 with two return addresses assertAd:LDA 1,i ;A := 1 LDBYTEX oldIR4,s ;X := OldIR ANDX 0x0007,i ;Keep only the addressing mode bits BREQ testAd ;000 = immediate addressing loop: ASLA ;Shift the 1 bit left SUBX 1,i ;Subtract from addressing mode count BRNE loop ;Try next addressing mode testAd: ANDA addrMask,d ;AND the 1 bit with legal modes BREQ addrErr RET0 ;Legal addressing mode, return addrErr: CHARO '\n',i LDA trapMsg,i ;Push address of error message STA -2,s SUBSP 2,i ;Call print subroutine CALL prntMsg STOP ;Halt: Fatal runtime error trapMsg: .ASCII "ERROR: Invalid trap addressing mode.\x00" ; ;******* Set address of trap operand oldX4: .EQUATE 7 ;oldX + 4 with two return addresses oldPC4: .EQUATE 9 ;oldPC + 4 with two return addresses oldSP4: .EQUATE 11 ;oldSP + 4 with two return addresses setAddr: LDBYTEX oldIR4,s ;X := old instruction register ANDX 0x0007,i ;Keep only the addressing mode bits ASLX ;An address is two bytes BR addrJT,x addrJT: .ADDRSS addrI ;Immediate addressing .ADDRSS addrD ;Direct addressing .ADDRSS addrN ;Indirect addressing .ADDRSS addrS ;Stack relative addressing .ADDRSS addrSF ;Stack relative deferred addressing .ADDRSS addrX ;Indexed addressing .ADDRSS addrSX ;Stack indexed addressing .ADDRSS addrSXF ;Stack indexed deferred addressing ; addrI: LDX oldPC4,s ;Immediate addressing SUBX 2,i ;Oprnd = OprndsSpec STX opAddr,d RET0 ; addrD: LDX oldPC4,s ;Direct addressing SUBX 2,i ;Oprnd = Mem[OprndSpec] LDX 0,x STX opAddr,d RET0 ; addrN: LDX oldPC4,s ;Indirect addressing SUBX 2,i ;Oprnd = Mem[Mem[OprndSpec]] LDX 0,x LDX 0,x STX opAddr,d RET0 ; addrS: LDX oldPC4,s ;Stack relative addressing SUBX 2,i ;Oprnd = Mem[SP + OprndSpec] LDX 0,x ADDX oldSP4,s STX opAddr,d RET0 ; addrSF: LDX oldPC4,s ;Stack relative deferred addressing SUBX 2,i ;Oprnd = Mem[Mem[SP + OprndSpec]] LDX 0,x ADDX oldSP4,s LDX 0,x STX opAddr,d RET0 ; addrX: LDX oldPC4,s ;Indexed addressing SUBX 2,i ;Oprnd = Mem[OprndSpec + X] LDX 0,x ADDX oldX4,s STX opAddr,d RET0 ; addrSX: LDX oldPC4,s ;Stack indexed addressing SUBX 2,i ;Oprnd = Mem[SP + OprndSpec + X] LDX 0,x ADDX oldX4,s ADDX oldSP4,s STX opAddr,d RET0 ; addrSXF: LDX oldPC4,s ;Stack indexed deferred addressing SUBX 2,i ;Oprnd = Mem[Mem[SP + OprndSpec] + X] LDX 0,x ADDX oldSP4,s LDX 0,x ADDX oldX4,s STX opAddr,d RET0 ; ;******* Opcode 0x24 ;The NOP0 instruction. Affiche "Bonjour ". (nom suggéré: BONJOUR) opcode24:CHARO "B",i ; <------------------------ CHARO "o",i ; <------------------------ CHARO "n",i ; <------------------------ CHARO "j",i ; <------------------------ CHARO "o",i ; <------------------------ CHARO "u",i ; <------------------------ CHARO "r",i ; <------------------------ CHARO " ",i ; <------------------------ RET0 ; ;******* Opcode 0x25 ;The NOP1 instruction. Affiche le message de terminaison. (nom suggéré: FINAL) ; opcode25:LDA code25ms,i ;Push address of string to print <------------------------ STA -2,s ; <------------------------ SUBSP 2,i ; <------------------------ ; ;on ne peut pas utiliser STRO ; ;car STRO est déjà une interruption CALL prntMsg ;and print ADDSP 2,i ; <------------------------ RET0 ; <------------------------ code25ms: .ASCII "\n\nFin normale du programme.\x00" ; <------------------------ ; ;******* Opcode 0x26 ;The NOP2 instruction --> MULT (effectue la multiplication) ;MULT: calcule le produit du multiplicande par le multiplicateur (avec les signes) ; opcode26:LDA 3,s ; contenu du registre A ; <------------------------ LDX 5,s ; contenu du registre X ; <------------------------ ; ;MULT: calcule le produit du multiplicande par le multiplicateur ; ; traitement spécial pour la multiplication par 10 ; ; si le multiplicande est négatif, on inverse son signe ; si le multiplicateur est négatif, on inverse son signe ; ; pour minimiser le nombre de boucles, le multiplicande doit être >= ; au multiplicateur sinon on les inverse. ; ; Les débordements sont traités. code d'erreur: X=-1 et A=0 ; ; Passage des arguments et résultats par registres ; ; IN: A=multiplicande ; X=multiplicateur ; OUT: A=produit ; X=0 ; X=-1 si débordement ; mucande: .EQUATE 0 ; <------------------------ mucateur:.EQUATE 2 ; <------------------------ signe: .EQUATE 4 ; <------------------------ MULTIP: SUBSP 6,i ; empilons le multiplicande et le multiplicateur #signe #mucateur #mucande STA mucande,s ; sauvons le multiplicande LDA 1,i STA signe,s ; signe positif LDA mucande,s CPX 0,i BREQ zéroprod ; on n'a pas à multiplier pour le nombre 0 BRGE positi NEGX ; rendre le multiplicateur positif LDA -1,i ; on retient le signe pour le produit final STA signe,s LDA mucande,s ; ; traitement rapide pour une multiplication par 10 ; positi: CPX 10,i BRNE pasdix ASLA ; * 2 BRV multdéb ASLA ; * 4 BRV multdéb ADDA mucande,s ; * 5 BRV multdéb ASLA ; * 10 BRV multdéb BR finmult ; ; multiplication normale ; ; multiplions le plus grand nombre par le plus petit nombre ; afin de minimiser le nombre de boucles ; pasdix: CPA 0,i ; multiplicande BRGE compar NEGA ; rendre le multiplicande positif STA mucande,s LDA signe,s ; on retient le signe pour le produit final NEGA STA signe,s LDA mucande,s compar: STX mucateur,s ; sauvons le multiplicateur CPA mucateur,s ; si le multiplicande est >= au multiplicateur BRGE correct ; oui, le nombre de boucles est minimisé LDX mucande,s ; non, on échange le multiplicande avec le LDA mucateur,s ; multiplicateur STA mucande,s correct: LDA 0,i ; Initialiser l'accumulateur additio: ADDA mucande,s BRV multdéb SUBX 1,i BRNE additio ; Faire un autre tour si non nul finmult: LDX signe,s ; a-t-on un signe négatif ? BRGE finim NEGA ; rendre négatif BR finim ; ; débordement survenu: X=FFFF et A=0000 ; multdéb: LDX -1,i ; code d'erreur LDA 0,i BR finimm zéroprod:LDA 0,i ; multiplication par zéro => 0 finim: LDX 0,i ; X=0000 finimm: STA 9,s ; <------------------------ STX 11,s ; <------------------------ RET6 ; désempilons le multiplicande et le multiplicateur #signe #mucateur #mucande ; ;******* Opcode 0x27 ;The NOP3 instruction. --> DIV (effectue la division avec les signes) opcode27:LDA 3,s ; contenu du registre A <------------------------ LDX 5,s ; contenu du registre X <------------------------ ; DIV: calcule le quotient et le reste d'une division ; Passage des arguments et résultats par la pile ; ; IN: A=dividende ; X=diviseur ; OUT: A=reste ; X=quotient ; signeseu:.EQUATE 0 ; signe du diviseur signeden:.EQUATE 2 ; signe du dividende diviseu: .EQUATE 4 ; diviseur dividen: .EQUATE 6 ; dividende quotie: .EQUATE 8 ; quotient rest: .EQUATE 10 ; reste ; DIV: SUBSP 12,i ; STA dividen,s STX diviseu,s LDA 1,i STA signeseu,s ; 1=positif -1=négatif STA signeden,s LDX diviseu,s ; BREQ zérocalc ; division par 0 tolérée retourne 0 comme quotient et reste BRGT positif1 NEGX ; diviseur négatif -> positif STX diviseu,s LDA -1,i ; on conserve son signe STA signeseu,s positif1:LDA dividen,s BREQ zérocalc ; dividende 0 retourne 0 comme quotient et reste BRGT positif2 NEGA ; dividende négatif -> positif LDX -1,i ;on conserve son signe STX signeden,s positif2:LDX 0,i ; initialisation du quotient soustrai:ADDX 1,i SUBA diviseu,s BRGE soustrai ; soustraction répétitive ; ; à ce stade, nous avons soustrait une fois de trop SUBX 1,i ; redéfaisons la dernière soustraction ADDA diviseu,s BR retdiv ; zérocalc:LDA 0,i ; reste = 0 LDX 0,i ; quotient = 0 retdiv: STX quotie,s STA rest,s ; ; 5 / 2 -> 2 reste 1 ; 5 / -2 -> -2 reste 1 ; -5 / 2 -> -2 reste -1 ; -5 / -2 -> 2 reste -1 ; LDX signeden,s ; si le signe du dividende BRGT positif3 ; est positif NEGA ; sinon, le reste sera négatif STA rest,s LDA signeseu,s ; si le signe du diviseur est aussi négatif BRLT positif4 ; le quotient sera positif BR quotneg ; sinon le quotient sera négatif positif3:LDA signeseu,s ; si le signe du diviseur BRGT positif4 ; est positif quotneg: LDX quotie,s ; sinon le quotient sera négatif NEGX STX quotie,s positif4:LDX quotie,s STX 17,s ; quotient <------------------------ LDA rest,s STA 15,s ; reste <------------------------ ADDSP 12,i ; <------------------------ RET0 ; ; ;******* Opcode 0x28 ;The NOP instruction. Affiche la lettre de l'alphabet à la position demandée. (nom suggéré: LETTRE) ; opcode28:LDA 0x0001,i ;Assert i <------------------------ STA addrMask,d ; <------------------------ CALL setAddr ;Set address of trap operand <------------------------ SUBSP 6,i ;Allocate storage for locals <------------------------ LDX opAddr,n ;A := oprnd <------------------------ CHARO alphanop,x ;affiche le caractère à la position demandée dans l'alphabet <------------------------ RET6 ; <------------------------ alphanop: .ASCII " ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; <------------------------ ; ;******* Opcode 0x30 ;The DECI instruction. Modifié pour redemander si l'entrée n'est pas numérique. <----------------------- ;Input format: Any number of leading spaces or line feeds are ;allowed, followed by '+', '-' or a digit as the first character, ;after which digits are input until the first nondigit is ;encountered. The status flags N,Z and V are set appropriately ;by this DECI routine. The C status flag is not affected. ; oldNZVC: .EQUATE 14 ;Stack address of NZVC on interrupt ; total: .EQUATE 10 ;Cumulative total of DECI number valAscii:.EQUATE 8 ;Value(asciiCH) isOvfl: .EQUATE 6 ;Overflow boolean isNeg: .EQUATE 4 ;Negative boolean state: .EQUATE 2 ;State variable temp: .EQUATE 0 ; init: .EQUATE 0 ;Enumerated values for state sign: .EQUATE 1 digit: .EQUATE 2 ; opcode30:LDA 0x00FE,i ;Assert d, n, s, sf, x, sx, sxf STA addrMask,d CALL assertAd CALL setAddr ;Set address of trap operand SUBSP 12,i ;Allocate storage for locals LDA FALSE,i ;isOvfl := FALSE STA isOvfl,s LDA init,i ;state := init STA state,s LDA 0,i ;wordBuff := 0 for input STA wordBuff,d ; do: CHARI byteBuff,d ;Get asciiCh LDA wordBuff,d ;Set value(asciiCH) ANDA 0x000F,i STA valAscii,s LDA wordBuff,d ;A = asciiCh throughout the loop LDX state,s ;switch (state) ASLX ;An address is two bytes BR stateJT,x ; stateJT: .ADDRSS sInit .ADDRSS sSign .ADDRSS sDigit ; sInit: CPA '+',i ;if (asciiCh == '+') BRNE ifMinus LDX FALSE,i ;isNeg := FALSE STX isNeg,s LDX sign,i ;state := sign STX state,s BR do ; ifMinus: CPA '-',i ;else if (asciiCh == '-') BRNE ifDigit LDX TRUE,i ;isNeg := TRUE STX isNeg,s LDX sign,i ;state := sign STX state,s BR do ; ifDigit: CPA '0',i ;else if (asciiCh is a digit) BRLT ifWhite CPA '9',i BRGT ifWhite LDX FALSE,i ;isNeg := FALSE STX isNeg,s LDX valAscii,s ;total := Value(asciiCh) STX total,s LDX digit,i ;state := digit STX state,s BR do ; ifWhite: CPA ' ',i ;else if (asciiCh is not a space BREQ do CPA '\n',i ;or line feed) BRNE deciErr ;exit with DECI error BR do ; sSign: CPA '0',i ;if asciiCh (is not a digit) BRLT deciErr CPA '9',i BRGT deciErr ;exit with DECI error LDX valAscii,s ;else total := Value(asciiCh) STX total,s LDX digit,i ;state := digit STX state,s BR do ; sDigit: CPA '0',i ;if (asciiCh is not a digit) BRLT deciNorm CPA '9',i BRGT deciNorm ;exit normaly LDX TRUE,i ;else X := TRUE for later assignments LDA total,s ;Multiply total by 10 as follows: ASLA ;First, times 2 BRV ovfl1 ;If overflow then BR L1 ovfl1: STX isOvfl,s ;isOvfl := TRUE L1: STA temp,s ;Save 2 * total in temp ASLA ;Now, 4 * total BRV ovfl2 ;If overflow then BR L2 ovfl2: STX isOvfl,s ;isOvfl := TRUE L2: ASLA ;Now, 8 * total BRV ovfl3 ;If overflow then BR L3 ovfl3: STX isOvfl,s ;isOvfl := TRUE L3: ADDA temp,s ;Finally, 8 * total + 2 * total BRV ovfl4 ;If overflow then BR L4 ovfl4: STX isOvfl,s ;isOvfl := TRUE L4: ADDA valAscii,s ;A := 10 * total + valAscii BRV ovfl5 ;If overflow then BR L5 ovfl5: STX isOvfl,s ;isOvfl := TRUE L5: STA total,s ;Update total BR do ; deciNorm:LDA isNeg,s ;If isNeg then BREQ setNZ LDA total,s ;If total != 0x8000 then CPA 0x8000,i BREQ L6 NEGA ;Negate total STA total,s BR setNZ L6: LDA FALSE,i ;else -32768 is a special case STA isOvfl,s ;isOvfl := FALSE ; setNZ: LDBYTEX oldNZVC,s ;Set NZ according to total result: ANDX 0x0001,i ;First initialize NZV to 000 LDA total,s ;If total is negative then BRGE checkZ ORX 0x0008,i ;set N to 1 checkZ: CPA 0,i ;If total is not zero then BRNE setV ORX 0x0004,i ;set Z to 1 setV: LDA isOvfl,s ;If not isOvfl then BREQ storeFl ORX 0x0002,i ;set V to 1 storeFl: STBYTEX oldNZVC,s ;Store the NZVC flags ; exitDeci:LDA total,s ;Put total in memory STA opAddr,n ADDSP 12,i ;Deallocate locals RET0 ;Return to trap handler ; deciErr: CHARO '\n',i LDA deciMsg,i ;Push address of message onto stack STA -2,s SUBSP 2,i CALL prntMsg ;and print BR exitDeci ;recommencera si non numérique <------------------------ ; deciMsg: .ASCII "Ce nombre n'est pas numérique,\nveuillez le réentrer svp: \x00"; <------------------------ ; ;******* Opcode 0x38 ;The DECO instruction. ;Output format: If the operand is negative, the algorithm prints ;a single '-' followed by the magnitude. Otherwise it prints the ;magnitude without a leading '+'. It suppresses leading zeros. ; remain: .EQUATE 0 ;Remainder of value to output chOut: .EQUATE 2 ;Has a character been output yet? place: .EQUATE 4 ;Place value for division ; opcode38:LDA 0x00FF,i ;Assert i, d, n, s, sf, x, sx, sxf STA addrMask,d CALL assertAd CALL setAddr ;Set address of trap operand SUBSP 6,i ;Allocate storage for locals LDA opAddr,n ;A := oprnd CPA 0,i ;If oprnd is negative then BRGE printMag CHARO '-',i ;Print leading '-' and NEGA ;make magnitude positive printMag:STA remain,s ;remain := abs(oprnd) LDA FALSE,i ;Initialize chOut := FALSE STA chOut,s LDA 10000,i ;place := 10,000 STA place,s CALL divide ;Write 10,000's place LDA 1000,i ;place := 1,000 STA place,s CALL divide ;Write 1000's place LDA 100,i ;place := 100 STA place,s CALL divide ;Write 100's place LDA 10,i ;place := 10 STA place,s CALL divide ;Write 10's place LDA remain,s ;Always write 1's place ORA 0x0030,i ;Convert decimal to ASCII STBYTEA byteBuff,d CHARO byteBuff,d RET6 ; ;Subroutine to print the most significant decimal digit of the ;remainder. It assumes that place (place2 here) contains the ;decimal place value. It updates the remainder. ; remain2: .EQUATE 2 ;Stack addresses while executing a chOut2: .EQUATE 4 ;subroutine are greater by two because place2: .EQUATE 6 ;the retAddr is on the stack ; divide: LDA remain2,s ;A := remainder LDX 0,i ;X := 0 divLoop: SUBA place2,s ;Division by repeated subtraction BRLT writeNum ;If remainder is negative then done ADDX 1,i ;X := X + 1 STA remain2,s ;Store the new remainder BR divLoop ; writeNum:CPX 0,i ;If X != 0 then BREQ checkOut LDA TRUE,i ;chOut := TRUE STA chOut2,s BR printDgt ;and branch to print this digit checkOut:LDA chOut2,s ;else if a previous char was output BRNE printDgt ;then branch to print this zero RET0 ;else return to calling routine ; printDgt:ORX 0x0030,i ;Convert decimal to ASCII STX wordBuff,d ;for output CHARO byteBuff,d RET0 ;return to calling routine ; ;******* Opcode 0x40 ;The STRO instruction. ;Outputs a null-terminated string from memory. ; opcode40:LDA 0x0016,i ;Assert d, n, sf STA addrMask,d CALL assertAd CALL setAddr ;Set address of trap operand LDA opAddr,d ;Push address of string to print STA -2,s SUBSP 2,i CALL prntMsg ;and print ADDSP 2,i RET0 ; ;******* Print subroutine ;Prints a string of ASCII bytes until it encounters a null ;byte (eight zero bits). Assumes one parameter, which ;contains the address of the message. ; msgAddr: .EQUATE 2 ;Address of message to print ; prntMsg: LDX 0,i ;X := 0 LDA 0,i ;A := 0 prntMore:LDBYTEA msgAddr,sxf ;Test next char BREQ exitPrnt ;If null then exit CHARO msgAddr,sxf ;else print ADDX 1,i ;X := X + 1 for next character BR prntMore ; exitPrnt:RET0 ; ;******* Vectors for System Memory Format .ADDRSS osRAM ;User stack pointer .ADDRSS wordBuff ;System stack pointer .ADDRSS loader ;Loader program counter .ADDRSS trap ;Trap program counter ; .END