Processorarchitectuur Samenvatting lesnota’s en Gestructureerde Computerarchitectuur. Processorarchitectuur ............................................................................................................... 1 Samenvatting lesnota’s.............................................................................................................. 1 en ................................................................................................................................................ 1 Gestructureerde Computerarchitectuur. .................................................................................. 1 1 Inleiding ............................................................................................................................. 5 1.1 Gestructureerde computerarchitectuur ______________________________________ 5 1.1.1 1.1.2 1.1.3 2 Talen, niveaus en virtuele machines _______________________________________________ 5 Hedendaagse machines met meerdere niveaus _______________________________________ 5 De evolutie van machines met meerdere niveaus _____________________________________ 6 Organisatie van computersystemen .................................................................................. 7 2.1 Processor _______________________________________________________________ 7 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2 De organisatie van de CPU ______________________________________________________ Het uitvoeren van een instructie __________________________________________________ RISC versus CISC _____________________________________________________________ Ontwerpprincipes voor tegenwoordige computers ____________________________________ Parallellisme op instructieniveau __________________________________________________ Parallellisme op processorniveau _________________________________________________ Primair geheugen _______________________________________________________ 10 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 Bits _______________________________________________________________________ Geheugenadressen ____________________________________________________________ De volgorde van bytes _________________________________________________________ 10 Cachegeheugen ______________________________________________________________ 10 10 10 10 2.3 11 2.4 Input/Output __________________________________________________________ 11 2.4.1 3 7 7 8 8 9 9 Bussen, interrupts ____________________________________________________________ 11 Het niveau van de digitale logica .................................................................................... 13 3.1 13 3.2 13 3.2.1 3.2.2 3.2.3 3.2.4 3.3 13 13 Rekenkundige circuits _________________________________________________________ 13 Klokken ____________________________________________________________________ 13 Geheugen _____________________________________________________________ 13 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4 13 13 13 Geheugenorganisatie __________________________________________________________ Geheugenchips ______________________________________________________________ RAM en ROM _______________________________________________________________ Aansluiten van geheugenchips __________________________________________________ Koppeling tussen microprocessor en randapparaten __________________________________ Adressering van de IO _________________________________________________________ CPU-chips en BUSSEN __________________________________________________ 16 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 CPU chips __________________________________________________________________ Computerbussen _____________________________________________________________ Busbreedte __________________________________________________________________ Het klokken van een bus _______________________________________________________ Busarbitrage ________________________________________________________________ Busoperaties ________________________________________________________________ 16 16 16 17 17 17 3.5 18 3.6 Voorbeelden van bussen _________________________________________________ 18 3.7 Interfaces _____________________________________________________________ 18 3.7.1 3.7.2 4 13 14 14 15 15 15 IO-chips ____________________________________________________________________ 18 Adresdecodering _____________________________________________________________ 18 Het niveau van de microarchitectuur ............................................................................. 19 4.1 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.2.3 4.2.4 4.3 4.3.1 4.3.2 Een voorbeeld van een microarchitectuur ___________________________________ 19 Het datapad _________________________________________________________________ 19 Micro-instructies _____________________________________________________________ 20 Micro-instructie besturing: de MIC-1 _____________________________________________ 21 Een voorbeeld ISA: IJVM ________________________________________________ 22 Stapels _____________________________________________________________________ Het IJVM-geheugenmodel _____________________________________________________ De IJVM instructieset _________________________________________________________ Compileren van Java naar IJVM _________________________________________________ 22 22 23 23 Een voorbeeld van een implementatie ______________________________________ 23 Micro-instructies en hun notatie _________________________________________________ 23 Implementatie van IJVM met de MIC-1 ___________________________________________ 23 4.4 Ontwerp van het microarchitectuurniveau __________________________________ 24 4.4.1 4.4.2 4.4.3 4.4.4 4.4.5 4.5 Snelheid versus kosten ________________________________________________________ Reductie van de lengte van het executiepad ________________________________________ De MIC-2: een ontwerp met prefetching: de MIC-2 __________________________________ Een ontwerp met pipelining: de MIC-3 ____________________________________________ Pipelining in zeven stadia: de MIC-4 _____________________________________________ Verbetering van de prestaties _____________________________________________ 26 4.5.1 4.5.2 4.5.3 4.5.4 5 Cachegeheugen ______________________________________________________________ Sprongvoorspellingen _________________________________________________________ Out-of-order uitvoering en herbenoemen van registers ________________________________ Speculatieve uitvoering ________________________________________________________ 26 29 30 30 Het niveau van de instructiesetarchitectuur................................................................... 31 5.1 31 5.2 31 5.3 Instructieformaten ______________________________________________________ 31 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.4 Ontwerpcriteria voor instructieformaten ___________________________________________ 31 Expanderende opcodes ________________________________________________________ 31 32 32 32 Adressering ____________________________________________________________ 32 5.4.1 5.4.2 5.4.3 5.4.4 5.4.5 5.4.6 5.4.7 5.4.8 5.4.9 5.4.10 6 24 24 25 25 25 Adresseringsmodi ____________________________________________________________ Onmiddellijke adressering ______________________________________________________ Absolute of directe adressering __________________________________________________ Registeradressering ___________________________________________________________ Registerindirecte adressering ___________________________________________________ Geïndexeerde adressering ______________________________________________________ Basisgeïndexeerde adressering __________________________________________________ Relatieve adressering__________________________________________________________ Adresseringsmodi voor sprongopdrachten _________________________________________ Orthogonaliteit van opcodes en adresseringsmodi ___________________________________ 32 32 32 32 32 33 33 33 33 33 Het assembleertaalniveau ............................................................................................... 34 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.2 6.2.1 6.3 Inleiding tot assembleertalen _____________________________________________ 34 Wat is assembleertaal? ________________________________________________________ Waarom assembleertaal gebruiken _______________________________________________ Een statement in assembleertaal _________________________________________________ Pseudo-instructies ____________________________________________________________ 34 34 34 34 Macro’s _______________________________________________________________ 35 Defenitie, aanroep en expansie van macro’s ________________________________________ 35 Het assembleerproces ___________________________________________________ 35 6.3.1 6.3.2 6.3.3 6.4 6.4.1 6.4.2 6.4.3 6.4.4 Two-pass-assemblers _________________________________________________________ 35 Pass 1______________________________________________________________________ 36 Pass 2______________________________________________________________________ 36 Linken en laden ________________________________________________________ 36 Door de linker uitgevoerde taken ________________________________________________ Structuur van een objectmodule _________________________________________________ Bindingstijdstip en dynamische relocatie __________________________________________ Dynamisch linken ____________________________________________________________ 36 37 37 38 1 INLEIDING 1.1 Gestructureerde computerarchitectuur 1.1.1 Talen, niveaus en virtuele machines Vertaling: Elke instructie in een taal T1 wordt vervangen door een equivalente instructie in een taal T0 Interpretatie: Een programma in T0 leest de instructies in taal T1 en voert equivalente instructies uit T1 uit. Onderstel een virtuele machine die taal T1 als machinetaal heeft. Dit is een virtuele machine. Elke taal definieert een machine, en elke machine definieert een taal. Hoe complexer de taal van de machine, hoe moeilijker en duurder het is om ze te bouwen. Een computer kan bestaan uit meerdere niveaus, waarbij elk niveau als een virtuele machine kan worden beschouwd. Iemand die programma’s wil schrijven voor een bepaald niveau, hoeft enkel dat niveau te kennen. 1.1.2 Hedendaagse machines met meerdere niveaus Het microarchitectuur niveau is het niveau boven de digitale logica. Het bestaat uit een collectie van registers en een ALU. De ALU staat in voor het uitvoeren van eenvoudige arithmetische en logische berekeningen. De registers en de ALU zijn met elkaar verbonden door middel van het datapad. De voornaamste taak van het datapad bestaat er in registers te selecteren en de ALU ermee te laten werken. Deze taken worden hetzij door een microprogramma gerealiseerd, hetzij rechtstreeks door hardware. Indien het datapad door software wordt gerealiseerd, is het programma een interpreter voor instructies van het hogere niveau (het instructiesetarchitectuur niveau, ISA-niveau). Het niveau is verbonden met de instructieset van de machine. Het volgende niveau (3) is het besturingssysteem architectuur niveau. Dit is meestal een gemengd niveau. Naast de instructies van het ISA niveau, zijn er ook een aantal nieuwe instructies. Verder is er nog een andere geheugenorganisatie, en de mogelijkheid om programma’s parallel uit te voeren. De faciliteiten van dit niveau worden uitgevoerd door een interpreter die op niveau twee loopt. Deze interpreter is het operating system. Sommige instructie worden rechtstreeks door het microprogramma van het hogere niveau uitgevoerd, andere worden geïnterpreteerd door het besturingssysteem. Samenvattend: computers zijn opgebouwd uit een reeks van niveaus, elk gebouwd op zijn voorganger. Elk niveau stelt een andere abstractie voor. De verzameling datatypen, operaties en faciliteiten op elk niveau wordt de architectuur genoemd. 1.1.3 De evolutie van machines met meerdere niveaus Het microprogrammeren werd uitgevonden om de complexiteit van het ISA niveau en de schakelingen op het niveau van de digitale logica te verlagen. Er werd een interpreter toegevoegd bovenop de ISA laag. Door deze toevoeging kon de instructieset van de ISA laag verkleind worden. Het besturingssysteem werd gemaakt om het inlezen en compileren van programma’s te automatiseren. Door de tijd heen werden besturingssystemen complexer, en worden ze zelfs als geheel nieuw niveau aanzien. Na verloop van tijd ontstonden time sharing systemen waarop meerdere programmeurs tegelijk konden werken. 2 ORGANISATIE VAN COMPUTERSYSTEMEN 2.1 Processor De functie van de CPU bestaat erin de instructies van programma’s uit het hoofdgeheugen een voor een op te halen, te analyseren en uitvoeren. De verschillende componenten van de digitale computer zijn intern verbonden door middel van een bus. De CPU bevat een geheugen dat bestaat uit een aantal registers. Het belangrijkste van deze registers is de programmateller. Deze bepaalt het adres van de volgende uit te voeren instructie. Het instructieregister bevat de instructie in uitvoering. 2.1.1 De organisatie van de CPU Het datapad bestaat uit registers, een ALU en bussen om beiden te verbinden. De ALU voert eenvoudige bewerking uit met zijn invoerregisters, en plaatst het resultaat in het uitvoerregister. De meeste instructies kunnen worden ondergebracht in twee categorieën: register-register instructies en register-geheugen instructies. 2.1.2 Het uitvoeren van een instructie De CPU voert elke instructie uit in een reeks kleine stappen: Plaats de volgende instructie uit het geheugen in het instructieregister Incrementeer de progammateller Bepaal het type van de opgehaalde instructie Als de instructie een woord uit het geheugen gebruikt, bepaal dan de plaats van dit woord Haal het geheugenwoord op in CPU register indien nodig Voer de instructie uit Ga naar stap 1 Dit is de fetch-decode-execute cyclus. Sommige instructies zijn complexer dan andere. Complexere instructies laten versnelde uitvoering toe (vb. floating point instructies), maar zorgen ervoor dat de hardware duurder wordt om te maken. Duurdere computers hadden een grotere instructieset dan goedkopere. Om deze ongelijkheid te elimineren werd door IBM een architectuur ontworpen waaraan zowel goedkopere als duurdere modellen voldeden. Dit werd gerealiseerd door de instructies te interpreteren op goedkopere modellen. Op de duurdere computers met een grotere instructieset werden deze instructies rechtstreeks door de hardware uitgevoerd. Hierdoor zijn de verschillende modellen toch in staat om dezelfde programma’s uit te voeren, hetzij met een verschil in snelheid. Het gebruik van interpretatie heeft bijkomende voordelen: Fouten in de implementatie van een instructie kunnen worden gecorrigeerd. Instrcuties kunnen goedkoop worden toegevoegd Complexe instructies kunnen efficiënt worden ontwikkeld. Een van de problemen was het de baas blijven van de complexiteit die door geïntegreerde schakelingen mogelijk was geworden. Door het gebruik van geïnterpreteerde instructies, kon men van een complex hardware ontwerp een complex software ontwerp maken. Een andere factor voor het succes van interpretatie is het bestaan van control stores. Dit zijn snelle read only geheugens, waarin de interpreters konden worden geplaatst. De control stores bevatten de micro instructies van de interpreter. 2.1.3 RISC versus CISC Het ontwerpen van nieuwe processoren die niet compatibel hoefden te zijn met oudere generaties, liet toe om de instructieset zo te kiezen, dat de totale prestaties van het systeem werden gemaximaliseerd. Na verloop van tijd werd de nadruk gelegd op het aantal instructies dat per tijdseenheid kon worden gestart. De uitvoeringstijd van een enkele instructie werd minder belangrijk. Het voornaamste idee achter CISC, is dat elke instructie efficiënt van het datapad kon worden gehaald, en uitgevoerd. Instructies moesten in één cyclus van het datapad kunnen worden gelezen en uitgevoerd. Uiteindelijk zijn de CISC computers niet verdreven, omdat enerzijds vele bedrijven erin hadden geïnvesteerd. Anderzijds is Intel in staat gebleken om bepaalde ideeën van de RISC architectuur toe te passen. De Intel CPU’s bevatten een RISC kern die de eenvoudigste instructies onmiddellijk in één datapad cyclus kon uitvoeren. De meer complexe CISC instructies werden op de gebruikelijke CISC manier uitgevoerd. 2.1.4 Ontwerpprincipes voor tegenwoordige computers RISC ontwerpprincipes Alle instructies worden best door hardware uitgevoerd: Geen interpretatie door microinstructies leidt tot versnelde uitvoering. Complexe instructies kunnen in stukken worden gesplitst die elk als een serie micro-instructies worden uitgevoerd. Maximaliseer de frequentie waarmee instructies worden gestart: Maximaal aantal instructies per seconde starten. Dit geeft aanleiding tot parallellisme, en bijhorden problemen. Zie ook pipeling. Instructies moeten eenvoudig te decoderen zijn: Zorgen voor een regelmatige vorm, een vaste lengte en een klein aantal velden. Dit heeft als doel om snel te kunnen bepalen welke resources er nodig zijn. Geheugentoegang beperken tot de LOAD en STORE instructies: Geheugentoegang is traag in vergelijking met de snelheid van de microprocessor. Het aantal registers in de microprocessor moet zo groot mogelijk zijn. Data overdracht gebeurt het best simultaan met andere instructies. Zorg voor voldoende registers: Woorden kunnen tijdelijk worden opgeslagen in de registers, zodat het hoofdgeheugen niet telkens hoeft worden gecontacteerd. 2.1.5 Parallellisme op instructieniveau Parallellisme op instructieniveau behelst het simultaan uitvoeren van instructies Parallellisme op processorniveau gebruikt meerdere processoren om de instructies uit te voeren. Grondslag van pipelining is het gebruik van de prefetch-buffer. De instructies worden van te voren uit het geheugen opgehaald, en in de prefetch-buffer opgeslagen. Het ophalen van instructies uit het geheugen is een belangrijke bottleneck. De uitvoering werd dus gesplitst in twee stadia: het ophalen en de eigenlijk invoering. Bij echte pipelining wordt de uitvoering in nog meer stadia opgeslipst. Elk stadium wordt afgehandeld door speciaal hiervoor ontworpen stuk hardware. Alle stadia kunnen parallel worden afgehandeld. Zie ook analogie met transportband in een fabriek. Pipelines maken een trade-off mogelijk tussen wachttijd (uitvoeringstijd van een instructie) en processorbandbreedte (aantal instructies per seconde.). Bij een processor met n stadia en een klokcyclus van T nanoseconden, is de wachttijd nT nanoseconden. Een instructie doorloop n stadia die elk T nanoseconden duren. Een superscalaire architectuur gebruikt meerdere Pipelines. Hierbij moet ervoor gezorgd worden dat gelijktijdig verwerkte instructies geen conflicten veroorzaken inzake resources. Ofwel moet de compiler dit garanderen, ofwel moeten de conflicten tijdens uitvoering worden gedecteerd en geëlimineerd door speciale hardware. 2.1.6 Parallellisme op processorniveau Organisatie van computers met meerdere processoren. Array computers: voor het uitvoeren van berekeningen van natuurkundige problemen gebaseerd op arrays. Twee soorten: arrayprocessors en vectorprocessors. Arrayprocessors bestaan uit een groot aantal identieke processoren, die eenzelfde reeks instructies uitvoeren op verschillende gegevens. Ze zijn opgebouwd als een rooster waarbij elke eenheid bestaat uit geheugen en een processor. Een controle eenheid stuurt de instructies uit, die elke processor dan op zijn eigen geheugen uitvoert. Vectorprocesors zijn gelijkaardig. Een vectorprocessor voert alle optellingsoperaties uit in één opteller, die sterk van pipelining gebruik maakt. Een vectorprocessor heeft bijkomend een vectorregister dat bestaat uit een aantal conventionele registers, die in een instructie uit het geheugen kunnen worden opgehaald. De processoren zijn in beide soorten niet onafhankelijk omdat er nog steeds een besturingseenheid is. Multiprocessors hebben in tegenstelling tot arraycomputers zelfstandige processoren. Alle CPU’s gebruiken hetzelfde gemeenschappelijk geheugen. Om te voorkomen dat de processoren elkaar hinderen, worden ze door software gecoördineerd. Er bestaan implementatievariaties waarbij elke processor is aangesloten op een bus, waar ook het geheugen is op aangesloten. Om conflicten bij geheugentoegang te reduceren, kan bij elke processor een lokaal geheugen worden geplaatst dat enkel door de desbetreffende processor mag worden benaderd. Multicomputers bestaan uit een groot aantal computers die onderling verbonden zijn, en geen gemeenschappelijk geheugen meer hebben, enkel privé geheugen. De communicatie verloopt door middel van berichten die vaak moeten worden gerouteerd. Dit systeem is gemakkelijker te bouwen. Men probeert ook met gemengde system (multicomputer/multiprocessor) om de illusie van gemeenschappelijk geheugen te creeëren, zonder het daadwerkelijk te moeten bouwen. 2.2 Primair geheugen 2.2.1 Bits Hoe meer waarden moeten kunnen worden voorgesteld door een bepaald spanningsbereik, hoe minder scheiding er is tussen de waarden, hoe minder betrouwbaar het systeem uiteindelijk is. Dus, zo weinig mogelijk waarden voorstellen: minimaal twee om als geheugen te kunnen functioneren. Vandaar de oorsprong van de binaire voorstelling. 2.2.2 Geheugenadressen Geheugen bestaat uit individuele cellen, die elk een eigen adres krijgen. Het aantal bits in het adres van een geheugen houdt direct verband met het maximaal aantal cellen dat direct kan worden aangesproken. Een individuele cel bevat een vast aantal bits 2.2.3 De volgorde van bytes Big endian = MSB links. Little Endian = MSB rechts. Bij overdrachten van gegevens tussen machines met verschillende bytevolgorde, ontstaan er conflicten. 2.2.4 2.2.5 Cachegeheugen Geheugens zijn meestal meerdere malen langzamer dan processoren. Het kan enkele cycli duren vooraleer een CPU het gewenste woord uit het geheugen ontvangt. Mogelijks moet hij een aantal cycli wachten vooraleer hij kan verder gaan met de uitvoering. Er zijn twee mogelijke benaderingen. In de eerste benaderingen worden de READ instructie gewoon uitgevoerd wanneer ze worden tegengekomen, maar de processor gaat gewoon door met de uitvoering. Hij moet dan pas wachten wanneer een instructie een woord probeert te gebruiken dat nog niet is aangekomen. Een andere benadering zegt dat de compiler ervoor moet zorgen dat er geen code is die woorden probeert te gebruiken vooraleer ze zijn opgehaald. Vaak zit er niets anders op dan NOP instructies te gebruiken. Men is technisch in staat om snellere geheugens te bouwen, maar dit is te kostelijk doordat ze op de CPU zelf zouden moeten aanwezig zijn. In de plaats daarvan maakt men een compromis: er is een kleine hoeveelheid snel geheugen aanwezig op de CPU: de cache. Door de gebruikte technieken benadert de performantie van dit systeem de performantie van het ideale geval. Het principe van de cache is eenvoudig: als de CPU een woord nodig heeft, kijkt hij eerst in de cache. Is het daar niet aanwezig, dan wordt pas in het hoofdgeheugen gekeken. Dit principe is verwant met het lokaliteitsbeginsel: geheugenreferenties die binnen eenzelfde korte tijdsspanne nodig zijn, bestrijken slechts een fractie van het totale geheugen. Als een woord uit het hoofdgeheugen in de cache wordt geladen, worden meteen ook een aantal buren ingeladen. Hoofdgeheugens en caches worden op grond van het lokaliteitsbeginsel in blokken van een vaste grootte verdeeld. Blokken in de cache worden meestal cacheregels genoemd. Het ontwerpen van caches behelst een aantal aspecten: De grootte van de cache: hoe groter de cache, hoe groter de trefkans, maar hoe duurder. De grootte van de cacheregel De organisatie van de cache: hoe wordt bijgehouden welke woorden aanwezig zijn in de cache. Worden data en instructies in dezelfde of aparte caches geplaatst? (verenigde caches vs. Gesplitste caches). Gesplitste caches zijn voordeliger voor systemen met pipeling. De eenheid die instructies ophaalt en de eenheid die de operanden bepaalt kunnen dan parallel toegang hebben tot de cache. 2.3 2.4 Input/Output 2.4.1 Bussen, interrupts De eenvoudige goedkope PC heeft een bus om CPU geheugen en randapparaten te verbinden. Elk I/O-apparaat bestaat uit een controller, en het apparaat zelf. De controller bestuurt het IOapparaat en regelt de bustoegang ervoor. Een controller kan zelfstandig waarden naar het geheugen schrijven. In dat geval wordt er van direct memory access gesproken. Na afloop van een overdracht d.m.v. DMA treedt meestal een interrupt op die de processor onderbreekt en in een interrupt-handler (ISR, interrupt service routine) verplicht om na te gaan of er geen fouten opgetreden zijn, en het besturingssysteem te verwittigen dat de IO voltooid is. Na afloop van de ISR, hervat de processor zijn programma. Wanneer een interrupt optreedt gebeurt het volgende: De huidige instructie wordt afgewerkt Het statusregister en programmateller worden op de stapel geplaatst Het beginadres van de ISR wordt in de programmateller geplaatst In de ISR: Extra register die worden gebruikt in de ISR worden op de stapel geplaatst De service wordt uitgevoerd De registers worden van de stapel gehaald De instructie RETI zorgt ervoor dat de PC en het SR van de stapel worden gehaald en worden hersteld. Elke processor heeft twee interrupt ingangen: De NMI: de non-maskable interrupt: deze interrupt kan niet door de processor worden genegeerd. De IRQ: de gewone interrupt request. Deze interrupts kunnen wel worden gemaskeerd. Wanneer er interrupts gelijktijdig optreden, wordt deze met de hoogste prioriteit afgehandeld. Hiervoor kan een PIC (programmable interrupt controller) worden gebruikt. Alle interrupt lijnen worden op de PIC aangesloten, die dan beslist welke interrupt er naar de processor wordt doorgegeven. De processor stuurt een interrupt acknowledge (IACK), waarna de aanvrager zichzelf identificeert via de adresbus. De busarbitrage bepaalt wie er toegang krijgt tot de bus. Er zijn twee benaderingen: Gecentraliseerde busarbitrage: een speciale eenheid, de busarbiter, bepaalt wie toegang krijgt bij gelijktijdige aanvragen. IO devices krijgen hierbij doorgaans voorrang. Als er geen IO plaatsvindt kan de processor alle cycli opeisen. Gedecentraliseerde busarbitrage: Er zijn een aantal busaanvraaglijnen (met een verschillende prioriteit). Elke aangesloten component moet dan die lijnen in de gaten houden om te weten of hij aan de beurt is. (dit is goedkoper, maar er zijn meer lijnen nodig.) 3 HET NIVEAU VAN DE DIGITALE LOGICA 3.1 3.2 3.2.1 3.2.2 3.2.3 Rekenkundige circuits Schuivers: zie diagram pg. 156 Optellers: half-adders, full-adders, ripple carry adders en cary select adders. ALU 3.2.4 Klokken Om een nauwkeurig kloksignaal te verkrijgen, wordt meestal gebruik gemaakt van een kristaloscilator. Omdat gedurende een cyclus dikwijls veel zaken moeten gebeuren, kan de klok in subcycli worden verdeeld. Dit kan worden gerealiseerd door de kloklijn af te tappen, en een vertraginsschakeling te plaatsen. Hierdoor wordt een kloksignaal gegenereerd dat in fase verschoven is. Dit kan voor elke nodige subcyclus worden gedaan (vertragingen verschillen dan) 3.3 Geheugen 3.3.1 3.3.2 3.3.3 3.3.4 Geheugenorganisatie Geheugens kunnen worden opgebouwd als een matrix van d-latches. Er zijn invoerlijnen voor adres, besturing en data. De adreslijnen selecteren het juiste woord, de data invoerlijnen dienen om gegevens in het geheugen te plaatsen. De besturingslijnen bestaan uit chip-enable, read/write select en output-enable. (zie tekening pg. 168). Deze types geheugens zijn eenvoudig uit te bereiden naar grotere geheugens. 3.3.5 Geheugenchips Voor een bepaalde grootte zijn er meerdere mogelijkheden om het geheugen te organiseren. Adressering kan gebeuren door woorden te adresseren, of zelfs individuele cellen door gebruik te maken van rijadressen en kolomadressen. Rij en kolomadressen worden soms in aparte cycli doorgegeven, zodat dezelfde pinnen kunnen worden gebruikt. Dit is echter trager. 3.3.6 RAM en ROM RAM: random access memory. Er zijn twee soorten: SRAM (statisch) en DRAM (dynamisch). Statische geheugens maken gebruik van d-flipflops om de gegevens te bewaren. Zolang deze worden gevoed,blijven ze hun toestand houden. Deze geheugens zijn snel, en daarom geschikt voor caches van niveau 2. De densiteit van SRAM’s is typisch veel lager dan die van de DRAM’s. Dynamische geheugens bevatten cellen die elk bestaan uit een transistor en een condensator. De condensator wordt opgeladen en ontladen naar gelang de waarde die de cel moet voorstellen. Omdat een condensator na verloop van tijd zijn lading verliest, moeten de cellen in dynamische geheugens om de zoveel miliseconden worden ververst. Deze geheugens zijn veel geodkoper dan statische rams, omdat ze slechts één transistor per cel vereisen t.o.v. van zes transistoren bij dynamische rams. Deze dynamische geheugens worden meestal gebruikt voor het hoofdgeheugen. Dynamische geheugens zijn trager, omdat het opladen van een condensator trager is dan het zetten van een flipflop. Ze laten echter wel een grotere densiteit toe. SDRAM (synchronous dynamic RAM) is een kruising tussen beide soorten. De processor laat aan het geheugen weten hoeveel cycli het moet doorlopen en start het dan. In elke cyclus levert het geheugen dan een aantal bits af aan de processor. Indien een niet vluchtig geheugen nodig is, kan ROM (read only memory) worden gebruikt. ROMS zijn typisch goedkoper als ze in voldoende grote aantallen worden geproduceerd. Klassieke ROMS kunnen op geen enkele manier worden aangepast. Eens gefabriceerd is hun inhoud vast. Om de inhoud van het geheugen te kunnen vastleggen na fabricage, werd de PROM (programmable ROM) ontwikkeld. Deze laat toe om de inhoud éénmaal te schrijven. Vervolgens werd de EPROM ontwikkeld, die toelaat de het geheugen kan worden gewist en opnieuw worden geschreven. Het wissen gebeurt door een kwartsvenster sterk te belichten met ultraviolet licht. Ze zijn handig voor ontwikkeling, maar ongeschikt voor massaproductie van apparaten. Nog beter is de EEPROM. Deze laat toe om de chip door middel van elektrische signalen te wissen. De recentste ontwikkeling in niet vluchtige geheugens is het flash memory. Deze geheugens kunnen per blok worden gewist en herschreven. Door de lage toeganstijden kunnen deze worden gebruikt als vervanger voor magnetische schijven. 3.3.7 Aansluiten van geheugenchips Om een geheugen aan te sluiten dat uit meerdere individuele geheugenchips bestaat, kunnen een paar lijnen van de adresbus worden gebruikt om een decoder aan te sturen die de juiste chip selecteert via de CS ingang van de chip. De adresruimte lijkt dan lineair te zijn, terwijl in wezen de woorden op verschillende chips zijn gesitueerd. Anderzijds is het ook mogelijk om meerdere chips te combineren om bredere woorden te verkrijgen. Alle adresingangen van de chips worden dan recht op recht verbonden met de adresbus, terwijl de data-uitgangen worden verbonden met een deel van de databus. Zo kunnen bijvoorbeeld 4 chips met een 8 bit data-uitgang worden gecombineerd om woorden van 32 bit te vormen, en deze aan te sluiten op de 8-bit databus van de microprocessor. Indien de adresbus breed genoeg is, kunnen de laagste orde bytes eventueel worden gebruikt om individuele bytes binnen het geheugenwoord te adresseren. 3.3.8 Koppeling tussen microprocessor en randapparaten Gebeurt door middel van een interface chip. Deze chip bevat registers, toestand en data. 3.3.9 Adressering van de IO Om de IO apparaten te adresseren zijn er twee mogelijke benaderingen. Beiden zijn snel en goedkoop te implementeren. Memory mapped IO zorgt ervoor dat IO chips op dezelfde manier als het hoofdgeheugen kunnen worden aangesproken. De adressen van de IO chip maken deel uit van dezelfde geheugenruimte als de andere geheugens. Externe decoder logica bepaalt dan welke chip er daadwerkelijk wordt aangesproken. Nadelig is dan weer dat een deel van de adresruimte moet worden opgeofferd voor de IO chips, dus er is minder geheugen. De programmeur moet weten hoe er geadresseerd wordt, en hij moet hierover de controle uitvoeren. De microprocessor voert domweg de instructies uit. Isolated IO brengt de IO onder in een eigen geheugenruimte. Er worden apparte stuurlijnen voorzien om IO in te schakelen. De processor geeft hiermee aan of de adressen op de adresbus bestemd zijn voor geheugen of IO. Hierdoor ontstaat meer IO ruimte dan er in wezen nodig is. (De extra stuurlijn verdubbelt de aanwezig geheugenruimte, en deelt ze op in twee gelijke stukken IO en MEMORY) De instructieset van de microprocessor moet ook worden uitgebreid met instructies voor IO bewerkingen. In moderne implementaties krijgt deze techniek de voorkeur. 3.4 CPU-chips en BUSSEN 3.4.1 CPU chips De CPU communiceert met geheugen en randapparaten door signalen op zijn pinnen te zetten en signalen van die pinnen te lezen. Er zijn datapinnen, adrespinnen en besturingspinnen. Hoe groter het aantal adrespinnen, hoe groter de hoeveelheid geheugen de processor kan adresseren. Hoe meer datapinnen, hoe meer bits de processor in een instructie uit het geheugen kan lezen. Verder nog pinnen voor busbesturing, interruptpinnen, pinnen voor busarbitrage, pinnen voor communicatie met een coprocessor. Sommigen zijn aanwezig omwille van compatibiliteitsredenen. 3.4.2 Computerbussen Een bus is een elektrisch pad dat gemeenschappelijk is tussen alle erop aangesloten randapparaten. Oorspronkelijke computers hadden slechts een externe (buiten de CPU gelegen) bus: de systeembus. Om toe te staan dat kaarten van andere fabrikanten konden gebruikt worden, was er nood aan een busprotocol dat beschrijft hoe de communicatie op de bus verloopt. Op een bus kunnen actieve apparaten (masters) en passieve apparaten (slaves) worden aangesloten. Bus communicatie verloopt dikwijls met three state apparaten die kunnen worden uitgeschakeld indien nodig. Three state wil zeggen dat de logica naast de gebruikelijk hoog en laag waarden, ook nog de toestand High-Z (hoogimpedant) kent. Door de hoge impedantie die het apparaat dan krijgt, lijkt het alsof de pinnen van de chip niet zijn aangesloten, en wordt de bus niet verder belast. Dit vermijdt problemen gerelateerd aan de maximale fan-out van de chips. Een bus heeft adreslijnen, besturingslijnen en datalijnen. Aspecten van busontwerp zijn: busbreedte, klokken van de bus, arbitrage, busarbitrage en busoperaties. 3.4.3 Busbreedte Hoe meer adreslijnen in de bus, hoe meer geheugen er kan worden aangesproken. Achteraf adreslijnen toevoegen is duur, omdat deze dan ook van besturingslijnen moeten worden vergezeld. De om de bandbreedte van de bus te verhogen, kan men hetzij het aantal datalijnen opvoeren, of de buscyclus verkleinen. Dit heeft wel als gevolg dat de compatibiliteit met oudere modellen verloren gaat. Het aantal datalijnen verhogen draagt dus de voorkeur. Om de breedte van een bus te verlagen (en ze dus goedkoper te maken) maakt men soms gebruik van een gemultiplexte bus waarbij data en adressen achtereenvolgens op de bus worden geplaatst. 3.4.4 Het klokken van een bus Er zijn twee soorten bussen: synchrone bussen, met een master kloksignaal, en asynchrone bussen, zonder master kloksignaal. Bij synchrone bussen treden alle gebeurtenissen op, op gehele veelvouden van de systeemklok. Dit heeft als nadeel dat het traagste apparaat op de bus de snelheid van de gehele bus bepaalt. Ook moeten er steeds volledige cycli worden doorlopen, ook al is er slechts een klein deel van de laatste cyclus nodig om een volledige overdracht te voltooien. De asynchrone bus maakt gebruik van synchronisatielijnen, waarbij masters en slaves elkaar verwittigen van een voltooiing van een opdracht. 3.4.5 Busarbitrage Naast de CPU moeten IO chips ook als master kunnen optreden om gegevens naar het geheugen weg te schrijven. Soms moet ook een coprocessor master van de bus kunnen worden. Om te voorkomen dat er chaos ontstaat wanneer twee apparaten tegelijk master van de bus willen bworden is er busarbitrage nodig. Busarbitrage kan gecentraliseerd of gedecentraliseerd gebeuren. In het gecentraliseerde geval wordt er gebruik gemaakt van een vergunningslijn waarop apparaten in serie zijn aangesloten, die vergunningen doorgeven als ze geen aanvraag hebben gedaan. (daisy chain) In het geval van gedecentraliseerde arbitrage, worden er meerdere aanvraaglijnen (elk met een prioriteit) door alle apparaten in de gaten gehouden. Op het einde van een aanvraagcyclus weet een apparaat dan of het de hoogste prioriteit had of niet. Een andere mogelijkheid voor gedecentraliseerde busarbitrage maakt gebruik van een wired-OR lijn voor de aanvragen, en twee gewone lijnen: BUSY wordt aangezet door de actuele master, en een arbitragelijn. 3.4.6 Busoperaties Datatransfers Bloktransfers kunnen efficiënter worden uitgevoerd dan afzonderlijke transfers. Bij overdrachten naar caches is het wenselijk dat volledige regels in een keer kunnen worden overgebracht. Afhandelen van interrupts Een CPU verwacht meestal een interrupt van het IO apparaat als een overdracht voltooid is. Als er meerdere interrupts tegelijk kunnen optreden is het gebruikelijk om prioriteiten toe te kennen aan de interrupts. 3.5 3.6 Voorbeelden van bussen 3.7 Interfaces 3.7.1 IO-chips UART (Universal asynchronous receiver transmitter) en USART (universal synchronous asynchronous receiver transmitter) Een UART is een chip die een byte uit de databus kan lezen, en bit voor bit op een transmissielijn plaatsen voor een terminal, of deze data van een terminal inlezen. Een USART kan naast alle functionaliteit van UART ook synchrone transmissies op basis van diverse protocollen afhandelen. PIO-chips Een parallel Input/Output chip zorgt ervoor dat de CPU de waarden van een willekeurige statuslijn kan lezen of er naar schrijven. 3.7.2 Adresdecodering Een deel van de lijnen van de adresbus wordt gebruikt voor de selectie van de juiste chip. De lijnen die worden gebruikt, verdelen de geheugenruimte in stukken, die elk een subset van de ruimte zijn. Elk van deze ruimten heeft dan een specifiek doel, de adressen van een bepaalde chip vallen dan binnen een bepaalde ruimte. 4 HET NIVEAU VAN DE MICROARCHITECTUUR 4.1 Een voorbeeld van een microarchitectuur 4.1.1 Het datapad Zie afbeelding pg. 236 Het datapad is het gedeelte van de CPU dat de ALU bevat met de bijbehorende registers, input en output. De meeste registers kunnen hun inhoud op de B-bus plaatsen. Het resultaat van de ALU stuurt de C bus aan. De ALU wordt aangestuurd door zes besturingslijnen. Het is niet zo dat elke mogelijke toekenning van waarden aan de besturingslijnen iets zinvol doet. De rechteroperand van de ALU wordt voorgesteld door de B-bus, de linkeroperand is de inhoud van het H register. Dit register kan worden geladen door de ALU te schakelen zodat hij de input in van de B-bus naar de uitgang doorgeeft. Het is mogelijk om in één klokcyclus te lezen en te schrijven naar hetzelfde register. Dit gebeurt tijdens verschillende fasen van dezelfde cyclus. Er zijn dus deelcyclussen nodig, en een precieze timing van het datapad. Timing van het datapad Bij de start van elke klokcyclus wordt een korte pul gegenereerd. Tegen de tijd dat deze puls is afgevallen, zijn alle signalen die de poorten aansturen opgezet. Dit neemt een tijdsinterval ∆w in beslag. Vervolgens wordt het register geselecteerd dat op de B-bus nodig is, en wordt de inhoud ervan op de B-bus geplaatst. Het duurt een tijd ∆x vooraleer de waarde stabiel is. De ALU kan dan zijn bewerking uitvoeren, het duurt een tijd ∆y vooraleer de uitgang van de ALU stabiel is. Het duurt nog een tijd ∆z vooraleer de resultaten zich over de C-bus hebben voortgeplant. Een andere benadering beschouwt deze gebeurtenissen als deelcycli met volgende omschrijvingen: Opzetten van de stuursignalen Laden van de registers op de B-bus Werking van de ALU en de shifter Voortplanting van de resultaten langs de C-bus terug naar de register. Tijdens de stijgende flank van de volgende klokcyclus, worden de resultaten in de registers opgeslagen. Merk op dat deze deelcycli hoe dan ook impliciet zijn. Ze worden niet gerealiseerd door klokpulsen, dan wel door de vertragingen op het datapad. Geheugenmodel Er kan op twee mogelijke manieren met het geheugen worden gecommuniceerd: In eerste instantie via een 32 bit poort bestuurd via twee registers: het memory address register (MAR) en via het memory data register (MDR). Deze poort is adresseerbaar per woord. In tweede instantie via een 8 bit poort adresseerbaar per byte. Deze poort wordt aangestuurd via de PC. Waarden worden in de minst significante acht bits van het memory buffer register (MBR) geplaatst. Elk van de registers wordt aangestuurd door twee stuurlijnen die bepalen. Een eerste lijn bepaalt of de inhoud van het register op de B-bus wordt geplaatst, een tweede lijn bepaald of er van de C-bus naar het geheugen wordt gelezen. De harvard architectuur wordt gevolgd. Programmageheugen en datageheugen zijn appart. De data gebruikt de MAR/MDR combinatie. Het programmagehgeugen wordt benaderd via de PC/MBR combinatie. De B bus is 32 bit breed terwijl het MBR slechts 8 bit breed is. Om het MBR op de B bus te plaatsen zijn er twee mogelijkheden. Enerzijds kunnen enkel de eerste 8 LSB van de B bus worden gebruikt. Anderzijds kan de tekenbit van het MBR (de MSB) worden uitgebreid over 24 plaatsen. Dit heet sign extension. Wanneer een gegeven wordt opgevraagd in cyclus k is het gegeven pas beschikbaar in cyclus k+2 4.1.2 Micro-instructies Een micro-instructie is een verzameling binaire waardes die wordt aangelegd aan de controlelijnen van het datapad gedurende een cyclus. Voor het besturen van het datapad zijn 27 signalen nodig: 9 signalen voor het regelen van het schrijven van de C bus naar de registers 9 signalen voor het regelen van de inschakeling van registers op de B-bus voor ALUinput. Dit wordt vereenvoudigd, we gebruiken er slechts 4. 8 signalen voor het regelen van ALU en schuifeenheid instructies. 1 signaal voor het ophalen van de geheugenwaarde via PC/MBR Een enkele cyclus bestaat uit volgende impliciete deelcycli: Opzetten van de stuursignalen voor het aandrijven van het datapad (delta-x) Het lezen van de waarden uit registers en het plaatsen van deze waarden op de B-bus (delta-y) Het voortplanten van de signalen over de schuifeenheid naar de C bus (delta-z) Het voortplanten van de resultaten naar een of meer registers. (enveneens onderdeel delta-z) Tijdens de stijgende flank van de volgende klokcyclus worden de waarden naar de registers geschreven. 4.1.3 Micro-instructie besturing: de MIC-1 De volgordebepaling bepaalt welke stuursignalen tijden elke cyclus wordt ingeschakeld. De sequencer is verantwoordelijk voor het in volgorde uitvoeren van de operaties die een enkele ISA instructie realiseren. De sequencer moet tijdens iedere cyclus twee soorten informatie produceren: De status van ieder stuursignaal binnen het systeem Het adres van de volgende uit te voeren micro instructie De micro architectuur bestaat uit twee gedeelten. Enerzijds is er het datapad, anderzijds is er het stuurgedeelte. Het belangrijkste onderdeel van het stuurgedeelte is de control store. Dit is een geheugen dat het volledige microprogramma bevat. In tegenstelling tot instructies in het hoofdgeheugen, hoeven instructies in de control store niet in adresvolgorde worden uitgevoerd. Om dit eenvoudig te realiseren geeft iedere microinstructie expliciet zijn opvolger aan. De control store heeft zijn eigen geheugenadresregister (MPC, micro program counter) en geheugendataregister (MIR, micro instruction register). De functie van het MIR is het bewaren van de huidige micro instructie; waarvan de bits de stuursignalen aandrijven die het datapad in werking zetten. Bits van het instructieregister (MIR): ADDR en J(JAM) zorgen voor de selectie van de volgende instructie ALU (8 bit) sturen de ALU en shifter aan De C bits zorgen ervoor dat de registers worden geladen vanaf de C bits De M en B bits sturen de decoder aan die bepaalt wat er op de B-bus wordt geplaatst. Werking van de microarchitectuur Bij het begin van iedere cyclus (dalende flank van de klokpuls) wordt MIR geladen in de controlstore vanuit het woord waarnaar MPC verwijst. De laadtijd van het MIR is ∆w. Dit is de eerste deelcyclus. Wanneer het MIR de micro-instructie bevat, planten de diverse signalen zich voort over het datapad. Een registerinhoud wordt op de B-bus geplaatst, de ALU weet welke bewerkingen er moeten worden uitgevoerd. Er worden dus meerdere handelingen uitgevoerd. Na een tijd ∆w + ∆x zijn de ALU inputs stabiel. Na een tijd ∆y is de ALU output en shifter output stabiel, alsook de N en Z outputs. De N en Z waarden worden opgeslagen in enkele flipflops. Deze bits worden samen met alle overige register die vanaf de C-bus worden geladen en uit het geheugen worden geladen, opgeslagen tijdens de stijgende flank van het kloksignaal, tegen het einde van de datapadcyclus. Na een interval ∆z wordt heeft de output van de schuifeenheid de registers via de C-bus bereikt. Nu kunnen de registers tijdens het einde van de cyclus worden geladen. Tezelfdertijd zijn de resultaten van de vorige geheugenbewerking beschikbaar en is de MPC geladen. De bepaling van de volgende instructie begint nadat het MIR geladen is, en stabiel geworden is. Eerst wordt de 9 bit waarde van NEXT_ADDRESS gekopieerd naar de MPC. Wanneer een van de JAM bits gezet is (JAMN of JAMZ), wordt de logische OR operatie gedaan tussen de gezette bits, en respectievelijk het N of Z bit (latches) afkomstig van de ALU. Het bekomen resultaat wordt geladen in de meest significante bit van de MPC. Wanneer het JMPC bit gezet is, wordt er een logische OR gedaan tussen de 8 MBR bits, en de 8 minst significante bits van de MPC. 4.2 Een voorbeeld ISA: IJVM Het ISA niveau waarvan de instructies moeten worden geïnterpreteerd door het microarchitectuur niveau. Het ISA niveau wordt soms ook de macro architectuur genoemd. 4.2.1 Stapels Procedures gebruiken lokale variabelen die traditioneel worden gerealiseerd met stapels. Lokale variabelen worden bijgehouden in stack frames. Er worden twee pointers bijgehouden: de stack pointer en de local variable pointer. De SP wijst steeds naar de bovenkant van de stapel, terwijl LV naar de basis van het stack frame wijst. Berekeningen worden via een operanden stack gerealiseerd. Hierbij worden alle operanden op de stack gepushed, er wordt een intructie uitgevoerd die de operanden popt en het resultaat op de stapel pusht. Daarna moet het programma het resultaat van de stapel poppen, en opslaan in de juiste variabele in de stack. 4.2.2 Het IJVM-geheugenmodel Het geheugengebied is in de volgende vier delen opgedeeld: Constantenpool Read-only Bevat constanten, strings en pointers naar andere geheugengebieden De inhoud wordt samen met het hoofdprogramma geladen, en blijft onveranderd tijden de uitvoering. CPP (constant pool pointer) Lokale variabelenframe Bij het aanroepen van een methode wordt een gebied gereserveerd voor opslag van de lokale variabelen. De geldigheid van het gebied is beperkt tot de levensduur van de aanroep. LV pointer duidt basis aan Operand stack Tijdelijk opslaan van operanden. De omvang wordt vooraf door de compiler berekend. SP duidt bovenzijde aan Method gebied Geheugengebied dat het programma zelf bevat. PC duidt volgende instructie aan. 4.2.3 De IJVM instructieset Instructies bestaan uit een opcode en eventuele operanden. (stapelgeoriënteerd) 4.2.4 Compileren van Java naar IJVM De het Java programma wordt vertaald naar een equivalent programma dat de IJVM instructies gebruikt. 4.3 Een voorbeeld van een implementatie 4.3.1 Micro-instructies en hun notatie Een fictieve taal waarbij een regel een microinstructie voorstelt, die in een klokcyclus wordt uitgevoerd. De taal is de micro assembly language (MAL) 4.3.2 Implementatie van IJVM met de MIC-1 Het microprogramma dat de instructies van de IJVM interpreteert bevat slechts 112 microinstructies. Registers: CPP pointer naar de constantenpool LV pointer naar de lokale variabelen SP bovenkant van de stapel MBR register van één byte dat de bytes vanuit de instructiestroom na elkaar bewaart als deze ter interpretatie vanuit het geheugen worden aangeboden. TOS bevat steeds de waarde van de geheugenlocatie waarnaar de SP wijst. OPC tijdelijk register zonder vast doel. De volgende opcode wordt steeds klaargezet in het MBR. Vanuit de hoofdlus wordt gespronden naar het label dat overeenkomt met de opcode die aanwezig is in de MBR. Aan het einde van een instructie wordt teruggesprongen naar de hoofdlus. 4.4 Ontwerp van het microarchitectuurniveau 4.4.1 Snelheid versus kosten Voor eenzelfde schakelsnelheid en ISA kan de verwerkingssnelheid op drie manieren worden verhoogd: Het aantal klokcycli nodig voor het uitvoeren van een instructie reduceren Organisatie vereenvoudigen met kortere klokcycli als gevolg De uitvoering van instructies te laten overlappen. (pipelining) Het aantal nodige cycli om een instructie uit te voeren heet de padlengte. Snelheid verhogen kan door horizontale microprogrammering: de micro-instructies worden zo weinig mogelijk gecodeerd. Dit is echter wel duur en ingewikkeld. De verbetering komt er dan omdat micro-instructies niet hoeven gedecodeerd te worden. De instructies zijn dan breder, maar hun aantal is kleiner. Kosten verlagen kan door verticale microprogrammering: de microinstructies worden maximaal gecodeerd. Hierdoor kan plaats worden bespaard. Er zijn dan meer micro instructies nodig, maar ze zijn korter. Bij nanoprogrammering bevat het microprogramma pointers naar micro-instructies. Bepaalde micro instructies komen in het microprogramma meerdere keren voor. Bij nanoprogrammering zijn er dan geen dubbele instructies meer aanwezig. De micro instructies zelf worden in de control store geplaatst, en de nano store bevat de verwijzingen naar de micro instructies. Dit is relatief traag, maar goedkoop. 4.4.2 Reductie van de lengte van het executiepad De volgende drie paragrafen beschrijven de verbetering die worden aangebracht om uit de MIC-1 de MIC-2 te construeren. Interpretatielus samenvoegen met het einde van iedere microcodereeks De interpretatielus wordt samengevoegd met het einde iedere microcode reeks. Dit heeft als nadeel dat het microprogramma langer wordt. Architectuur met drie bussen Een bus toevoegen voor de linkeroperand van de ALU zorgt ervoor dat er geen cyclus meer nodig is om een waarde in het H register te laden. Aparte unit voor het ophalen van instructies Er wordt een apparte unit gemaakt voor het ophalen van de instructies, de instruction fetch unit (IFU), om de ALU te ontlasten met zaken zoals de programmateller incrementeren. De taken zijn: PC onafhankelijk verhogen Bytes uit de instructiestroom ophalen en bufferen. Dit gebeurt ongeacht het feit of ze nu nodig zijn of niet. 8 en 16 bit operanden assembleren. Zie figuur 4-27 p. 283 voor de instruction fetch unit. Modelleren als een finite state machine met 7 staten. 4.4.3 De MIC-2: een ontwerp met prefetching: de MIC-2 Verbeteringen t.o.v. de MIC-1: Het gebruik van de IFU zorgt ervoor dat de padlengte van de gemiddelde instructie aanzienlijk wordt verkort: De hoofdlus wordt volledig geëlemineerd, het einde van iedere instructie is eenvoudigweg een sprong naar de volgende instructie Telkens een 16 bits index of een offset moet worden berekend (16 bits waarde wordt geassembleerd en direct aan de ALU als 32 bit waarde gepresenteerd.) 4.4.4 Een ontwerp met pipelining: de MIC-3 Door het toevoegen van latches (registers van) wordt het datapad in afzonderlijke delen verdeeld, die onafhankelijk van elkaar kunnen opereren. Door deze toevoeging kan de klok versneld worden, omdat de maximale vertraging korter wordt. Verder kunnen nu ook alle delen van het datapad tegelijk opereren en tijdens iedere cyclus worden gebruikt. Het is redelijk om te veronderstellen dat de kloksnelheid kan worden verdrievoudigd. (Padlengte voor elk deel is een derde van het oorspronkelijke pad.) Zie ook RAW dependencies en stalling. (p. 292) 4.4.5 Pipelining in zeven stadia: de MIC-4 De laatste verbeteringen lossen het probleem op dat voorwaardelijke spronden de pipelining in de war sturen. Er wordt een decoderingseenheid toegevoegd, die eveneens wordt gevoed door de IFU. De decoderingseenheid bevat een ROM die wordt geïndexeerd met de opcode van de instructie. De ROM bevat dan voor elke opcode de lengte van de instructie, en een index in een ander ROM: het ROM voor de microbewerkingen. De IJVM instructielengte wordt gebruikt om de decoderingseenheid in staat te stellen de inkomende bytestroom in instructies te ontleden. De eenheid weet dan voortdurend welke bytes opcodes zijn, en welke operanden voorstellen. De decoderingseenheid transporteert de index naar het ROM voor micro-operaties, die in de tabel wordt gevonden, naar de volgende component: de queuing unit. Deze unit bevat twee interne tabellen: een in ROM en een in RAM. De tabel in de ROM bevat het microprogramma, waarbij elke IJVM instructie een aantal opeenvolgende elementen bevat, micro-operaties genaamd. Deze micro-operaties zijn gelijkaardig aan de micro-instructies, en bevatten twee extra bitvelden: Final en Goto. Final duidt de laatste instructie in de reeks aan, Goto duidt op een voorwaardelijke microsprong. De queueing unit ontvangt een index in het micro-operatie ROM van de decoderingseenheid. De micro-operatie wordt opgezocht, en gekopieerd naar de interne wachtrij, de volgende micro-operatie wordt ook opgezocht en ook gekopieerd naar de wachtrij. Dit gaat door totdat er een micro-operatie wordt gevonden waarvan de Final bit gezet is. Dit is de laatste microoperatie die wordt gekopieerd. De queueing unit geeft bij het laden van de laatste instructie een bevestigingssignaal aan de decodeereenheid, die dan de volgende IJVM instructie doorgeeft. De queueing unit voedt het MIR register. Omdat het MIR register niet in elke deelcyclus beschikbaar is, wordt er voor elke deelcyclus een eigen MIR voorzien, waarin de instructie voor elke opeenvolgende Het eerste MIR wordt steeds geladen met een nieuwe micro-instructie. Wanneer de queuing unit een instructie met het Goto bit ziet, wacht houdt hij de queing op, totdat de spronginstructie verwerkt is. Samengevat is de structuur van de pipeline de onderstaande: IFU Deco der Wach trij Opera nden Uitvo ering Terug schrij ven Gehe ugen 4.5 Verbetering van de prestaties Verbeteringen aan de implementatie, zonder de architectuur te wijzigen. Oude programma’s blijven dan compatibel. Op een gegeven moment zullen er geen verbeteringen mogelijk zijn met de huidige architectuur, dan besluit men om de architectuur bij te werken. 4.5.1 Cachegeheugen Moderne processoren stellen grote eisen aan het geheugen inzake de latentie en de geheugenbrandbreedte. Daarom voegt men een klein en snel cachegeheugen tussen de microprocessor en het grote trage hoofdgeheugen. Het gebruik van meervoudige caches is een van de meest doeltreffende technieken om zowel de latentie te verlagen als de geheugenbandbreedte te verhogen. Een elementaire doch effectieve techniek gebruikt een aparte cache voor data en instructies. (split cache) Verder kan ook een L2 cache worden toegevoegd, die zich niet in de CPU bevindt, maar dikwijls wel via een hogesnelheidspad verbonden is met de CPU, en in dezelfde behuizing zit. Dit is meestal een unified cache die een mengsel van data en instructies bevat. Wanneer er toegang tot geheugen vereist is, laadt men het opgevraagde woord en een aantal woorden uit zijn omgeving in de cache, zodat deze de volgende keer snel kunnen worden benaderd. Dit principe is gebaseerd op het lokaliteitsprincipe, dat uiteenvalt in twee aspecten: Met Ruimtelijke lokaliteit wordt bedoeld dat het waarschijnlijk is dat geheugenlocaties zullen worden benaderd met adressen die in numerieke zin lijken op de adressen van zojuist benaderde geheugenlocaties. Men spreekt van Tijdelijke lokaliteit als geheugenlocaties worden benaderd die even terug ook werden benaderd. Sommige caches berusten in het feit dat regels die lang niet meer werden geraadpleegd, uit het geheugen mogen verwijderd worden. Het geheugen wordt verdeeld in blokken van vaste grootte, die cacheregels worden genoemd. Wat betreft de opbouw van de caches zijn er drie benaderingen: associatieve caches, directmapped caches en set-associatieve caches. Associatieve caches Zijn een voorbeeld van content adressable memory (CAM). De cache is opgebouwd als een tabel met een entry per bloknummer. Een entry bevat een valid bit dat aangeeft of de verwijzing geldig is, het bloknummer en de inhoud van het blok. Om te bepalen of een blok aanwezig is in de cache, moet elk slot in de tabel worden gekoppeld met een hardware comparator die het bloknummer vergelijkt met het gevraagde nummer. Elke entry in de cache wordt tegelijk gecontroleerd, daarom is de omvang van de cache beperkt door de plaats die de comparatoren innemen. Dit is toepasbaar voor L1 caches. Wanneer de cache vol is wordt een plaats gezocht om een nieuw gegeven op te zoeken. De least recently used benadering is de beste, maar deze vergt te veel tijd. Een willekeurig blok uit de cache verwijderen is dan weer te rudimentair. Daarom gebruikt men meestal de FIFO strategie. Direct mapped cache Er wordt een relatie opgesteld tussen het bloknummer en het slotnummer. Dit heeft als gevolg dat een blok slechts op een plaats in de cache kan worden gevonden. Er moet dus ook maar een comparator worden voorzien. Het adres wordt onderverdeeld in: TAG: Zelfde als de TAG bits van het cache element LINE: Geeft aan in welk cache element het blok wordt verwacht WORD: Geeft aan welk woord binnen het blok wordt benaderd BYTE: welke byte van het woord, indien slechts een byte. Om een regel in de cache op te zoeken: LINE bits worden gebruikt als index om het cache slot op te zoeken Als het element geldig is, worden de TAG bits vergeleken. Als de TAG bits gelijk zijn, werd er een cache hit gevonden. Het blok is beschikbaar in de cache Als de TAG bits verschillend zijn, is de regel afwezig en moet het hoofdgeheugen worden gecontacteerd. Men spreekt van een cache miss. Collisions waarbij een cache regel te vroeg werd overschreven zijn zeer zeldzaam. Daarom presteren deze caches goed. Set-associatieve caches Deze benadering is een combinatie van de beide. Men staat nu toe dat een slot meerdere cache elementen bevat. Elke van deze cache-elementen moet nu weer door parallelle comparatoren worden onderzacht. De cache bestaat uit m slots met elke n elementen. Als m=1 is dit equivalent aan het geval van de associatieve cache. Als n=1 krijgen we het geval van de direct mapped cache. (merk ook de overeenkomst met seperate chaining op.) Bedenkingen bij het gebruik van caches Hoe moet de blokgrootte worden gekozen? Groot of klein? Elke benadering heeft zowel voordelen als nadelen. Problemen bij schrijfacties: er moet worden voor gezorgd dat de cache en het geheugen dezelfde waarde bevat. Twee mogelijk benaderingen: Write trough: bij een schrijfactie naar de cache wordt onmiddellijk het geheugen bijgewerkt. Er onstaat een permanente gelijkheid tussen cache en hoofdgeheugen. Deze benadering zorgt voor vertragingen bij schrijfacties. Copy back: bij een schrijfactie wordt enkel de cache zelf bijgewerkt. Het geheugen wordt pas aangepast op het moment dat de regel van de cache vervangen wordt. Dit is sneller maar vereist extra geheugenadministratie. De cache moet ook worden geleegd alvorens I/O transfers te doen. Een andere vraag is het schrijfbeleid. Wat moet er gebeuren als er wordt geschreven naar een woord dat niet aanwezig is in de cache? Moet deze in de cache worden gehaald, en daar worden bijgewerkt, of wordt er rechtsreeks naar het geheugen geschreven? Wat betreft adressen voor I/O: deze blokken mogen niet in de cache worden opgenomen. Als deze aanwezig zouden zijn, zouden er gegevens kunnen worden gelezen die intussen al vervangen zijn door de IO controller. 4.5.2 Sprongvoorspellingen Spronginstructies vormen een probleem bij pipelining. Pipelining werkt het best bij lineaire code. Zowel onvoorwaardelijke als voorwaardelijke sprongen zorgen voor problemen nij pipelining. Problemen ten gevolge van onvoorwaardelijke sprongen kunnen worden vermeden door het inlassen van delay slots. De verantwoordelijkheid om deze in te lassen ligt bij de compiler. De oorsprong van het probleem licht bij de feit dat het fetch stadium van de pipeline reeds de volgende instructie moet ophalen, nog voor het weet wat voor instructie de vorige is. Veel machines hebben de eigenschap dat bij een onvoorwaardelijke sprong de volgende instructie altijd wordt uitgevoerd. De compiler kan dit dan oplossen, door de volgorde van de instructies te wijzigen, en een nuttige instructie na de sprongopdracht te plaatsen. Dit is het delay slot. Als er geen nuttige instructie beschikbaar is om het delay slot op te vullen, moet er een NOP instructie worden gebruikt. Voorwaardelijke sprongen zijn nog vervelender, omdat de fetch unit pas vele instructies later in de pipeline weet wat de volgende instructie is die hij moet ophalen. De eenvoudigste benadering bestaat erin om te wachten met de verwerking van de volgende instructies tot bekend is naar waar moet gesprongen worden. Dit is echter nadelig voor de verwerkingssnelheid. De meeste machines proberen daarom te voorspellen welke sprongen moeten worden uitgevoerd: Een eerste eenvoudige benadering zegt dat alle achterwaartse sprongen moeten worden uitgevoerd, en voorwaartse sprongen niet moeten worden uitgevoerd. De redenering hierachter is dat achterwaartse sprongen meestal op het einde van lussen voorkomen, en deze in het merendeel van de gevallen moeten worden uitgevoerd. Problemen ontstaan wanneer de sprongvoorspelling foutief was en een instructie ten onrechte werd uitgevoerd, en deze ongedaan moet worden gemaakt. Om dit probleem op te lossen zijn er twee mogelijke benaderingen: De eerste benadering zegt dat de instructie na de voorspelling van de sprong mogen worden uitgevoerd, totdat een instructie de toestand van de machine (een register) probeert te wijzigen. In plaats van de schrijfopdracht uit te voeren naar het register, wordt de berekende waarde bijgehouden in een kladregister en pas daadwerkelijk naar het eigenlijke register geschreven als is vastgesteld dat de voorspelling correct was. De tweede benadering bestaat erin de toestand van de machine te bewaren op het punt dat er wijzigingen worden doorgevoerd. Indien een voorspelling foutief bleek te zijn, kan de toestand worden hersteld door een roll back uit te voeren. Naast de eenvoudige benadering, zijn er nog twee andere benadering om de voorspelling van sprongopdrachten uit te voeren. Dynamische sprongvoorspelling Dynamische sprongvoorspelling houdt in dat de sprongvoorspelling tijdens de uitvoering van het programma gebeurt. Dit laat toe dat de instructie op volle snelheid doorstromen. Het maakt gebruik van een historietabel die aangeeft of een sprong in een verleden al dan niet werd uitgevoerd. Op basis van deze waarde wordt er al dan niet gesprongen. De implementatie van de historietabel kan met dezelfde mogelijkheden als caches. Als de sprong foutief werd voorspeld, wordt hij in de historietabel als verkeerd gemerkt. Sommige implementaties hebben twee foutbits, zodat de sprong pas als foutief wordt aangemerkt wanneer de voorspelling twee maal achter elkaar fout bleek te zijn. Statische sprongvoorspelling Deze sprongvoorspelling vereist ook inspanningen van de compiler. Aan de instructieset van de machine voegt men een tweede set spronginstructies toe, die elk een vlag bevatten die aangeeft of de spronginstructie gewoonlijk wordt uitgevoerd of niet. De compiler kan bijvoorbeeld spronginstructies ten gevolge van een for lus merken met dit bit. Met behulp van profilering kan men het programma gesimuleerd laten uitvoeren, en het sprongprofiel bepalen. Deze informatie wordt dan aan de compiler gegeven, die betreffende sprong instructies merkt met het nodige bit. 4.5.3 Out-of-order uitvoering en herbenoemen van registers Sommige processoren staan toe dat afhankelijke instructies worden overgeslagen, om verder te gaan met onafhankelijke instructies. De instructies worden zo optimaal herordend om een verbeterde prestatie te bereiken. 4.5.4 Speculatieve uitvoering Het uitvoeren van code zonder dat bekend is of deze werkelijk moet worden uitgevoerd. 5 HET NIVEAU VAN DE INSTRUCTIESETARCHITECTUUR Programma’s in hogere programmeertalen worden vertaald naar een tussenniveau. Dit niveau is het ISA niveau. Doordat verschillende programmeertalen naar hetzelfde ISA niveau worden vertaald, is een systeem potentieel in staat om heel veel hogere programmeertalen te ondersteunen. 5.1 5.2 5.3 Instructieformaten Een instructie bestaat doorgans uit een opcode, en eventuele extra informatie over de oorsprong van de operanden, en de bestemming van de resultaten. Het algemene onderwerp van specificeren van locaties noemt men adresseren. De lengte van instructies kan variëren, zowel voor eenzelfde machine, als tussen de machines. Instructies met vaste lengte zijn eenvoudig te decoderen, en snel op te halen. Instructies met variabele lengte zorgt er dan weer voor dat er minder geheugen wordt verspild. Een ander probleem, is het probleem van de beperkte geheugenbandbreedte. Geheugens kunnen instructies niet aanleveren aan de snelheid waarmee de processor de instructies kan verwerken. Hoe korter de instructies, hoe meer instructies er per tijdseenheid kunnen worden aangeleverd. 5.3.1 Ontwerpcriteria voor instructieformaten Minimaliseren van de afmetingen van de instructies. Instructies zijn daardoor moeilijker te decoderen, maar dit laat toe om meer instructies per tijdseenheid aan de CPU aan te leveren, en de beperkte geheugenbandbreedte op te vangen. Binnen het instructieformaat moet voldoende ruimte aanwezig zijn om alle gewenste operaties te kunnen formuleren. De opcode moet bijvoorbeeld voldoende groot zijn om het gewenste aantal instructies te kunnen coderen. Een machine met 2 n instructies moet minstens n bits voor de opcode voorzien. Het aantal bits per adresveld. De lengte van het adresveld bepaalt de geheugenresolutie. 5.3.2 Expanderende opcodes De lengte van de instructies blijft vast, maar de ligging van de grens van de velden van de instructie kan variëren. Gaan we bijvoorbeeld uit van een 16 bit instructie met 4 bit opcode en 3 x 4 bit adresvelden, dan kunnen de eerste 15 opcodes (waarden 0000b to 0111b) beschouwd als instructies met 3 adresvelden. De volgende opcodes kunnen dan beschouwd worden als instructies met 8 bit opcodes, en twee adresvelden, enzovoort. 5.3.3 5.3.4 5.3.5 5.4 Adressering Adressering is de manier waarop wordt aangegeven waar de operanden van een instructie te vinden zijn. 5.4.1 Adresseringsmodi Het adresveld van de instructie kan op verschillende manieren worden geïnterpreteerd. Deze verschillende interpretaties zijn de verschillende adresseermodi. 5.4.2 Onmiddellijke adressering In plaats van de locatie van het operand op te geven, wordt het operand onmiddellijk meegegeven in de instructie zelf. (immediate operand) Er is dus geen geheugentoegang nodig om het operand op te halen. Nadelig is dan wel dat er enkel constanten kunnen worden meegegeven. 5.4.3 Absolute of directe adressering Het volledige adres wordt als operand gespecifieerd. Nu kan de waarde van het operand veranderen, maar deze wordt altijd uit dezelfde plaats van het geheugen opgehaald. Directe adressering kan bijgevolg enkel worden gebruikt om globale variabelen te benaderen, waarvan de locatie tijdens compilatie bekend is. In het geval van memory mapped IO kunnen ook 5.4.4 Registeradressering Deze aanpak is analoog aan directe adressering, het operand specifieert echter een register in plaats van een locatie in het hoofdgeheugen 5.4.5 Registerindirecte adressering De bestemming of bron van de operand ligt in het hoofdgeheugen, maar in plaats van deze expliciet mee te geven, wordt deze in een register geplaatst. Dit heeft als voordeel dat de instructie geen volledig geheugenadres hoeft te bevatten, maar wel het hele hoofdgeheugen kan bestrijken. Een bijkomend voordeel is dat het adres tijdens de loop van het programma veranderd kan worden. Dit is bijvoorbeeld nuttig bij het overlopen van een tabel in het geheugen. (Register incrementeren.) Sommige platformen hebben instructies waarbij voor of na een dereferentie van een register, de pointer die het register voorstelt kan worden verplaatst. (postincrement, predecrement) 5.4.6 Geïndexeerde adressering De adressering gebeurt nu via een constante offset, vermeerderd met inhoud van een register. Als constante offset kan bijvoorbeeld het beginadres van een tabel worden meegegeven. Om de individuele tabelelementen te benaderen, kan dan de waarde van het register worden veranderd. Deze addresseringswijze heeft als voordeel dat er geen volledig geheugenadres in de instructie hoeft worden opgenomen. Bijkomend kunnen de geheugenwoorden veranderen telkens de instructie wordt uitgevoerd. 5.4.7 Basisgeïndexeerde adressering In deze modus zijn wordt het effectieve adres bepaald door de som van twee registers vermeerderd met een constante offset. Een van de register fungeert als basis, het andere fungeert dan als offset. 5.4.8 Relatieve adressering Het adres van de operanden wordt relatief t.o.v. de programmateller genoteerd. Dit kan worden gebuikt als addresseringsmodus voor sprongopdrachten. Dit is evenwel niet altijd het geval, sommige platformen implementeren ook absolute sprongen. Een relatieve sprong heet een branch een absolute sprong is een jump. Alle voorwaardelijke sprongen zijn branches. Deze zijn beperkt tot een klein bereik, en staan positieonafhankelijk programmeren toe. Stack-adressering Er moeten geen adressen worden meegegeven. Alle operanden worden op de stapel geplaatst, de instructie haalt de nodige operanden van de stapel, en plaatst indien nodig het resultaat terug. Een eenvoudig voorbeeld van een taal die ook deze principes gebruikt is de reversed polish notation voor rekensommen. HP rekenmachines kunnen in deze modus worden geschakeld. In plaats van de operator tussen de operanden in te geven, worden eerst de operanden ingegeven, en dan de operator. Het resultaat van de bewerking wordt terug op de stapel geplaatst. 5.4.9 Adresseringsmodi voor sprongopdrachten 5.4.10 Orthogonaliteit van opcodes en adresseringsmodi Orthogonaliteit van opcodes: alle opcodes ondersteunen alle adresseringsmodi waar zinvol. Dit komt echter zelden voor. 6 HET ASSEMBLEERTAALNIVEAU Het onderscheid tussen interpretatie en vertaling is belangrijk. Bij vertaling wordt een programma eerst vertaald naar de doeltaal, waarbij er een uitvoerbaar programma wordt gegenereerd. Dit vertaalde programma wordt dan uitgevoerd. 6.1 Inleiding tot assembleertalen Als de brontaal een symbolische voorstelling is voor een machinetaal, en de doeltaal een machinetaal wordt het vertaalproces assembleren genoemd. De brontaal heet dan een assembleertaal, en de vertaler een assembler. 6.1.1 Wat is assembleertaal? Elk statement komt overeen met precies één instructie van de machinetaal. Er is dus een één op één correspondentie tussen de machinetaal en de assembleertaal. De assembleertaal wordt gebruikt zodat de programmeur enkel symbolische instructies zou moeten onthouden. Eenzelfde assembleertaal kan enkel slechts op een bepaalde familie van machines lopen. Hogere talen kunnen potentieel op verschillende families van machines lopen. 6.1.2 Waarom assembleertaal gebruiken Programmeren in assembleertaal heeft twee voordelen: Enerzijds is het sneller en zijn de programma’s kleiner dan die van in een hogere taal, anderzijds is er ook betere toegang tot de hardware van het systeem. Programmeren in assembleertaal is veel arbeidsintensiever in termen van opstellen van programma’s, debuggen en onderhoud. Bepaalde stukken van een programma in een hogere programmeertaal waarvan men merkt dat ze voor een groot deel de tijd van de processor in beslag neemt, of kritisch zijn kan men herschrijven in assembleertaal. Dit heet tuning. Als vuistregel geldt dat programma’s in assembleertaal 3 maal sneller zijn dan programma’s in een hogere taal, maar 5 keer moeilijker te ontwikkelen zijn. 6.1.3 Een statement in assembleertaal 6.1.4 Pseudo-instructies Een pseudo-instructie of assembler directieve is een instructie voor de assembler zelf. 6.2 Macro’s Als een bepaald stuk code veelvuldig voorkomt, kan men ofwel de instructies iedere keer herschrijven (vervelend) of wel de code onderbrengen in een procedure (aanroep moet iedere keer worden gecodeerd en is langzamer) Macro’s vormen een eenvoudige oplossing voor het aanroepen van een reeks gelijke of weinig verschillende instructies. 6.2.1 Defenitie, aanroep en expansie van macro’s Een macrodefinitie is een manier om een stuk tekst een naam te geven. De macro kan dan worden aangeroepen door de macronaam als instructie te gebruiken. Als de assembler deze naam ziet, zal hij deze ter plaatse expanderen (vervangen) door de tekst die hij voorstelt. Macro’s kunnen ook formele parameters hebben (in de definitie). Bij de aanroep van de macro worden deze dan vervangen door de actuele parameters. Bij de definitie van de macro worden er sleutelwoorden gebruikt die begin en einde aanduiden. Deze zijn afhankelijk van het platform. Macro’s kunnen genest worden. Ze mogen ook recursief worden aangeroepen. Macroaanroepen mogen niet verward worden met procedureaanroepen. Procedure aanroepen zijn machineinstructies, terwijl een macrodefinitie ene assembler directive is. 6.3 Het assembleerproces 6.3.1 Two-pass-assemblers Om het probleem van de voorwaartse referentie waarbij een symbool wordt gebruikt alvorens het gedefinieerd is op te lossen, wordt er twee keer door de code gegaan. In de eerste doorgang worden de definities van de symbolen ontdekt en bijgehouden, in de tweede doorgang wordt de broncode vertaald. One-pass assemblers vertalen het programma met een doorgang. Het programma wordt dan naar een soort tussenvorm vertaald, die gaandeweg wordt aangevuld wanneer er meer symbolen bekend worden. De kern van de assembler is de opcodetabel. De opcodetabel bevat de vertalingen tussen de mnemonics en de hexadecimale opcodes, als ook de lengte van de instructie, de klasse van de instructie en de types van de operanden. De symbooltabel bevat een de vertaling tussen de labels en de instructie locaties plus eventuele andere informatie. 6.3.2 Pass 1 De voornaamste functie van de eerste pass is het opbouwen van de symbooltabel, die de waarden en namen van alle gedefinieerde symbolen bevat. Voor de adressen van labels bij te houden gebruikt de assembler de instruction location counter (ILC) die bij iedere instructie wordt verhoogd met de lengte van de instructie. Elke mogelijke implementatie van een symbooltabel simuleert een associatief geheugen. Mogelijke implementaties zijn: Lineair zoeken in een ongeordende tabel Binair zoeken in een geordende tabel Hashing 6.3.3 Pass 2 Genereren van het objectprogramma, en produceren van de informatie die de linker eventueel nodig kan hebben. Eventueel wordt ook een listing bestand gegenereerd. 6.4 Linken en laden Procedures worden apart geassembleerd. Uit elke procedure bron ontstaat dan een objectbestand (een objectmodule). Alle objectbestanden moeten worden samengevoegd tot een uitvoerbaar binair programma. Dit proces heet linken, en wordt uitgevoerd door de linker. Deze werkwijze heeft het voordeel dat, wanneer een procedurebron wordt gewijzigd, het volledige binaire programma niet moet worden opnieuw geassembleerd. Enkel de gewijzigde procedure moet opnieuw worden geassembleerd, en de linkage moet opnieuw worden uitgevoerd. Verder zijn kleinere modules beter beheersbaar, en gaat de vertaling van apparte module sneller. Wanneer een programma gelinkt is, moet het uitvoerbare programma in het hoofdgeheugen worden geladen. 6.4.1 Door de linker uitgevoerde taken Elke geassembleerde module start bij adres 0, en stelt dus zijn eigen virtuele adresruimte voor. De linker moet dus deze adressen aanpassen. Daarbij zijn volgende problemen: Relocatieprobleem: referenties naar geheugenadressen kloppen niet meer, omdat de modules in het geheugen verschoven zijn. Op systemen met gesegmenteerde adresruimten is dit eenvoudig op te lossen door elke module in zijn eigen segment de plaatsen. De systemen waarop dit toepasbaar is, zijn echter zeldzaam. Probleem van externe referenties: Bij linken is het adres van een andere module die wordt aangeroepen met CALL nog niet bekend. De linker voert de volgende taken uit om beide problemen op te lossen: Constructie van een tabel met object modules en hun lengte Op basis van de geconstrueerde tabel wordt aan iedere module een laad adres toegekend. Alle instructies die een adres bevatten worden verhoogd met een relocatieconstante. Alle instructies met aanroepen van procedures worden van een correct adres voorzien. 6.4.2 Structuur van een objectmodule Figuur 7-16, pg. 551. Identificatie: naam, lengte, datum Ingangentabel: symbolen die door andere modules worden gebruikt Machines-instructies en constanten Relocatie-dictionary: lijst van de te wijzigen adressen Einde van de module Merk op dat enkel de machine-instructies uiteindelijk in het geheugen zullen worden geladen. 6.4.3 Bindingstijdstip en dynamische relocatie Bij het in- en uitswappen van programma’s is er geen garantie dat een programma twee keer op dezelfde plaats terechtkomt. De adressen zijn dus na een swapin mogelijks verkeerd, en de relocatie gegevens zijn dan niet meer beschikbaar. Het bindinstijdstip is het tijdstip waarop aan de symbolische namen het werkelijke geheugenadres wordt toegekend. Een programma verplaatsen na de binding levert fouten op. Een aantal mogelijke bindinstijdstippen: Bij het schrijven Bij de vertaling Bij het linken, voor het laden Bij het laden Bij het laden van een basisregister dat voor adressen wordt gebruikt Bij het uitvoeren van de instructie Bij het binden zijn er twee aspecten die van belang zijn: Wanneer wordt aan een symbolische naam een virtueel adres toegekend? Dit gebeurt door de linker. Wanneer wordt aan een virtueel adres een fysiek adres toegekend? Hiervoor zijn drie benaderingen gangbaar: Het programma kan worden verplaatst, enkel de paginatabel wordt aangepast, niet het programma. Dit is positie onafhankelijk programmeren. Werken met een relocatieregister. Geheugenreferenties relatief maken t.o.v. PC. Als het programma wordt verplaatst, hoeft enkel de PC worden aangepast. 6.4.4 Dynamisch linken Dynamisch linken houdt in dat een procedure pas wordt gelinkt op het moment dat ze wordt aangeroepen, tijdens de uitvoering van het programma. Dynamisch linken in MULTICS Elk programma bevat een linksegment, dat een blok informatie bevat voor elke procedure die kan worden aangeroepen. Dit blok bevat onder andere het virtuele adres van de procedure, en de string naam van de procedure. Procedure aanroepen van de brontaal worden vertaald naar instructies die indirect het eerste woord van het corresponderende linkblok adresseren. De compiler vult dit blok op zodanig dat er een trap wordt veroorzaakt. De trap start dan de linker op, die de naam van de procedure opzoekt, de procedure linkt, en het adres in het linkblok aanpast. De instructie die de trap veroorzaakte wordt opnieuw uitgevoerd, het programma kan dan verder lopen. Dynamisch linken in OS/2 Het hoofdprogramma doet een systeemaanroep met de vraag een bepaalde file op te zoeken op disk, deze in het geheugen te laden, en de locatie van de file in het geheugen terug te geven. Dit heeft als voordeel dat programma’s uit te bereiden zijn door files op disk te plaatsen. Dynamisch linken in windows Er wordt gebruik gemaakt van speciale files zoals dll’s. De dll bevat procedures en/of data. Dit laat toe om een bibliotheek van procedures te sharen door meerdere processen, zodat er geheugen wordt bespaard. Elke dll bevat naast de gewone procedures en data ook minstens Een procedure voor allocatie Een procedure voor deallocatie Allocatie kan gebeuren Impliciet (of statisch): de dll’s worden vermeld in een import library, en worden bij opstarten van proces allen ingeladen Expliciet: er wordt at run-time gelinkt door een dll aan te roepen als die nodig is. Dynamisch linken in UNIX Gebeurt zoals in windows, met impliciete allocatie van een shared library.