Processoren/Practicum
Inhoud
- 1 Inleiding
- 2 Tips voor het gebruik van HADES
- 3 Hulpbestanden
- 4 Structuur van de CPU
- 5 Architectuur
- 6 Instructies
- 7 Onderdelen
- 8 Samenvoegen en testen
- 9 Beoordelingscriteria
Inleiding
Doel van deze opdracht is het een werkende, eenvoudige microprocessor te maken volgens de specificaties in deze tekst. Concreet maak je de volgende onderdelen:
- de HADES-bestanden die de CPU beschrijven (als .zip of .tar.gz-bestand). Alle bestanden moeten in dezelfde map geplaatst worden. Lever alleen de .hds- en .sym-bestanden in die bij jouw definitieve uitwerking horen; bestanden met een cijfer (.hds_0, .sym_1 etc.) zijn backups die ik niet nodig heb.
- voor elk instructieformaat en voor de fetch-fase een datastroomdiagram dat laat zien welk onderdeel van de CPU welke taak uitvoert, liefst als .pdf-bestand.
- een C- of Java-programmafragment met een vertaling ervan naar practicum-assembly, ca. 20 assembly-instructies lang. (Op basis van het programmafragment kan ik zien wat de assembly-instructies zouden moeten berekenen.) Het assembly-programma mag bij vertaling met de practicum assembler geen syntaxfouten e.d. veroorzaken. Een Windows7 executable zou met deze link te downloaden moeten zijn.
Deadlines
Je wordt geacht ongeveer 32 uur aan deze practicumopdracht te besteden. Opdat jullie niet te laat aan de practicumopdracht beginnen (Dit doen in de laatste week voor de finale inleverdatum is een gegarandeerd recept voor een ramp), wil ik graag dat jullie halverwege december een eerste ontwerp inleveren. Via de huiswerkopgaven zullen jullie ook hints krijgen welke delen van de practicumprocessor al eerder ontworpen kunnen worden.
- 19 december 2014
- Principiële opbouw van de CPU met nadruk op datastromen.
- een datastroom-schema dat aangeeft welke interne data-, adres- en controlebussen er in de CPU zijn (zie ook hieronder, bij Interne bussen en instructie-decodeerder);
- per instructieformaat een beschrijving hoe die bussen gebruikt worden.
- 16 januari 2015 23.59.59 CET
- Definitieve uitwerking van de practicumopdracht, waarbij je ook een kort verslag inlevert (Hieronder aan de pagina staat wat daarin verwacht wordt). Lever ook het (verbeterde) datastroom-schema in.
- Datum van de herkansing (vrijdag 8 mei 12.30 CEST)
- Herkansing van de definitieve uitwerking.
Lever alle onderdelen in bij mij, Marc Seutter, liefst per e-mail. Pas bij het inleveren er wel voor op dat je geen exe bestand probeert mee te sturen: vorig jaar werden bij mij een aantal zip bestanden ingeleverd met daarin de executable van de assembler. Aangezien dit door de mailhandler van de faculteit als een virus wordt gekarakteriseerd, belanden deze mails in de quarantaine inbox. Voor overschrijding van elke deadline geldt: 1 minuut tot 2 uur te laat is –0,25 op je practicumcijfer; 2–24 uur te laat is –0,5 op je practicumcijfer; meer vertraging bij de voorlopige deadlines is –1 op je practicumcijfer, en meer dan 24 uur vertraging bij de laatste deadline geldt als niet ingeleverd.
Tips voor het gebruik van HADES
Met de tool HADES teken je de onderdelen van de processor, orden je ze hiërarchisch en simuleer je ze.
Hierarchisch ontwerp
Je kunt een ontwerp in HADES verdelen over meerdere bestanden. Maak eerst de basisbouwsteen, sla hem op en kies in het menu Edit > Create Symbol. Daarna kun je in een ontwerp op een hoger niveau met contextmenu > Create > Create subdesign... de basisbouwsteen als blokje openen. De Ipins van de basisbouwsteen worden invoerlijnen van het blokje, de Opins worden uitvoerlijnen.
HADES-bestanden op meerdere computers
In de hiërarchie gebruikt HADES absolute paden die van één bestand naar een ander te verwijzen; als je de bestanden verplaatst moet je met een teksteditor de paden aanpassen. Dat is redelijk eenvoudig als alle bestanden in dezelfde map staan; als je meerdere verschillende mappen gebruikt kan ik het moeilijk nakijken.
Sneltoetsen
Kijk in de HADES-Kurzreferenz (duits) of het HADES-tutorial (engels). Het is handig een aantal sneltoetsen te kennen:
- m verschuif de component onder de muis; M verschuif het draadeinde onder de muis
- c kopiëer de component onder de muis
- e bewerk het subdesign onder de muis of de parameters van de component onder de muis (b.v. aantal bits van een register)
- w creëer een aftakking van het draadje onder de muis
- x wis het draadsegment onder de muis
- z zoom out; Z zoom in
Meer sneltoetsen vind je op pagina 47 van de HADES-tutorial.
Signalen in HADES
Er zijn 2 soorten signalen in HADES nl. enkele draadjes en bundels van draden. Voor de eerste soort, een enkele draad, wordt bij componenten aangegeven dat deze van het type StdLogic1164 is. Voor de tweede soort, een bundel van draadjes wordt bij componenten aangegeven dat deze van het type StdLogicVector is.
Beide namen zijn afkomstig van het std_logic_1164 package gedefiniëerd in de IEEE1164 standaard waarin voor de hardware beschrijvingstaal VHDL het simulatiemodel voor enkele signaaldraden en bundels van signaaldraden is vastgelegd. HADES volgt dit model in zijn eigen simulatie. Dit model definieert 9 verschillende waarden die een draad kan aannemen. Voor onze simulaties in het practicum zijn er maar 5 van belang:
- U Uninitialized: nog geen waarde gekregen
- X Unknown: HADES kan deze draad geen waarde geven, meestal geeft dit een foutsituatie aan
- 0 0
- 1 1
- Z High impedance: deze draad is met een tri-state driver stroomloos gezet.
Zoals hierboven al verteld hebben bundels van draden het type StdLogicVector. Meestal korten we dit af en spreken over een inputvector of outputvector. Wat wel van belang is dat als componenten vectoren als input of output hebben je de breedte van deze vectoren moet configureren in de component voordat je er een draad of bundel van draden mee verbindt. HADES staat het namelijk niet toe dat je een component herconfigureert als er al iets mee verbonden is.
Aanbevolen componenten
Ik beveel aan in de uitwerking van de practicumopdracht de volgende elementen uit de component library gebruiken:
- built-in:hades.models.rtlib.muxes.Mux21 en Mux41 (multiplexers: zij kiezen één van de mogelijke ingangen, op basis van een selectie-invoer, en geven die door);
- rtlib.muxes.TriBuf (een tri-state buffer: als de selectie-invoer = 0 is, wordt de uitvoer losgekoppeld van de invoer);
- rtlib.io.Expander en MergeBits (maakt van een vector een reeks van losse draden en omgekeerd);
- rtlib.io.SelectBit (geeft één bit uit een vector door);
- rtlib.io.Subset (geeft een aantal bits uit een vector door);
- rtlib.io.Merge (voegt twee vectoren samen in één grote vector. Er is een bug in HADES waardoor je geen 32-bit-merger kunt maken; dan moet je helaas Expander en MergeBits-componenten gebruiken. Bij kleinere mergers moet je soms twee keer op Apply klikken voordat alle aantallen bits goed opgeslagen worden.);
- rtlib.muxes.BitDecoder (zet één van zijn 2n uitvoerlijnen op 1, afhankelijk van het ingevoerde getal);
Je mag ook andere elementen gebruiken die ongeveer even complex zijn als de genoemde; de belangrijkste noem ik nog verderop. Maar het is niet de bedoeling de component rtlib.arith.UserDefinedALU o.i.d. te gebruiken, want dan zie je niet hoe de ALU opgebouwd is.
Meer tips
- Als de aansluitingen van een component op de juiste plaats lijken te liggen maar niet werken, probeer contextmenu > wire > autoconnect SimObject. Dan worden alle in- en uitgangen van het object waar de muis naar wijst aangesloten aan draadjes die op de juiste plek liggen.
Hulpbestanden
- computer.hds is een globaal design waar je je CPU in kunt plakken.
- Het RAM wordt automatisch geinitialiseerd met de gegevens die in bestand hello.rom staan (in dezelfde directory als computer.hds). Je kunt b.v. het .rom-bestand van het eerste voorbeeldprogramma gebruiken. Als je het RAM wilt bekijken of veranderen, kies het menupunt “edit” (of toets 'e' terwijl de muis naar het RAM wijst).
- timer.hds en timer.sym zijn de bestanden voor de timer, het onderdeel van de practicum-processor dat ik je aanbied. Zie hieronder voor een gedetailleerde beschrijving.
- De practicum assembler is een eenvoudige assembler voor de assembly-taal die de opgave voorschrijft. Onder Unix/Linux moet je de tarball behandelen als elke ander GNU tar ball (./configure; make; make install). Voor Windows gebruikers vind je in de msvc100 subdirectory een solution om de assembler mee te bouwen. Bij Aia heb ik een Windows 7 executable gebouwd die met deze link te downloaden moet zijn.
- Deze assembler nog niet volledig getest. Als iemand nog fouten vindt, zullen ze gefixt worden.
- relocate.sh is een programma dat het makkelijker maakt HADES-bestanden van iemand anders te gebruiken.
- Ga in een shell (of commando-venster) naar de map waarin de .hds-bestanden staan en start daar relocate.sh op. Het programma vervangt alle absolute paden van subdesigns door „./”, d.w.z. er wordt aangenomen dat de subdesign in dezelfde map staat als de hoofddesign.
- Als je sommige esoterische ingebouwde componenten (b.v. built-in:hades.models.rtl.Mux8_1) gebruikt worden die na het toepassen van deze utility niet meer correct weergegeven; dan moet je het backup-bestand (.hds.bak) terughalen en met de hand bewerken.
Structuur van de CPU
Een voorstel voor een hiërarchische structuur van de CPU wordt getoond in de afbeelding hieronder. De onderdelen Timer en Computer staan ter beschikking in vorm van .hds-bestanden; de overige vierkante onderdelen zijn in de library voorgedefiniëerd. Van de ronde onderdelen moeten jullie een implementatie (een .hds- en een .sym-bestand) verzorgen; je mag o.a. gebruik maken van de vierkante onderdelen, die in HADES ingebouwd zijn. Je mag ook van dit voorstel afwijken, b.v. kan het zinvol zijn een functie in meerdere onderdelen op te splitsen.
De ImageMap-extensie is niet geïnstalleerd.
Voor de instructiedecoder stel ik voor dat je een opdeling maakt per instructieformaat: Eén onderdeel bepaalt welk formaat van instructie wordt uitgevoerd, en de overige onderdelen genereren de juiste signalen. In de decoder moet je dan ervoor zorgen dat het juiste signaal wordt doorgegeven aan de besturingslijnen van de ALU, de registers etc. Fetchen kun je ook als een instructieformaat behandelen. Bedenk ook waar je eventueel functionaliteit kan delen.
De ImageMap-extensie is niet geïnstalleerd.
Architectuur
Algemeen
De CPU werkt met 32 bits: registers, adressen en geheugeninhoud is telkens 32 bits breed. De CPU heeft 16 registers, R0 tot R15. Het register R0 bevat altijd de waarde 0; R15 is tegelijk de programmateller. Daarnaast heeft de CPU vier aparte vlaggen: negative, overflow, zero en carry.
Externe aansluitingen van de CPU
De CPU heeft de volgende externe aansluitingen:
Uitvoer:
- een 32-bit adresbus
- een 32-bit databus voor uitvoer van de CPU
- een besturingslijn nWE; als deze lijn 0 is wil de processor gegevens in het RAM laten opslaan. (De CPU gaat ervan uit dat bij het eerstvolgende rising edge van de klok de gegevens worden opgeslagen.)
- een besturingslijn nRE; als deze lijn 0 is wil de processor gegevens uit het RAM lezen. (De CPU gaat ervan uit dat het RAM bij het eerstvolgende rising edge van de klok de gegevens op de databus heeft gezet.)
- een besturingslijn HALTED; als deze lijn 1 is, is de processor gestopt.
Invoer:
- een 32-bit databus voor invoer in de CPU
- een besturingslijn nRESET: als deze lijn 0 is, gaat de processor zo snel mogelijk in de begintoestand; zodra de lijn op 1 gaat, begint hij te werken.
- een besturingslijn CLK die klokpulsen aan de CPU geeft. Let erop dat je geen andere klokken in de CPU inbouwt.
We hoeven de stroomtoevoer in HADES niet te modelleren.
Geheugen en in-/uitvoer
De CPU kan 232 verschillende adressen genereren; op elk adres wordt één byte opgeslagen. In de praktijk worden alleen adressen gebruikt die een veelvoud zijn van 4 omdat het RAM telkens vier bytes samen (= 32 bits, zoveel gegevens als de databus aankan) leest of schrijft. De computer heeft 256 KB RAM, in de praktijk gebruik je dus alleen adressen 0000 0000hex t/m 0003 FFFChex. De adressen FFFF FF00hex–FFFF FFFChex dienen voor in- en uitvoer, onafhankelijk ervan welk adres in dat bereik gekozen wordt. Als de CPU iets in die adressen opslaat, worden de laagste 8 bit van het opgeslagene als ASCII-code geïnterpreteerd en op het scherm getoond. Als de CPU iets van die adressen leest, krijgt hij FFFF FFFFhex als de gebruiker sinds de laatste lees-operatie geen toets gedrukt heeft, en anders de ASCII-code van de gedrukte toets.
Begintoestand
Als de computer start, worden alle registers (ook de programmateller) en de vlaggen op 0 gezet. Dat betekent dus dat het bootstrap-programma op adres 0 opgeslagen moet worden.
Instructies
Alle instructies hebben in de vier hoogste bits een condition. Dit veld wordt ná de instructieformaten uitgelegd. De meeste instructies geven dan in de volgende 4 bits (bits 27 t/m 24) het doel-register aan: 0000bin betekent R0, 0001bin betekent R1, ..., 1111bin betekent R15. Andere registers worden op dezelfde manier opgeslagen. Een signed constante is een constante in twee-complement. Bij de onderstaande intructies heeft ze altijd 10 bits; ze ligt dus tussen –512 en +511. Als je ermee gaat rekenen, moet je deze constante eerst d.m.v. sign extension opschalen naar een constante met 32 bits. (Voor degenen die te lui zijn om op deze link te klikken: Dat betekent dus dat je de hoogste bit van de 10-bits-constante zo vaak moet herhalen tot het 32 bits zijn).
Het is de programmeur verboden andere machinecodes te gebruiken dan degene die hieronder gedefiniëerd zijn; als hij het toch doet is het gevolg onbepaald. Dat kan jullie helpen om de eenvoudigste manier te vinden de operaties te implementeren.
format | bits | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
1 | condition | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |||
2a | condition | dest reg | addr reg | 0 | 1 | signed constant (offset) | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ||||||||||||||||||
2b | condition | 0 | 0 | 0 | 0 | addr reg | 0 | 1 | signed constant (offset) | src reg | 1 | 1 | 0 | 0 | ||||||||||||||||||
3 | condition | dest reg | constant | 1 | 0 | |||||||||||||||||||||||||||
4a | condition | dest reg | src reg B | flg | 1 | signed constant (source A) | 0 | 0 | 0 | 0 | opcode | 1 | ||||||||||||||||||||
4b | condition | dest reg | src reg B | flg | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | src reg A | opcode | 1 | ||||||||||||||
5a | condition | dest reg | src reg B | flg | 1 | signed constant (source A) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | ||||||||||||||||||
5b | condition | dest reg | src reg B | flg | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | src reg A | 0 | 1 | 0 | 0 |
Instructieformaat 1: HALT
Instructieformaat 1 is bedoeld voor de HALT instructie. HALT stopt de CPU; ze doet dan niets meer tot ze gereset wordt. In het bijzonder zet ze de uitvoer HALTED op 1. Net als bij andere instructies die geen RAM gebruiken blijft na HALT nRE en nWE op 1. Net als alle andere instructies is deze conditioneel.
Instructieformaat 2: Geheugentoegang
Instructieformaat 2a is bedoeld voor de READ-instructie en 2b voor WRITE. De instructies lezen uit het RAM en schrijven naar het RAM. Het adres waaruit ze lezen/waarnaar ze schrijven is de som van het adresregister en een constante (de offset); een eventuele carry mag je negeren. Je moet dus, net als bij een additie met een constante, de inhoud van het adresregister en de constante door de ALU laten optellen en het resultaat ervan naar de adresbus doorsturen.
instructie | werking | ||
---|---|---|---|
READ.cond | [addr reg + constante], dest reg | lees uit het RAM, vanaf adres (addr register + constante), en sla het resultaat in dest reg op. | |
WRITE.cond | src reg, [addr reg + constante] | schrijf naar het RAM, op adres (addr register + constante), de inhoud van register src reg. |
De assembly-programmeur moet ervoor zorgen dat het berekende adres een veelvoud van 4 is; de CPU hoeft dat niet zelf te controleren.
Instructieformaat 3: Constanten inladen
Omdat een register net zo groot is als een instructie is het onmogelijk een instructie te construeren waarmee je een willekeurig register op een willekeurige waarde kunt zetten. Instructieformaat 3 is zo ontworpen dat je met hoogstens twee instructies een register op een willekeurige waarde kunt zetten.
instructie | werking | ||
---|---|---|---|
LOADHI.cond | constante, dest reg | verschuift constante met 10 bits naar links en laadt het resultaat daarvan in het dest reg. |
Je kunt dus met LOADHI de hoogste 22 bits van een register bepalen en daarna met ADD of OR de laagste 10 bits. Als de constante een veelvoud is van 210, dan is LOADHI alleen voldoende. Bij kleine constanten is ADD of OR alleen voldoende.
Instructieformaat 4: Rekenoperaties
Rekenoperaties lezen twee bronnen. Eén bron (B) is altijd een register en de tweede bron (A) kan een register of een constante zijn; bit 18 geeft het type van bron A aan. Ze voeren een berekening daarop uit en slaan het resultaat op in het destination register. Rekenoperaties en roteeroperaties (formaat 5) kunnen de vlaggen veranderen, afhankelijk van het resultaat van de berekening. De flg-bit geeft aan of dat moet: flg=0 betekent vlaggen niet veranderen, flg=1 betekent vlaggen wel veranderen.
De volgende operaties zijn mogelijk:
opcode (binair) |
instructie | werking op registers | werking op vlaggen (als flg=1 is) | |||||
---|---|---|---|---|---|---|---|---|
000 | OR(f).cond | src A, src reg B, dest reg | dest reg := src A | src reg B | Overflow := 0; Carry := 0 |
Zero := 1 als het resultaat = 0 is; Sign := (bit 31 van het resultaat) | |||
001 | XOR(f).cond | src A, src reg B, dest reg | dest reg := src A xor src reg B | |||||
010 | AND(f).cond | src A, src reg B, dest reg | dest reg := src A & src reg B | |||||
011 | BIC(f).cond | src A, src reg B, dest reg | dest reg := src A & ~src reg B | |||||
100 | ADD(f).cond | src A, src reg B, dest reg | dest reg := src A + src reg B | Overflow: zie het hoorcollege van 15 december; Carry := 1 als het resultaat eigenlijk 33 bits groot zou zijn. | ||||
101 | SUB(f).cond | src A, src reg B, dest reg | dest reg := src A + ~src reg B + 1 (dat is: dest reg := src A – src reg B) | |||||
110 | ADC(f).cond | src A, src reg B, dest reg | dest reg := src A + src reg B + Carry | |||||
111 | SBC(f).cond | src A, src reg B, dest reg | dest reg := src A + ~src reg B + Carry (dat is: dest reg := src A – src reg B – ~Carry) |
Aftrekking
Hierboven heb ik aangegeven dat je de aftrekking moet implementeren als src A + ~src reg B + 1, dus het 1-complement van src reg B en 1 optellen bij src A. Dat zorgt ervoor dat de vlaggen precies goed gezet worden. Als je in plaats daarvan het 2-complement van src reg B bij src A optelt, krijg je een verkeerde carry-vlag als je 0 aftrekt, en een verkeerde overflow-vlag als je –231 (8000 0000hex) aftrekt. HADES biedt een subtractiewerk aan, maar dat kun je niet gebruiken omdat het helemaal geen carry-vlag produceert.
Instructieformaat 5: Roteeroperaties
Roteeroperaties lezen twee bronnen. Eén bron (B) is altijd een register en de tweede bron (A) kan een register of een constante zijn; bit 18 geeft het type van bron A aan. Bron B wordt geroteerd om 'Bron A' bits naar links en het resultaat wordt opgeslagen in het destination register. Roteeroperaties (formaat 5) kunnen de vlaggen veranderen, afhankelijk van het resultaat van de berekening. De flg-bit geeft aan of dat moet: flg=0 betekent vlaggen niet veranderen, flg=1 betekent vlaggen wel veranderen. Dit instructieformaat kent een specifieke uitzondering voor het geval dat bron A de constante 0 is, hetgeen door de instructiedecoder herkend kan worden doordat bit 18 = 1 (formaat 5a) en bits 17 t/m 8 allemaal 0. In dit geval vormen de carry en bron B een 33 bits woord dat over 1 bit naar links wordt geroteerd.
instructie | werking | ||
---|---|---|---|
ROL(f).cond | src A, src reg B, dest reg | dest reg := src reg B, om (src A & 31) bits naar links geroteerd tenzij src A de constante 0 is. | |
ROLC(f).cond | src reg B, dest reg | dest reg := (de carry geconcateneerd met src reg B) om 1 bit naar links geroteerd. Bit 0 van het resultaat wordt dus bepaald door de waarde van de Carry. |
Het effect op de vlaggen als flg=1 is overeenkomstig de rekeninstructies, behalve voor de Carry: Zero := 1 als het resultaat 0 is, Sign := (bit 31 van het resultaat). Overflow := (bit 31 van src reg B) xor (bit 31 van het resultaat). Als bron A niet de constante 0 is (het normale geval) dan geldt Carry := bit 0 van het resultaat. Indien bron A wel de constante 0 is, geldt dat Carry := bit 31 van src reg B
Condities
Alle instructies kunnen voorwaardelijk worden uitgevoerd. Dat betekent dat het destination register en de vlaggen alleen worden veranderd als de vlaggen (voordat deze instructie wordt uitgevoerd) een bepaalde voorwaarde aangeven. Net zo mogen de READ en WRITE instructie alleen een operatie op het geheugen doen en de HALT instructie de processor stoppen als vooraf aan de voorwaarde voldaan wordt. De voorwaarde wordt in bits 31–28, het condition-veld, aangegeven. De mogelijke voorwaarden zijn:
Condition (binair) | benaming | uitleg | vlaggen-voorwaarde |
---|---|---|---|
0000 | NC, LU | not carry, less (unsigned) | C = 0 |
0001 | C, GEU | carry, greater or equal (unsigned) | C = 1 |
0010 | NO | not overflow | O = 0 |
0011 | O | overflow | O = 1 |
0100 | NZ | not zero | Z = 0 |
0101 | Z | zero | Z = 1 |
0110 | NN | not negative | N = 0 |
0111 | N | negative | N = 1 |
1000 | LEU | less or equal (unsigned) | C & ~Z = 0 |
1001 | GU | greater (unsigned) | C & ~Z = 1 |
1010 | GE | greater or equal (signed) | N xor O = 0 |
1011 | L | less (signed) | N xor O = 1 |
1100 | G | greater (signed) | Z | (N xor O) = 0 |
1101 | LE | less or equal (signed) | Z | (N xor O) = 1 |
1110 | F | nooit (false) | 1 = 0 |
1111 | T | altijd (true) | 1 = 1 |
Bij het opschrijven van een instructie in practicumassembly mag je de conditie .T (altijd uitvoeren) weglaten. De assembler vertaalt dit automatisch naar een conditieveld 1111.
Voorbeelden
instructie | machinecode (hexadecimaal) |
werking | |||
---|---|---|---|---|---|
ADD | R5, R2, R3 | f320 0059 | zet R3 := R5 + R2 | ||
SUBf | 2, R4, R6 | f64c 020b | zet R6 := 2 – R4 en verander de vlaggen | ||
ROL.NN | 3, R10, R10 | 6aa4 0304 | roteer R10 om 3 bits naar links, maar alleen als hiervoor de negative-vlag op 0 stond | ||
ANDf.Z | 1, R2, R9 | 592c 0105 | zet R9 := 1 & R2 en verander de vlaggen, maar alleen als hiervoor de zero-vlag op 1 stond | ||
ADDf | –1, R7, R0 | f07f ff09 | bereken R7 – 1 en zet de vlaggen, maar vergeet het resultaat. | ||
READ.C | [R3+12], R2 | 1234 0c08 | lees uit het RAM, van het adres (R3 + 12), en sla het resultaat in register R2 op, maar alleen als hiervoor de carry-clag op 1 stond | ||
WRITE | R0, [R6–28] | f067 e40c | sla de inhoud van R0 in het RAM op, op adres (R6 – 28) |
Afkortingen
Om het schrijven van assembly-programma’s iets makkelijker te maken, kent de assembler nog een paar afkortingen. Deze zijn niet echt instructies, maar worden vertaald naar bestaande instructies.
afkorting | vertaling naar instructie | betekenis | ||||
---|---|---|---|---|---|---|
CMPf.cond | src A, src reg B | SUBf.cond | src A, src reg B, R0 | vergelijk twee operanden en zet de vlaggen | ||
JUMP.cond | label | ADD.cond | label – ($+4), R15, R15 | spring naar een bepaalde label ($ = adres waar deze instructie wordt opgeslagen.) | ||
MOVE(f).cond | src, dest reg | OR(f).cond | src, R0, dest reg | zet register dest reg op waarde src (dat kan een register of een kleine constante zijn). | ||
NEG(f).cond | src reg, dest reg | SUB(f).cond | 0, src reg, dest reg | bereken het 2-complement van src reg en sla het resultaat in dest reg op. | ||
NOP | OR | R0, R0, R0 | doe niets. | |||
NOT(f).cond | src reg, dest reg | XOR(f).cond | –1, src reg, dest reg | draai alle bits van src reg om en sla het resultaat in dest reg op. | ||
ROR(f).cond | constant, src reg, dest reg | ROL(f).cond | –constant, src reg, dest reg | roteer src reg om constante bits naar rechts en sla het resultaat in dest reg op. |
Alle instructies en afkortingen (alfabetisch)
Bij veel instructies kun je kiezen of de vlaggen veranderd moeten worden (instructienaam met f) of gelijk moeten blijven (instructienaam zonder f). Bij CMPf is de tweede variant niet zinvol.
instructie (of afkorting) | werking | ||
---|---|---|---|
ADC.cond | src A, src reg B, dest reg | Addition with carry: dest reg := src A + src reg B + Carry | |
ADCf.cond | src A, src reg B, dest reg | Addition with carry: dest reg := src A + src reg B + Carry, en zet de vlaggen. | |
ADD.cond | src A, src reg B, dest reg | Addition: dest reg := src A + src reg B | |
ADDf.cond | src A, src reg B, dest reg | Addition: dest reg := src A + src reg B, en zet de vlaggen. | |
AND.cond | src A, src reg B, dest reg | Bitwise and: dest reg := src A & src reg B | |
ANDf.cond | src A, src reg B, dest reg | Bitwise and: dest reg := src A & src reg B, en zet de vlaggen. | |
BIC.cond | src A, src reg B, dest reg | Bitwise clear: dest reg := src A & ~src reg B | |
BICf.cond | src A, src reg B, dest reg | Bitwise clear: dest reg := src A & ~src reg B, en zet de vlaggen. | |
CMPf.cond | src A, src reg B | Compare: Bereken src A – src reg B en zet de vlaggen, maar vergeet het resultaat. | |
HALT.cond | Halt: Stop de processor. | ||
JUMP.cond | label | Jump: Spring (in het programma) naar het adres label. | |
LOADHI.cond | constante, dest reg | Load into highest bits: Verschuif constante met 10 bits naar links en laad het resultaat daarvan in het dest reg. | |
MOVE.cond | src, dest reg | Move: Kopiëer src naar dest reg. | |
MOVEf.cond | src, dest reg | Move: Kopiëer src naar dest reg, en zet de vlaggen. | |
NEG.cond | src reg, dest reg | Negate: dest reg := –src reg | |
NEGf.cond | src reg, dest reg | Negate: dest reg := –src reg, en zet de vlaggen. | |
NOP | No operation: Doe niets. | ||
NOT.cond | src reg, dest reg | Bitwise not: dest reg := ~src reg | |
NOTf.cond | src reg, dest reg | Bitwise not: dest reg := ~src reg, en zet de vlaggen. | |
OR.cond | src A, src reg B, dest reg | Bitwise or: dest reg := src A | src reg B | |
ORf.cond | src A, src reg B, dest reg | Bitwise or: dest reg := src A | src reg B, en zet de vlaggen. | |
READ.cond | [addr reg + constante], dest reg | Read from RAM: Lees uit het RAM, vanaf adres (addr register + constante), en sla het resultaat in dest reg op. | |
ROL.cond | src A, src reg B, dest reg | Rotate left: dest reg := src reg B, om src A bits naar links geroteerd. | |
ROLf.cond | src A, src reg B, dest reg | Rotate left: dest reg := src reg B, om src A bits naar links geroteerd, en zet de vlaggen. | |
ROLC.cond | src reg, dest reg | Rotate left with carry: (Carry . dest reg) := (Carry . src reg), om 1 bit naar links geroteerd. | |
ROLCf.cond | src reg, dest reg | Rotate left with carry: (Carry . dest reg) := (Carry . src reg), om 1 bit naar links geroteerd, en zet de vlaggen. | |
ROR.cond | src constante A, src reg B, dest reg | Rotate right: dest reg := src reg B, om src constante A bits naar rechts geroteerd. | |
RORf.cond | src constante A, src reg B, dest reg | Rotate right: dest reg := src reg B, om src constante A bits naar rechts geroteerd, en zet de vlaggen. | |
SBC.cond | src A, src reg B, dest reg | Subtract with carry: dest reg := src A – src reg B – ~Carry | |
SBCf.cond | src A, src reg B, dest reg | Subtract with carry: dest reg := src A – src reg B – ~Carry, en zet de vlaggen. | |
SUB.cond | src A, src reg B, dest reg | Subtract: dest reg := src A – src reg B | |
SUBf.cond | src A, src reg B, dest reg | Subtract: dest reg := src A – src reg B, en zet de vlaggen. | |
WRITE.cond | src reg, [addr reg + constante] | Write to RAM: Schrijf naar het RAM, op adres (addr register + constante), de inhoud van register src reg. | |
XOR.cond | src A, src reg B, dest reg | Bitwise exclusive-or: dest reg := src A ^ src reg B | |
XORf.cond | src A, src reg B, dest reg | Bitwise exclusive-or: dest reg := src A ^ src reg B, en zet de vlaggen. |
Pseudoinstructies
Pseudoinstructie | werking | ||
---|---|---|---|
.ALIGN | constante | Sla zoveel adressen over als nodig dat de volgende (pseudo)instructie op een veelvoud van constante staat. | |
.DATA | gegevens | Maak ruimte voor variabelen e.d. De gegevens mogen een lijst van integers, karakters en strings zijn. | |
.LOAD | constante, dest reg | Laad een constante in een register. Deze pseudoinstructie wordt vertaald naar een passende LOADHI + ADD. | |
.START | constant adres | De volgende instructies moeten vanaf het opgegeven adres moeten worden opgeslagen. (Het adres moet een constante zijn, bv. een getal). |
Onderdelen
Hieronder geef ik hints voor de implementatie van de verschillende onderdelen.
Maak vooral gebruik van de genoemde onderdelen van de RTLIB, de library van voorbereide componenten die HADES aanbiedt. Je kunt die library bekijken met het menupunt “Edit > Open component browser”. Klik daarna op “built-in > hades > models > rtlib”.
ALU
De ALU kun je ook gebruiken om andere instructies dan rekeninstructies te implementeren, door extra opcodes toe te voegen. Je mag maximaal één adder gebruiken voor ADD, SUB, en de adresberekeningen van READ/WRITE samen. Zoals hierboven al beschreven, moet je de aftrekking A – B uitrekenen als A + ~B + 1.
Arithmetische en logische operaties
Bruikbare bouwstenen in de RTLIB zijn b.v. rtlib.arith.Add of rtlib.arith.Addc voor de adder; rtlib.logic.BitwiseAnd en rtlib.logic.BitwiseOr voor de logische operaties. Daarmee kun je een eenheid opbouwen die de arithmetische en logische operaties direct aankan.
Invoer: source A en source B (beide 32-bit vector data); opcode 000bin–111bin; Carry vlag.
Uitvoer: result (32-bit vector data); vlaggen.
Het klinkt misschien pietluttig, maar het is handig als je je aan deze indicaties van invoer en uitvoer houdt. Wijk er alleen van af als je een goede reden daartoe hebt. | ||
David Jansen → Processoren | Remove this comment when resolved! |
Rotatiewerk of barrel-shifter
De RTLIB-component rtlib.arith.RotateLeft kunnen jullie gebruiken om een vast bedrag naar links te roteren. Stel uit rotators om 1, 2, 4, 8 en 16 bits en wat logica eromheen een rotator samen dat om elk bedrag tussen 0 en 31 bits kan roteren, door sommige rotators wel en andere niet te gebruiken. De speciale rotate (ROLC) kun je met een extra multiplexer oplossen.
Invoer: source B (32-bit vector data); source A (5-bit vector data); Carry vlag; controle signaal uit decoder voor speciale rotate.
Uitvoer: result (32-bit vector data); vlaggen.
Registers en vlaggen
De RTLIB bevat een component rtlib.memory.RegBank, maar de registers daarin worden niet goed gereset. Bouw een registerbank op uit rtlib.register.RegRE. Bedenk, afhankelijk van je datastromen, hoeveel registers je tegelijk wilt lezen en/of schrijven. Teken eerst een registerbank met twee of vier registers om te zien hoeveel leidingen je rond elk register moet kunnen plaatsen. Breid hem daarna uit naar 16 registers.
Gebruik voor de vlaggen een vier-bit-register (en geef aan welke bit welke vlag betekent) of losse flipflops. Alleen rekenoperaties veranderen hun waarde; bij andere operaties blijven de waarden van de vlaggen opgeslagen.
Invoer: register-adres (telkens een getal tussen 0 en 15); een indicatie óf er iets opgeslagen moet worden; gegevens die opgeslagen moeten worden (32-bit vector data).
Uitvoer: inhoud van de gelezen registers (telkens 32-bit vector data).
Tester
Voor de tester heb ik geen passende RTLIB-component gevonden; bouw hem zelf. Let op de systematische opbouw van het “condition”-veld: met de drie hoogste bits kies je een voorwaarde, met de laagste bit geef je aan of die voorwaarde 1 of 0 moet zijn. Als het hoogste bit = 1 is, is de berekening meestal Z ∨ (berekening als de hoogste bit = 0 is).
Invoer: condition-veld (4 bits) en vlaggen (4 bits).
Uitvoer: 1 bit TEST_SUCCEEDS dat aangeeft of de voorwaarde vervuld is of niet.
Timer
De timer hoef je niet zelf te maken, maar ik geef hem als uitgewerkte component aan jullie. De timer is geschikt voor een CPU zonder pipelining. Dat betekent dat je CPU instructies na elkaar (en niet tegelijk) leest, decodeert en uitvoert. Maak een CPU met de volgende twee cycli:
- een instructie lezen: Telkens als de timer-uitvoer FETCH = 1 is, zet de CPU het adres in de programmateller op de adresbus en slaat ze de machinecode die binnenkomt in het instructieregister op. Tegelijk berekent de CPU programmateller + 4; aan het begin van de volgende cyclus wordt die waarde in de programmateller teruggeschreven.
- een instructie decoderen en uitvoeren: Telkens als de timer-uitvoer EXECUTE = 1 is, bepaalt de decodeerder het instructieformaat, geeft hij de juiste signalen door aan de diverse onderdelen van de processor en wacht tot de uitvoerlijnen van bv. de ALU zijn gestabiliseerd. Registers die veranderd worden worden exact aan het begin van de volgende cyclus opgeslagen.
De timer zorgt ervoor dat de andere onderdelen telkens in de juiste cyclus hun werk doen. De CPU heeft bovendien nog een paar bijzondere toestanden, die ik ook in de timer heb ingebouwd:
- Reset: Zolang de nRESET-invoer op 0 staat, doet de processor niets. Als de invoer naar 1 gaat, (wacht de processor even op een gunstig moment en) leest de eerste instructie. De timer heeft ook een uitvoer nRESET_OUT; gebruik die om de registers etc. te resetten. Die uitvoer blijft namelijk = 0 tot het gunstige moment is aangebroken.
- Halted: Als de processor een HALT-instructie heeft uitgevoerd, doet hij ook niets. Hij let alleen nog op de nRESET-invoer. Als je de invoer HALT van de timer op 1 zet, neemt hij aan dat een HALT-instructie wordt uitgevoerd; vanaf dat moment blijft FETCH = EXECUTE = 0 en wordt HALTED = 1.
Interne bussen en instructie-decodeerder
Maak voor de eerste deadline een principeschema van de CPU waarin jullie de routes verzamelen die gegevens in de CPU kunnen nemen: van een register naar de ALU (2×), van een constante naar de ALU, van de ALU naar een register, van de vlaggen naar de tester etc. Begin bij de belangrijkste, maar ga door tot je alle mogelijke routes hebt gevonden: kunnen alle instructies uitgevoerd worden door de routes op een geschikte manier aan of uit te zetten? Elke route wordt een (deel van een) interne bus. Daarna kunnen jullie interne besturingslijnen toevoegen die van de instructie-decodeerder naar de punten gaan waar een bus aan- of uitgezet moet worden. Om een bus uit te zetten kunnen jullie rtlib.logic.N1And, rtlib.muxes.Mux21 of rtlib.muxes.Mux41 gebruiken.
Jullie bouwen de instructie-decoder als “random logic”. Dat betekent niet dat er iets toevalligs in zit, maar dat de decoder op een plaatje minder regelmatig eruit ziet dan b.v. een stuk geheugen. In de instructiedecoder zit ook het instructieregister, dat in de fetch-fase gevuld wordt en in de executie-fase gelezen.
De decoder heeft tenminste een invoer CLK, nRESET, INSTRUCTION, FETCH, EXEC, TEST_SUCCEEDS, en zoveel uitvoeren als nodig zijn om de andere subdesigns, multiplexers, aansluitingen etc. aan te sturen.
Samenvoegen en testen
Hierboven, bij de hulpbestanden, staat een HADES-design waar alle externe aansluitingen van de processor voorbereid en het RAM en de console al aangesloten zijn. Jullie kunnen het subdesign van de CPU daarin plakken en de verbindingen met autoconnect laten aanmaken; dan moet de computer werken.
Om te testen, stel ik ook een eenvoudige assembler ter beschikking. Jullie zullen in de komende weken ook kennis maken met een paar principes van assembly-programmering, zodat je eenvoudige programma’s kunt schrijven.
Beoordelingscriteria
Deze beoordelingscriteria worden vanaf 2013/14 toegepast. Ze zijn nog niet helemaal definitief geformuleerd; ik moet ze dus nog eens doorlopen. | ||
David Jansen → Processoren | Remove this comment when resolved! |
Het cijfer wordt als volgt samengesteld:
- onderdelen die correct werken
- vlaggen 0,5 pt (met enable, reset, clock)
- (zonder enable: -0,15)
- tester 0,75 pt
- verkeerde uitkomst: -0,25 pt
- N and O in plaats van N xor O: -0,1 pt
- instructiedecoder 3,5 pt
- 0,5 punten per instructieformaat, maar dan inclusief correcte verbinding tussen instructieregister en andere onderdelen + correcte datastromen. (Fetchen geldt ook als een instructieformaat.)
- Ongeveer de volgende zes testinstructies ga ik voor elke decoder proberen.
fetch LOADHI 0x12345, R1 XOR.N 11, R1, R2 SUBf R5, R2, R3 WRITE R3, [R2+0x20] HALT
- fetch begint op adres 4: -0,2.
- registers 1 pt
- minder dan twee lees-uitgangen: -0,25 pt
- incorrecte afhandeling van enable en/of clock: -0,15 pt
- incorrecte afhandeling van reset: -0,1 pt
- overbodig register R0: -0,15 pt
- R0 kan veranderen: -0,25 pt
- ALU totaal 1,25 pt
- waarvan 0,25 pt voor vlaggen
- ca. 0,25 pt per operatie, waarbij de drie logische operaties als één tellen
- algemene fouten:
- kortsluiting ca. -0,2
- componenten over elkaar heen ca. -0,2
- componenten zonder naam -0,1 per component (instantienaam "unnamed" is niet erg)
- hoog spaghettigehalte (veel kruisingen, onoverzichtelijke lijnen) ca. -0,15
- ontbrekende component die in een voorlopige versie wel zat: -0,1
- cpu kan programma's executeren 0,5 pt
- verslag 1 pt
- assembly-programma 0,5 pt
Wat wil ik zien in het verslag?
- De grote lijn van het ontwerp:
- (belangrijkste) datastromen (ongeveer hetzelfde diagram als in de eerste deadline dus!)
- (belangrijkste) extra keuzes
- Korte beschrijving van elk onderdeel.
- functie in één zin. Bij complexe onderdelen hoogstens twee zinnen.
- Waarom is dit onderdeel nodig?
- Voorbeeldprogramma in assembly
- Wat doet het programma?
- Welke in- en uitvoer verwacht het?
Het verslag mag maximaal vier pagina's omvatten. De vijfde en volgende pagina's lees ik niet. Ik lees bovendien geen Word-documenten.