RC/RC3600/RC3803 Extended Instructions

Fra DDHFwiki
< RC
Spring til navigation Spring til søgning


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:

RC3600 PLINK Mikrokode.png

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.