Processoren/Practicum (nieuw)
Deze pagina bevat stukken van een nieuwe versie van de practicumopdracht. De bedoeling is dat deze versie in collegejaar 2015/16 gebruikt wordt. | ||
David Jansen → Processoren | Remove this comment when resolved! |
Inhoud
Leerdoel
Hoofddoel van deze practicumopdracht is dat je kunt uitleggen hoe een CPU in een computer werkt. Zelfs als je later nooit een processor bouwt, is het zinvol te weten wat er op detailniveau gebeurt, zodat je een realistisch beeld van de (on)mogelijkheden van een computer krijgt. Meer specifiek:
- Je kent de basisprincipes van computerorganisatie.
- Je kunt de relatie tussen de opbouw van een processor en zijn instructieset verklaren.
- Je hebt enige kennis van circuitontwerp, processorontwerp en assembly-programmering.
De onderstaande opdrachtomschrijving is tamelijk lang. Verschaf jezelf eerst een overzicht voordat je aan het practicum begint. Onderaan staan in het bijzonder tips voor het maken van de opdracht en beoordelingscriteria – zeker handig als je een goed cijfer wilt krijgen voor deze opdracht zonder te veel werk te verzetten.
In te leveren stukken en deadlines
Maak de onderstaande opdracht in groepen van twee (of alleen). Er gelden de volgende deadlines:
- 19 december 2014
- Principiële opbouw van de CPU met nadruk op datastromen. Lever de volgende stukken in:
- datastroom-schema’s die aangeven welke informatie tussen de invoerlijnen, de belangrijkste onderdelen van de processor en zijn uitvoerlijnen stroomt, voor de FETCH-fase en voor elk instructieformaat van de EXECUTE-fase.
De in- en uitvoerlijnen en de belangrijkste onderdelen staan verderop in de opdracht genoemd. Word-bestanden lees ik niet. - een kort, eenvoudig programma in een hogere programmeertaal (C, C++, Java o.i.d.), vertaling naar een assembly-programma als .asm-bestand en vertaling naar machinecode als .rom-bestand. Je geeft ook in één zin een korte specificatie ervan. Voorbeelden van specificaties: „Dit programma leest een getal van het toetsenbord en print het hexadecimaal.” – „Dit programma vermenigvuldigt de getallen op RAM-lokaties 200 en 204 en slaat het resultaat in RAM-lokatie 208 op.” – „Dit programma berekent de wortel van het getal in R1 d.m.v. de relatie 1 + 3 + 5 + ... + 2n–1 = n2 en levert een resultaat in R2.”
- Het programma is niet triviaal; daaraan is in ieder geval voldaan als het een (zinvolle) loop bevat. Het C-programma mag je met bronvermelding overnemen, de vertaling naar assembly maak je zelf, en de vertaling naar machinecode maak je met de assembler (zie hieronder).
- Het assembly-programma bevat commentaar dat aangeeft welke instructie(s) in assembly met welke coderegel in het oorspronkelijke programma overeenkomt en welk register waarvoor gebruikt wordt.
- De drie versies van het programma (en de specificatie) moeten precies overeenkomen! Als er bv. cout << result; in staat, moet het assembly-programma ook naar het beeldscherm schrijven.
- Tip: Het beste is het als jouw programma één functie bevat:
- void main() {
... /* De eigenlijke inhoud van het programma */
exit(EXIT_SUCCESS);
}
- void main() {
- datastroom-schema’s die aangeven welke informatie tussen de invoerlijnen, de belangrijkste onderdelen van de processor en zijn uitvoerlijnen stroomt, voor de FETCH-fase en voor elk instructieformaat van de EXECUTE-fase.
- 16 januari 2015 23.59.59 CET
- Definitieve uitwerking van de practicumopdracht. Lever de volgende stukken in:
- processor als .hds- en .sym-bestanden, die je met de tool HADES maakt. De processor voldoet aan de onderstaande specificatie. Lever precies één versie in, zonder overbodige bestanden. In het bijzonder krijg je aftrek voor hades.jar, .exe, .doc, .docx, .hds_0, .hds_1 etc., .sym_0, .sym_1 etc.-bestanden. Alle bestanden moeten in dezelfde map staan.
- een korte uitleg die aangeeft waarin je bijzondere keuzes gemaakt hebt, bv. waar je de processor anders indeelt dan hieronder voorgesteld en waar je bij nader inzien toch anders indeelt dan in de datastroom-schema’s van de eerste deadline genoemd. Word-bestanden lees ik niet.
- Tentamendatum van de herkansing
- Dit is ook de datum waarop een eventuele herkansing van het practicum wordt ingeleverd. Je levert alle hierboven genoemde onderdelen tegelijk in.
Na elke deadline krijg je een cijfer voor het ingeleverde. Het cijfer van de principiële opbouw maakt 1/3 van het practicumcijfer uit. Er is een sanctie op overschrijding van de deadlines – zie beoordelingscriteria.
Beschrijving van de processor: Assembly
De processor die je maakt moet voldoen aan de onderstaande specificatie. De belangrijkste punten hieruit zijn:
- De processor heeft 16 algemene 32-bits-registers, R0, R1, ..., R15. Register R0 is altijd = 0. Register R14 is de stackpointer en kun je ook SP noemen. Register R15 is de programmateller en kun je ook PC noemen.
- De processor heeft vier vlaggen: Negative, Overflow, Zero, Carry. Rekenoperaties kunnen deze vlaggen veranderen; bijna elke instructie kan deze vlaggen testen.
- De processor voert instructies uit in de machinecode die overeenkomt met de practicum-assembly. Hij wisselt af tussen twee fases:
- FETCH: de processor leest één instructie uit het RAM, vanaf het adres dat de programmateller aangeeft, en verhoogt de programmateller.
- EXECUTE: de processor voert de zojuist gelezen instructie uit.
- Tussen het einde van de FETCH-fase en het begin van de EXECUTE-fase verifiëert de processor of voldaan is aan de voorwaarde van de zojuist gelezen instructie; zo niet, wordt de EXECUTE-fase overgeslagen en begint gelijk de volgende FETCH-fase.
- Tijdens een reset en als de processor gestopt is, voert hij geen van deze twee fasen uit.
- De processor communiceert via een data- en een adresbus met de rest van de computer. Je kunt een HADES-design downloaden voor de rest van de computer (zie onderaan, hoofdstuk „Hulpbestanden”), waar je jouw processor in plakt. Als het goed is kun je dan programma’s uitvoeren.
Je maakt de processor in HADES, een tool om circuits te tekenen en te simuleren. De tool is in Java gemaakt en draait daarom op bijna elke computer. Tips voor het gebruik van HADES staan verderop in de practicumopdracht.
Assembly-instructies
De processor begrijpt machinecodes die overeenkomen met de onderstaande assembly-instructies. Sommige hebben geen aparte machinecode; het zijn een soort afkortingen die de assembler naar machinecodes van andere instructies vertaalt. Als je een assembly-programma schrijft verdient het aanbeveling deze afkortingen wel te gebruiken; MOVE maakt duidelijker wat je wilt doen dan OR.
instructie (of afkorting) | werking | wordt vertaald als | ||||
---|---|---|---|---|---|---|
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 B | Compare: Bereken src A – src B en zet de vlaggen, maar vergeet het resultaat. | SUBf.cond ADDf.cond |
src A, src reg B, R0 of –src B, src reg A, R0 | ||
HALT.cond | Halt: Stop de processor. | |||||
JUMP.cond | label | Jump: Spring (in het programma) naar het adres label. | ADD.cond | label – ($+4), PC, PC | ||
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. | OR.cond | src, R0, dest reg | ||
MOVEf.cond | src, dest reg | Move: Kopiëer src naar dest reg, en zet de vlaggen. | ORf.cond | src, R0, dest reg | ||
NEG.cond | src reg, dest reg | Negate: dest reg := –src reg. | SUB.cond | R0, src reg, dest reg | ||
NEGf.cond | src reg, dest reg | Negate: dest reg := –src reg, en zet de vlaggen. | SUBf.cond | R0, src reg, dest reg | ||
NOP | No operation: Doe niets. | OR.Z | 0, R0, R0 | |||
NOT.cond | src reg, dest reg | Bitwise not: dest reg := ~src reg | XOR.cond | –1, src reg, dest reg | ||
NOTf.cond | src reg, dest reg | Bitwise not: dest reg := ~src reg, en zet de vlaggen. | XORf.cond | –1, src reg, dest reg | ||
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. | ||||
POP.cond | dest reg | Pop from stack: Lees van adres R14 (oude waarde) in het RAM en sla deze waarde in dest reg op, en zet R14 := R14 + 4. | ||||
PUSH.cond | src reg | Push to stack: Zet R14 := R14 – 4 en schrijf de inhoud van src reg op adres R14 (nieuwe waarde) in het RAM. | ||||
READ.cond | [addr reg + constante], dest reg | Read from RAM: Lees uit het RAM, vanaf adres (constante + register addr reg), en sla het resultaat in dest reg op. | ||||
RET.cond | Return from subroutine: Lees van adres R14 (oude waarde) in het RAM en sla deze waarde in R15 op, en zet R14 := R14 + 4. | POP.cond | PC | |||
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. | ||||
ROR.cond | src const A, src reg B, dest reg | Rotate right: dest reg := src reg B, om src const A bits naar rechts geroteerd. | ROL.cond | –src const A, src reg B, dest reg | ||
RORf.cond | src const A, src reg B, dest reg | Rotate right: dest reg := src reg B, om src const A bits naar rechts geroteerd, en zet de vlaggen. | ROLf.cond | –src const A, src reg B, dest reg | ||
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 (constante + register addr reg), 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. |
De meeste instructies hebben twee invoer-operanden: src A mag een constante of een register zijn en src reg B moet een register zijn. De uitvoer-operand dest reg is altijd een register; als dat R0 is, wordt het resultaat niet opgeslagen (maar de overige werkingen, bv. het vlaggen-zetten, gebeuren wel).
Elke rekeninstructie komt in twee varianten: één zonder f en één met f. De tweede variant voert niet alleen een berekening uit, maar zet ook de vlaggen op basis van het resultaat. Die vlaggen kunnen in de volgende instructies gebruikt worden om te testen of het resultaat > 0, = 0 of < 0 is, en afhankelijk daarvan kunnen die instructies wel of niet worden uitgevoerd. Het suffix .cond aan de instructienaam geeft de voorwaarde aan waaronder deze instructie wordt uitgevoerd. Een typisch gebruik is eerst een berekening uitvoeren en direct daarna met JUMP.L o.i.d. naar een ander gedeelte van het programma te springen om iets bijzonders te doen als het resultaat < 0 was. Mogelijke condities zijn:
conditie | naam | uitvoeren als ... | conditie | naam | uitvoeren als ... | |
---|---|---|---|---|---|---|
.T of niets | true | altijd | .F | false | nooit | |
.E | equal | laatste resultaat = 0 | .NE | not equal | laatste resultaat ≠ 0 | |
.L | less | laatste resultaat < 0 | .GE | greater or equal | laatste resultaat ≥ 0 | |
.G | greater | laatste resultaat > 0 | .LE | less or equal | laatste resultaat ≤ 0 | |
.LU | less (unsigned) | laatste resultaat < 0 | .GEU | greater or equal (unsigned) | laatste resultaat ≥ 0 | |
.GU | greater (unsigned) | laatste resultaat > 0 | .LEU | less or equal (unsigned) | laatste resultaat ≤ 0 | |
.Z | zero | zero-vlag is gezet | .NZ | not zero | zero-vlag is gewist | |
.C | carry | carry-vlag is gezet | .NC | not carry | carry-vlag is gewist | |
.O | overflow | overflow-vlag is gezet | .NO | not overflow | overflow-vlag is gewist | |
.N | negative | negative-vlag is gezet | .NN | not negative | negative-vlag is gewist |
De operatoren voor <, ≤, ≥ en > bestaan in twee varianten: Als de laatste berekening signed-getallen (in twee-complement) verwerkte, moet je .L, .LE, .GE of .G gebruiken; als de laatste berekening unsigned-getallen verwerkte, moet je .LU, .LEU, .GEU of .GU gebruiken. Het is meestal beter .L of .LU te gebruiken dan .N omdat de laatste voorwaarde incorrect is als het resultaat niet echt in 32 bits paste en er een overflow ontstond.
LOADHI
Waartoe dient LOADHI? Met deze instructie kun je een willekeurige constante inladen in maximaal twee instructies: eerst zet je met LOADHI de bovenste 22 bits op de juiste waarde en vervolgens met ADD de onderste bits. De assembler biedt een afkorting aan, namelijk .LOAD constante, dest reg, die intern naar die twee instructies vertaald wordt. (Maar vaak is één instructie voldoende: Als de constante tussen –512 en +511 ligt, kun je MOVE gebruiken; als de constante een veelvoud is van 1024, is alleen LOADHI voldoende.)
Beschrijving van de processor: Machinecodes en aansluitingen
De bovenstaande instructies komen overeen met de volgende machinecodes.
Alle machinecodes hebben in de vier laagste bits een condition. Dit veld wordt ná de instructieformaten uitgelegd. De meeste instructies geven een aantal registers aan: 0000bin betekent R0, 0001bin betekent R1, ..., 1111bin betekent R15. 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.
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 | instructie | |||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
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 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | condition | HALT.condition | |||||
2a | 1 | 1 | 0 | 0 | 0 | 0 | signed constant (offset) | 0 | 0 | 0 | 0 | addr reg | dest reg | condition | READ.condition | [addr reg + offset], dest reg | |||||||||||||||||||
2b | 1 | 1 | 1 | 0 | 0 | 0 | signed constant (offset) | src reg | addr reg | 0 | 0 | 0 | 0 | condition | WRITE.condition | src reg, [addr reg + offset] | |||||||||||||||||||
2b' | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | src reg | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | condition | PUSH.condition | src reg | |||||||
2c | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | dest reg | condition | POP.condition | dest reg | |||||||
3 | 1 | 0 | constant | dest reg | condition | LOADHI.condition | constant, dest reg | ||||||||||||||||||||||||||||
4a | 0 | opcode | flg | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | src reg A | src reg B | dest reg | condition | rekeninstructie | src reg A, src reg B, dest reg | |||||||||||||||
4b | 0 | opcode | flg | 0 | signed constant (src A) | 0 | 0 | 0 | 0 | src reg B | dest reg | condition | rekeninstructie | signed constant, src reg B, dest reg |
Opcodes van rekeninstructies
Alle rekeninstructies hebben hetzelfde formaat; de opcode (bits 30–28) geeft aan welke rekeninstructie het precies is. In de onderstaande tabel gebruik ik operatoren zoals ze in C gebruikt worden, bv. ^ = bitwise xor.
opcode | instructie | werking op registers | werking op vlaggen (als flg=1 is) | |
---|---|---|---|---|
000bin | OR, ORf | dest reg := src A | src reg B | Carry := 0 Overflow := 0 |
Negative := bit 31 van het resultaat Zero := 1 desda het resultaat = 0 is |
001bin | XOR, XORf | dest reg := src A ^ src reg B | ||
010bin | AND, ANDf | dest reg := src A & src reg B | ||
011bin | BIC, BICf | dest reg := src A & ~src reg B | ||
101bin | ROL, ROLf | dest reg := src reg B, om src A bits naar links geroteerd | Carry := bit 0 van het resultaat Overflow := (bit 31 van src reg B) != (bit 31 van het resultaat) | |
110bin | ADD, ADDf | dest reg := src A + src reg B | Carry := 1 als het resultaat eigenlijk 33 bits groot zou zijn Overflow := 1 als het resultaat in twee-complement niet in 32 bits past | |
111bin | SUB, SUBf | dest reg := src A + ~src reg B + 1 |
Condities
bitpatroon | suffix | uitleg | voorwaarde | |||
---|---|---|---|---|---|---|
0000 | .Z of .E | zero, equal | Z | |||
0001 | .NZ of .NE | not zero, not equal | ! Z | |||
0010 | .C of .GEU | carry, greater or equal (unsigned) | C | |||
0011 | .NC of .LU | not carry, less (unsigned) | ! C | |||
0100 | .N | negative | N | |||
0101 | .NN | not negative | ! N | |||
0110 | .O | overflow | O | |||
0111 | .NO | not overflow | ! O | |||
1000 | .GU | greater (unsigned) | C && ! Z | |||
1001 | .LEU | less or equal (unsigned) | Z || ! C | |||
1010 | .GE | greater or equal (signed) | N == O | |||
1011 | .L | less (signed) | N != O | |||
1100 | .G | greater (signed) | (N == O) && ! Z | |||
1101 | .LE | less or equal (signed) | (N != O) || Z | |||
1110 | .F | false | 0 | |||
1111 | .T of niets | true | 1 |
Aansluitingen van de processor
- De processor heeft de volgende invoerlijnen:
- DATABUS_IN: een 32-bit-databus voor gegevens die van het RAM of van het toetsenbord naar de processor worden gestuurd.
- CLOCK: een besturingslijn die klokpulsen van de centrale klok in de computer aan de processor doorgeeft. Als de processor iets in het RAM of op het scherm wil schrijven, gebeurt dat op het moment dat de klok van 0 naar 1 springt.
- nRESET: een besturingslijn die aangeeft of de processor gereset moet worden. Zolang deze invoerlijn = 0 is, gaat de processor zo snel mogelijk naar zijn begintoestand; zodra zij op 1 springt, begint de processor met werken.
- (We hoeven de stroomtoevoer in HADES niet te modelleren.)
- De processor heeft de volgende uitvoerlijnen:
- ADDRESSBUS: een 32-bit-adresbus die aangeeft op welk adres de processor uit het RAM of van het toetsenbord wil lezen, of op welk adres hij naar het RAM of naar het beeldscherm wil schrijven.
- DATABUS_OUT: een 32-bit databus voor gegevens die de processor wil opslaan.
- een besturingslijn nWRITE_ENABLE; als deze lijn 0 is wil de processor gegevens in het RAM laten opslaan of op het beeldscherm tonen. (De processor gaat ervan uit dat bij het eerstvolgende rising edge van de klok de gegevens die dan op DATABUS_OUT staan worden opgeslagen.)
- een besturingslijn nREAD_ENABLE; als deze lijn 0 is wil de processor gegevens uit het RAM of van het toetsenbord lezen. (De processor gaat ervan uit dat de gegevens die bij het eerstvolgende rising edge van de klok op DATABUS_IN staan de gewenste zijn.)
- een besturingslijn HALTED; als deze lijn 1 is, is de processor gestopt.
Sommige in- en uitvoerlijnen beginnen met een n. Dat betekent negatieve logica: juist als het signaal = 0 is, moet er iets bijzonders gebeuren (bv. nRESET = 0: de processor moet naar de begintoestand springen; nREAD_ENABLE = 0: de processor geeft het RAM de opdracht om gegevens naar DATABUS_IN te sturen).
Tips voor het maken van de practicumopdracht
Tips voor specifieke instructies
- LOADHI wordt vaak verkeerd geïmplementeerd. Je moet de constante in de 22 hoogste bits van het dest reg zetten en de laagste 10 bits ervan op 0 zetten.
- Bij overige instructies met een signed constant moet je die constante juist in de laagste 10 bits zetten en de overige bits met sign extension opvullen.
- SUB moet de drie getallen src A, ~src reg B en 1 in één operatie bij elkaar optellen om de vlaggen goed te zetten. De hieronder aanbevolen component rtlib.arith.Addc dient om twee getallen op te tellen, en bovendien biedt zij de mogelijkheid om met de carry-invoer nog eens 1 op te tellen.
- READ en WRITE moeten het adres berekenen op dezelfde manier als een instructie ADD offset, addr reg, (address bus). Gebruik de ALU hiervoor, maar uiteraard wordt dan het resultaat van de berekening naar de adresbus gestuurd i.p.v. naar een register. De bits 30–28 hebben waarden die je (als je het goed implementeert) ook als opcode voor de ALU kunt gebruiken. Wellicht kun je zelfs de bits 30–28 van PUSH en POP als opcode voor de ALU gebruiken.
- Probeer WRITE en PUSH op dezelfde manier te implementeren; je hoeft, als je goed nadenkt, geen onderscheid te maken tussen de twee. Daarom hebben ze ook bijna hetzelfde formaat.
- POP moet twee registers veranderen, namelijk R14 en het in de instructie genoemde dest reg; dat wijkt af van alle andere instructies.
Gebruik van HADES
Met de tool HADES teken je de onderdelen van de processor, orden je ze hiërarchisch en simuleer je ze.
Hiërarchisch ontwerp
Je kunt een ontwerp in HADES verdelen over meerdere bestanden. Maak eerst de basisbouwsteen, geef deze met menu Edit > Set design name... een naam, 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, dat de naam toont die je eerder (met Edit > Set design name...) had gekozen. 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. Je kunt ook hieronder (bij Hulpbestanden) een klein Linux-shell script downloaden hiervoor. 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; Ctrl-F zoom to fit
Meer sneltoetsen vind je op pagina 47 van het 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 (lichtblauw): nog geen waarde gekregen
- X Unknown (paars): HADES kan deze draad geen waarde geven, meestal geeft dit een foutsituatie aan, bv. kortsluiting.
- 0 0 (grijs)
- 1 1 (rood)
- Z High impedance (geel): 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 te 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);
- buitl-in:hades.models.rtl.Mux8_1 en rtl.Mux16_1 (multiplexers voor enkele draden. De eerste multiplexer zou je ook uit het contextmenu kunnen kiezen, als daar geen bug in zou zitten. Je moet ze dus uit de component library overnemen.);
- rtlib.io.Constant (voor een vector-constante); één-bits-constanten kun je in het contextmenu > create > io > VCC (1) en ... > GND (0) kiezen, of gebruik built-in:hades.models.io.Constant0 etc.;
- 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 en rtlib.io.Merge3 (voegt twee vectoren samen in één grote vector. Er is een bug in HADES waardoor je met Merge geen 32-bit-merger kunt maken; dan moet je helaas Expander en MergeBits-componenten of Merge3 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);
- rtlib.arith.Addc (additie met carry: telt twee getallen en eventueel één bit bij elkaar op), rtlib.arith.RotateLeft (roteert een getal om een vast aantal bits naar links);
- rtlib.logic.BitwiseAnd, rtlib.logic.BitwiseOr, rtlib.logic.BitwiseXor (voor bit-operaties);
- rtlib.register.RegRE (een register met een reset- en een enable-invoer).
Je mag ook andere elementen uit de map rtlib gebruiken die ongeveer even complex zijn als de genoemde.
Afgeraden componenten
De volgende componenten worden meestal verkeerd gebruikt of zijn overbodig:
- contextmenu > create > io > Power on reset: genereert een reset-signaal. Het is juist de bedoeling dat je de nRESET-invoer van de processor aansluit op de timer, en de nRESET_OUT-uitvoer van de timer op alle andere onderdelen die iets met een reset-signaal kunnen, zodat ze allemaal tegelijk gereset worden.
- contextmenu > create > io > Clock generator: genereert een klokpuls. Het is juist de bedoeling dat je de CLOCK-invoer van de processor aansluit op alle onderdelen die van de klok afhangen, zodat ze allemaal synchroon lopen.
- rtlib.compare.CompareEqual etc.: Het is niet nodig een dergelijk onderdeel te gebruiken om te testen of een invoer = 1 is; je kunt dat doen door een aantal gewone gates te combineren tot een circuit dat test of bit 0 gezet en alle andere bits gewist zijn.
- Latches: Gegevens moeten opgeslagen worden op het moment dat de klok van 0 naar 1 springt, maar een latch slaat gegevens op zolang de klok = 1 is.
- rtlib.arith.Add, rtlib.arith.Sub: Deze addeer- en subtraheerwerken genereren geen carry-uitvoer.
- rtlib.arith.UserDefinedALU: Je moet de ALU zelf opbouwen uit eenvoudigere onderdelen.
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.
- Een paar weinige onderdelen hebben aansluitingen die niet op het 1/4-inch-grid passen. Kies dan in het menu View > Magnetic grid... > 1/8 inch. Maar teken lijnen nog steeds met minstens 1/4 inch afstand van elkaar.
Belangrijkste onderdelen
Ik stel voor de volgende onderdelen te maken.
- ALU: voert berekeningen uit (van rekeninstructies of om het adres uit te rekenen bij READ, WRITE en PUSH).
- Registerbank: bevat de 16 (eigenlijk 15) algemene registers. Telkens één register kan geschreven worden; bovendien kan register R14 op verzoek met 4 verhoogd worden (voor POP).
- Tester: bevat het vlaggenregister en bepaalt of aan de conditie van de instructie is voldaan.
- Instructiedecoder: bevat het instructieregister, bepaalt het formaat van de instructie, stuurt besturingssignalen naar de overige onderdelen.
- Timer: Bepaalt wanneer en FETCH- en een EXECUTE-fase plaatsvinden (en ook wanneer de EXECUTE-fase wordt overgeslagen). Dit onderdeel hoef je niet zelf te maken, maar kun je downloaden – zie hieronder, in het hoofdstuk „Hulpbestanden”.
Hulpbestanden
Beoordelingscriteria
- Algemene fouten
- Deadline overschreden met 1 minuut tot 2 uur: –0,25. Deadline overschreden met >2 uur tot 24 uur: –0,5. Deadline overschreden met meer dan 24 uur: geldt als niet ingeleverd; je beoordeling van het onderdeel wordt 1.
- Bestanden niet in dezelfde map: –0,25 per map. Overbodige bestanden ingeleverd: –0,1 per bestand.
- een signaal met een onduidelijke (bv. OUT1 i.p.v. CARRY_OUT) of onpassende (bv. RESET voor nRESET) naam: –0,1. (Dit geldt ook als het vlaggenregister niet duidelijk aangeeft welke bit welke vlag is.)
- Multiplexers en decoders natekenen i.p.v. de voorgedefiniërde componenten gebruiken: –0,1 per nagetekend onderdeel.