RC/RC3600/RC3803 Extended Instructions
RC3803 CPU'en, også kendt som CPU720 og CPU721, er udstyret med en række mikroprogrammerede "makroinstruktioner" med særligt fokus på at få MUSIL programmer til at køre hurtigere.
Instruktionerne er beskrevet overfladisk og delvist forkert bagerst i RC 3803 CPU Programmer s Reference Manual så en korrekt, eller i det mindste fungerende implementering, kræver at man også konsulterer TEST OF INSTRUCTION SET FOR CPU 720 og CPU720 MICROPROGRAM FLOWCHARTS.
Instruktioner der arbejder på store datamængder, f.eks "WMOVE", er konstrueret så de udfører en operation og derefter udfører noget der minder om et jump til sig selv, hvis de endnu ikke er færdige, hvilket tillader interrupts og DMA transfers at komme til uden katastrofal latency.
Men det betyder også, at instruktionerne er nødt til at holde hele deres tilstand i de fire akkumulatorer og carry, for kun de overlever intakt, hvis der kommer et interrupt.
For de fleste instruktioner er det temmelig trivielt, f.eks kan "WMOVE" implementers som følger:
if (acc[0]) { u = core_read(acc[1]); core_write(acc[2], u); acc[1]++; acc[2]++; acc[0]--; next_pc = pc; } else { next_pc = pc + 1; }
PLINK Instruktionen
I RC3803 Programmer's Reference Manual beskrives PLINK instruktionen således:
PLINK ┌──────┬─────┬───────┬─────┬─────────────┐ │0 1 1 │ X X │ 1 1 0 │ 1 0 │ 0 0 0 0 1 0 │ └─┼─┴──┴──┼──┴──┴─┼──┴──┴──┼──┴─┴─┼─┴─┴──┘ X = DON'T CARE CALL: RETURN: ; AC0 - DESTROYED ; AC1 - QUEUE HEAD ; AC2 PROCESS PROCESS ; AC3 - QUEUE HEAD [Prosa Beskrivelse Udeladt] The instruction may be interrupted by interrupt and data channel request follwing the algorithme: ; PLINK: START: word(PROC.state) := 0 ; Proc state := running priority := word(Proc.prior) ; AC0 := proc.priority HEAD := word (54₈) ; HEAD := running queue head element := HEAD ; AC3 := HEAD LOOP: element := word(element.next) ; AC3 := next element Q := word(element.prior) ; AC1 := priority of next if Q < priority then goto EXIT TEST: If (INT REQ or DMA REQ) = 0 then goto LOOP WAIT: Servereq(PC) ; Dcr. PC and servereq Fetchnext(PC) ; Incr. PC and exec instr EXIT: predecessor := word(element.prev) ; Update queue word(element.prev) := proc word(proc.next) := element ; insert element word(proc.prev) := predecessor word(predecessor.next := proc Fetchnext(PC) ; Incr. PC and exec.instr.
Problemet opstår i "Fetchnext" i anden linie af "WAIT": Hvordan kan den vide om den skal udføre "START" eller om den skal hoppe direkte til "LOOP" ?
Svaret finder man i mikrokode flowchart for denne instruktion:
Hvis AC1 er forskellig fra nul, udføres START via mikrokodetrin 316|1, 321|1 osv. mens hvis AC1 er nul udføres LOOP via trin 317|1, 327|1 osv.
Man må med andre ord ikke kalde denne instruktion med AC1=0, hvis den skal virke som beskrevet i manualen.
Hvor mange interrupts kan der være i PLINK ?
Men der er fejl mere i pseudo-algol beskrivelsen og den er endnu mere obskur: Hvor mange interrupts kan der blive plads til under udførslen af instruktionen?
Pseudo-algol beskrivelsen fortsætter fra START til LOOP, mens mikrokoden går fra START til WAIT (= "DCHR" valget) hvilket tillader ét interrupt mere at afbryde PLINK instruktionen, end pseudo-algol koden ville gøre.
Hvis denne opskure detalje ikke er implementeret korrekt, giver testprogrammet følgende fejl:
CPU 720 EXT TEST 000400 STARTADDR 000024 000025 022221 AC0 AC1 AC2 PC 020120 SWITCH 12 CONTINUE (Y/N): Y ?
Kigger man i dokumentationen til testprogrammet, hvilket bare er en assembler-liste med masser af gode kommentarer finder man:
28 20115 020144 LDA 0,NTRLF ; CHECK NO OF INTRS 29 20116 025145 LDA 1,NTREX 30 20117 106414 SUB# 0,1,SZR 31 20120 006114 EHALT ; AC0=NO OF INTRS 32 ; AC1=EXPECTED 33 20121 006113 LOOP ; *******
Godt så, men hvordan pokker tæller programmet hvor mange interrupts der er plads til "inde i" en instruktion ?
Data Generals Nova arkitektur har ingen stack og følgelig heller ikke nogen "Return from Interrupt" instruktion.
Interrupts virker ved at program tælleren lagres i lokation nul og der udføres et jump til lokation 1.
Derfor vil en normal interrupt handler være nødt til at slutte med:
... INTEN JMP @0,0
Hvis INTEN instruktionen tillod interrupts med det samme, ville et allerede ventende interrupt overskrive lokation nul og dermed ville returaddressen fra det oprindelige interrupt gå tabt.
Derfor startes instruktionen efter INTEN før interupt enable flaget sættes.
Testprogrammet "misbruger" denne detalje på følgende snedige vis:
Først disables interrupt.
En I/O enhed bringes til at interrupte, i dette tilfælde bruges Real-Time-Clock controlleren, men enhver anden I/O enhed kunne bruges.
Herefter udføres instruktionerne:
... INTEN PLINK ...
Ved først givne lejlighed efter starten af PLINK instruktionen bliver interrupt accepteret, en tæller tælles op og øvelsen gentages, indtil PLINK instruktionen er færdig, hvilket kan ses ved at undersøge lokation nul i interrupt rutinen.
Hat-tip til 'LAB' og 'JEP' for det trick!
Hvor meget hjælper instruktionerne ?
Det har krævet en ikke ubetydelig indsats at implementere disse "makro" instruktioner i mikrokoden, men stod indsatsen mål med gevinsten ?
Instruktionerne gør som udgangspunkt præcist det samme som man ellers ville have kodet med rigtige instruktioner, det er det samme antal ord WMOVE skal flytte og det samme antal processer PLINK skal søge forbi og derfor er den primære besparelse at man undgår at hente selve instruktionerne fra hukommelsen.
For et kald til 'LINK PROCESS' "system-kaldet" i MUM kan det gøre denne forskel:
JSR @ +6e,0 JSR @ +6e,0 STA 3,+00,0 STA 3,+00,0 SUB 1,1 SUBZL 1,1 STA 1,+0b,2 PLINK 0 LDA 3,+2c,0 PLINK 0 LDA 0,+0d,2 PLINK 0 LDA 3,+00,3 LDA 1,+0d,3 SUBZ # 0,1,SZC JMP -03,1 LDA 3,+00,3 LDA 1,+0d,3 SUBZ # 0,1,SZC JMP +03,1 LDA 0,+01,3 STA 2,+01,3 STA 3,+00,2 STA 0,+01,2 STA @ 2,+01,2 JMP @ +00,0 JMP @ +00,0
I en mere realistisk situation tager kommandoen "CATLI MU$$$" 674241 instruktioner uden og kun 434501 med anvendelse af makroinstruktionerne.
Det er til at tage og føle på: 35½% færre instruktioner.