Upload
others
View
9
Download
0
Embed Size (px)
Citation preview
Pipelining i RISC-processorn
Joakim Lindström Institutionen för informationsbehandling
Åbo Akademi E-post: [email protected]
1
Innehållsförteckning
1. Inledning
2. Historia: Intel 8086 (1978) till Pentium 4 (2000)
3. Principer för RISC-arkitektur
4. Effektiviseringsmetoder .
4.1 Pipelining
4.2 Pipelining av funktionella enheter
4.3 Dynamisk exekvering (Dynamic execution)
4.4 Branch prediction
5. IA-32 arkitekturen
5.1 Instruktionsuppsättning och assemblerprogrammering
5.2 Mikroarkitekturen
5.3 Pipelinen i Pentium 4
6. Avslutning
Abstrakt Den explosiva utvecklingen av integrerade kretsar har lett till dagens effektiva processorer. Enligt Moores lag kommer antalet transistorer som ryms på en krets att fördubblas var 18:e månad. Detta möjliggör att man kan konstruera mer avancerade processorer. Orsaken till dagens snabba processorer är inte endast högre klockfrekvenser utan även mer utvecklade och optimerade processorarkitekturer och effektiviseringsmetoder. Sådana metoder är: Pipelining; flera instruktioner utförs parallellt och ökar genomloppet av instruktioner i processorn, branch prediction; löser datakonflikter i pipelinen, out-of-order execution eller dynamisk exekvering; omorganisera instruktioner för att lösa datakonflikter. De flesta moderna processorer är RISC-processorer för ökad prestanda, vilket betyder att det finns ett relativt litet antal (enkla)instruktioner. Trots detta kan det för assembler-programmeraren se ut som om det skulle finnas ett stort antal instruktioner. Detta beror på att mikroarkitekturen översätter dessa instruktioner till RISC-instruktioner, så kallad mikrokod (microcode).
2
1. Inledning
Inom de närmaste 20 åren har det gått mycket framåt inom processorutvecklingen.
Processorerna har blivit mycket snabbare vilket här märkts i det allt bredare
användningsområdet för persondatorer. Datorerna har blivit bättre på att hantera bild,
ljud och datorgrafik. Men ”megahertz racet” har en aning avstannat verkar det som.
Klockfrekvenserna har inte ökat i samma rasande takt som de gjorde för några år sedan.
Istället har processortillverkarna satsat på att utveckla processorer som kan göra fler
saker parallellt. Att göra två saker samtidigt men på halva hastigheten ger ju samma
resultat. I vissa fall har t.o.m. klockfrekvenserna sjunkit.
Denna uppsats handlar om dessa metoder för parallellitet. Hur man kan göra fler
saker samtidigt på en uppsättning hårdvara. Dessa metoder leder även till en hel del
problem av olika slag som måste beaktas. Jag kommer att ta upp en hel del historik.
Hur det har varit tidigare och hur datorerna har förändrats.
2. Historia: Intel 8086 (1978) till Pentium 4 (2004)
Intels processorer är bakåtkompatibla. Objektkod från 1978 kan fortfarande köras på de
senaste processorerna i IA-32 arkitekturen.
IA-32 arkitekturens föregångare är 16-bits processorer. Sådana processorer är
8086 och 8088 som båda kom 1978. 8086 hade 16-bits register, 16 bits extern databuss
och en 20 bits adressering vilket gav 1 MB RAM. 8088 var liknande som 8086 förutom
att den hade en 8 bits databuss istället för 16 bitars.
Intel 80286 som kom 1982 var även den en 16 bits processor. Det nya med
80286 var minneshanteringen. Den hade protected mode för minnet. En funktion som
använder en basaddress (base address) och en gränsadress (limit adress) för att
förhindra programmet att gå utanför sitt eget minnesområde. 80286 använde en 24 bits
basadress, vilket gav 16 Mbyte minnesrymd.
Intel 80386 processorn, som kom 1985 var den första 32 bits processorn i
familjen. Den hade 32 bits register för både operander och adresser precis som många
3
av dagens processorer har. Den första halvan av de 32 bitarna motsvarar de 16 bitarna i
de tidigare arkitekturerna. På så vis är processorerna bakåtkompatibla. 32 bitar ger en
minnerymd på 4 GB vilket fortfarande gäller för dagens 32 bits processorer. 80386 gav
även virtuellt minne och sidbyten (paging) på 4 KB stora sidor. 386 gav även en del
parallellitet i exekveringen av instruktionerna.
80486 processorn som kom 1989 gav stöd för utökad parallellitet. Den hade en
5 stegs pipeline som kunde ha maximalt 5 instruktioner i exekvering samtidigt. Utöver
detta hade den ett 8 KB cacheminne integrerat i processorn.
1993 kom den första Pentium processorn. Parallelliteten utökades ytterligare
genom att en extra pipeline. Den hade 2 pipelines som kallades för u och v och gjorde
att 2 instruktioner kunde exekveras för varje klockcykel. Det integrerade cacheminnet
utökades från till det dubbla jämfört 80846. Pentium hade två 4 Kbyte cacheminnen, en
för programkod och en för data. Dessutom kom branch prediction med en hopptabell
(branch table) som var integrerad på kretsen [IA32IntMan].
1997 var det dags att utöka Pentium processorerna med MMX multimedia
extensioner. Där tillkom 57 nya instruktioner som använde flyttalsstacken för att
accelerera multimedia och kommunikations applikationer. MMX arbetar på många
korta dataelement. Metoden kallas single instruktion, multiple data (SIMD). Alltså
multipla dataelement i en enda instruktion [HenPat2005].
Pentium II och Pentium III processorn tillhör P6 mikroarkitekturfamiljen (1995-
1999). Pentium II ökade storleken av cacheminnet till 16 KB för nivå 1 cache och 265
för nivå 2 [IA32IntMan].
Pentium III introducerade nya multimediaextensioner: dataström SIMD (data
stream SIMD). Skillnaden var 8 nya register dubbelt så breda (128 bit), så att fyra 32
bits flyttalsinstruktioner kunde utföras parallellt. För förbättrad minnesprestanda SSE
inkluderade streaming store (dataström sparning) som kunde förbikoppla cache
minnena och skriva direkt till RAM [HenPat2005].
År 2000 kom den senaste och nuvarande Pentium 4 processor familjen som
bygger på NetBurst arkitekturen. Pentium 4 introducerade SSE2 som är en förbättrad
variant av SSE, och introducerade även Hyper Threading teknologin, som är en
funktion som ger en illusion av två logiska processorer. Detta för att kunna utnyttja alla
4
funktionella enheter bättre.[IA32IntMan] Det nya med SSE2 var att den kunde hantera
en ny datatyp: dubbel precision flyttal, alltså 64 bit. Med SSE2 kan man göra par av
dubbel precision flyttalsoperationer parallellt. Nästan alla av de 144 instruktionerna är
varianter ar av gamla SSE instruktioner som opererar på 64 bits flyttal [HenPat2005].
3. Principer för RISC-arkitektur
RISC (reduced instruction set) processorer är vad som används i dagens desktop
datorer. Tidigare användes CISC (complex instruction set computer). CISC
karakteriseras av ett stort antal instruktioner som processorn kan exekvera. Dessa
syns om man tittar på assemblerspråket för processorn. Instruktionerna består av
kraftfulla konstruktioner som liknar satser i ett högnivå språk som t.ex. C eller
FORTRAN. Principen med CISC är att: ”Gör inte i mjukvara vad du kan göra direkt i
hårdvaran”. RISC arkitekturerna däremot består av enkla instruktioner som är
betydligt enklare är instruktionerna i ett högnivåspråk. Det kommer att ta flera RISC-
instruktioner att göra samma sak än det tar CISC-instruktioner. Men det går snabbare
att utföra RISC-instruktionerna.
RISC konceptet är egentligen inte något nytt. Det fans maskiner redan på 60-
och 70-talet som hade RISC, men det var först på 80-talet som tekniken hade gått så
mycket framåt att RISC utgjorde något ordentligt alternativ till CISC. PÅ den tiden
fanns en debatt huruvida det var RISC eller CISC som gällde. Idag är det dock klart
att RISC- arkitekturer är den som ger mest fördelar.
Man kan ju undra: om RISC är så mycket snabbare varför använde man
överhuvudtaget CISC maskiner? Svaret till det är att med den tidens teknik så var
CISC en bättre väg att gå.
För det första så var inte kompilatorerna lika väl utvecklade som de är idag.
Det fanns kompilatorer men de gav inte någon speciellt optimal kod och slösade med
minne. Därför var det viktigt för programmerare att känna till assemblerspråket. Ett
kraftfullt assemblerspråk var användbart och gjorde programmerarens arbete enklare,
5
men det fanns också andra fördelar. Kraftfulla instruktioner sparade minne och tid vid
exekveringen.
En annan sak var att datorer på den tiden hade mycket begränsat
minnesutrymme. Därför var det bra att ha instruktioner som gjorde så mycket som
möjligt med en enda instruktion. Vissa av gamla tidens datorer kunde ha en
instruktion som gjorde en slinga (loop) i bara en enda maskininstruktion. Desto
mindre utrymme själva programkoden tog upp desto mer fanns det tillgängligt för
data.
En annan sak var att minnen dessutom var väldigt långsamma. De var mycket
långsammare än själva processorn. Så om en enda instruktion kan göra många
operationer samtidigt så kommer antalet hämtningar från minnet att bli färre.
Fram till 1980-talet satte man likhetstecken mellan en mer effektiv dator och
en större instruktionsuppsättning. På den tiden experimenterades det mycket med
detta att få mer avancerade instruktioner. Det visade sig dock att assembler-
programmerare kunde använda dessa instruktioner på ett bra sätt men inte
kompilatorerna.
Dat fanns olika orsaker till att man på 1980-talet började utveckla RISC-
processorer. En sak var att antalet transistorer på ett mikrochip ökade. Man ville då få
hela processorn att rymmas på ett enda mikrochip, och för att lycka med detta så
måste man skära ner på hårdvaran. Orsaken varför man ville ha processorn att
rymmas på ett anda chip var att det blev billigare. En annan sak var att man började
utveckla pipelining tekniker (parellell instruktions exekvering). CISC arkitekturen var
inte speciellt lämpad för pipelining eftersom den hade instruktioner av varierande
längd och med varierande exekveringstider.
De första RISC-processorerna var byggda på ett mycket begränsat utrymme
och hade därför en väldigt liten instruktionsuppsättning. Dessa processorer hade inget
stöd i hårdvaran för flyttalsberäkningar och vissa hade inte ens integer multiplikation.
Dessa sköttes med rutiner i mjukvaran som använde sig av flera enklare instruktioner,
en sorts mikrokod (microcode). Dessa tidiga RISC-processorer blev ingen större
framgång främst p.g.a. att de var nya och de använde för mycket minnesreferenser,
6
vilket ledde till att minnet blev en flaskhals. Detta problem löstes senare med cache-
minnen.
Det finns ett antal saker som anses vara karakteristiska för RISC-arkitekturer.
Dessa är:
• Pipelining
• Flyttalsberäkning i pipeline
• Alla instruktioner lika långa
• Delayed branching (fördröjt hopp, p.g.a. datakonflikter)
• Load/Store arkitekturer (endast speciella instruktioner får accessera minnet
direkt)
• Några enkla och väldefinierade addresseringstyper (addressing modes)
Det är dock inte sant att RISC processorer alltid är ”enklare” än CISC, utan snarare
tvärtom. Nya RISC processorer har avancerade tekniker så som: funktionell pipeline
och sofistikerade minnestekniker och kan även slutföra 2 eller flera instruktioner i
samma klockcykel (superscalar) [DowSev1998]. En del av Dessa tekniker behandlas i
nästa kapitel.
4. Effektiviseringsmetoder
I detta kapitel skall jag berätta om de metoder som finns för att effektivisera
instruktions-exekveringen i en modern RISC processor. Främst handlar det om
pipelining och metoder för att få själva pipeliningen att fungera som det är tänkt. En
annan benämning på pipelining är ILP (instruktion level parallellism), (parallellitet på
instruktionsnivå). När pipelinen blir mera komplicerad så kommer den att se väldigt
annorlunda ut från den enkla MIPS processor som jag har använt som exempel här. Ett
exempel på en sådan pipeline kommer i nästa kapitel som handlar om IA-32
arkitekturen.
7
4.1 Pipelining
Pipelining går ut på att instruktionerna överlappar varandra, man kan börja exekvera en
ny instruktion även om den första instruktionen inte ännu är slutförd. Detta ger nästan
samma prestanda som äkta parallell exekvering men med endast en uppsättning
hårdvara [DowSev1998]. Jämfört normal exekvering blir exekvering med pipelining i
allmänhet lika många gånger snabbare som det finns steg i pipelinen [HenPat1998]. I
pipeline exekvering så är varje instruktion uppdelad i ett antal delar. Dessa kunde vara
t.ex.
1. Instruktionshämtning (Instruction fetch IF) Processorn Hämtar en instruktion
från minnet
2. Instruktionsavkodning (Instruction decode ID) och Hämtande av operander.
Dessa kan vara register eller i minnet.
3. Exekvering (Execution EXE)
4. Minnes access (Memory access MEM)
5. Skrivning till register (Write back WB)
Jag skall förklara hur dessa steg fungerar. Det första är instruktionshämtningen. Ett
speciellt register Programräknaren (program counter) håller reda på var nästa
instruktion finns. En instruktion hämtas från minnet från adressen som specificeras av
programräknaren. Programräknaren måste sedan uppdateras. I en arkitektur med 32 bits
ord och om inget hopp sker så kommer programräknaren att ökas med 4 för att sedan
peka på nästa 32 bits instruktion. Därefter görs avkodning på instruktionen. Det går till
så att man plockar ut de enskilda bitarna i instruktionen. Det är färdigt bestämt vilka
bitar som specificerar vad. Vissa bitar kan t.ex. bestämma vad för sorts instruktion det
är frågan om. Om det är en hoppinstruktion en aritmetisk instruktion en minnesaccess-
instruktion etc. Vissa av bitarna kan specificera minnesaddresser andra register.
Hårdvaran avkodar dess bitar och styr dem till rätta platser. Bitarna som anger vilken
sorts instruktion det är frågan om kopplas till styrenheten. Styrenheten innehåller logik
som på basen av vilken sorts instruktion det är frågan om kommer att koppla logiken i
8
processorn på olika sätt. T.ex. om det är frågan om en aritmetisk logisk instruktion med
2 register som operander ser styrenheten till att 2 register kan läsas från registerbanken.
I nästa fas hämtas data från minnet eller från register som är specificerade i
bitarna som avkodades i föregående fas. När operanderna har hämtats så utförs någon
slags exekvering på dessa i den aritmetisk logiska enheten (ALU). Det kan vara t.ex. en
aritmetisk operation på 2 integer tal eller att beräkna en effektiv adress med hjälp av
innehållet i ett register och en konstant. I nästa steg; minnes access steget görs
eventuellt en läsning av eller skrivning till minnet och det är en sådan instruktion. I det
sista steget skrivs eventuellt ett resultat tillbaka till ett register.
Hela iden med att dela upp en instruktions exekvering i olika faser är att man
skall kunna påbörja en ny instruktion varje klockpuls. Så att när den första
instruktionen har gått vidare till sitt andra skede (instruktions avkodning) så kan nästa
instruktion sättas in, vilket betyder att den instruktionen är i sitt
instruktionshämtningsskede. Förloppet har illustrerats i fig1 (från [HenPat2003]).
fig1. Bilden visar exekveringen av 5 instruktioner i pipelinen. I den första klockcykeln
börjar instruktion i, sitt första skede. I nästa klockcykel kommer instruktion i att ha
slutfört sitt första skede (IF-steget) och överfört resultatet till nästa steg (ID-steget),
vilket ger möjlighet för instruktion i+1 att påbörja sin exekvering.
För att man skall kunna göra allt detta i en pipeline så måste det finnas register som
lagrar data mellan en klockpuls till en annan. Dessa register lagrar allt som fanns i den
ursprungliga instruktionen, plus hämtad data som behövs senare, uträknade resultat och
styrsignaler. Dessa register sätts in mellan de olika stegen: IF, ID, EXE, MEM, WB.
fig2 (från [HenPat2003]) illustrerar detta. I bilden är de svarta balkarna pipeline
9
registren. Och mellan dem finns hårdvaran som sköter varje av de 5 stegen i
exekveringen [HenPat2003].
fig2. Denna figur visar hårdvaran som behövs för att genomföra enkel pipelining i en
MIPS-processor. De olika delarna: IM, REG, ALU etc. separeras av pipeline-register
och motsvarar de 5 olika instruktionsstegen i fig 1.
I en pipeline så är genomloppet (throughput) det antalet instruktioner som kan slutföras
på en tidsenhet. Desto oftare en instruktion blir färdig desto högre blir genomloppet.
Tiden det tar att föra en instruktion från ett steg i pipelinen till ett annat är definierat
10
som en processorcykel (processor cycle). Den undre gränsen på en processorcykel
bestäms av det längsta steget (det steg som tar längs tid att utföra) i pipelinen.
Det finns olika sätt att mäta prestanda i en pipeline. Man kan tänka sig att
pipelining sänker antalet klockcykler per instruktion. Ett annat sätt att se på saken är att
pipelining minskar tiden för en klockcykel. I en konstruktion utan pipelining kan det
hända att det tar 5 eller flera klockcykler att slutföra en instruktion, men med pipelining
kan man (i bästa fall) få en instruktion slutförd per klockcykel. En annan möjlighet är
att man har en icke pipeline processor som slutför varje instruktion i en mycket lång
klockcykel. I det fallet gäller det ju att pipelining förkortar klockcykeltiden.
Det finns situationer där nästa instruktion inte kan exekveras i påföljande
clockcykel som det är meningen. Dessa situationer kallas konflikter (hazards). Det finns
flera olika datakonflikter.
Den första är något som kallas en strukturell konflikt (structural hazard). Det
innebär att två instruktioner i olika faser av sin exekvering försöker komma åt samma
hårdvaruenhet t.ex. minnet. Detta kan lösas t.ex. genom att duplicera minnet eller ha
flera cacheminnen.
Den andra konflikten är en kontrollkonflikt (control hazard). Det har att göra
med att man måste göra ett beslut om hopp innan man har räknat ut de värden som
behövs för att avgöra om hoppet skall göras eller inte. Detta löses i praktiken med att
estimera hopp (branch prediction) som tas upp i kapitel 4.4.
Den tredje och sista typen av konflikt är datakonflikten (data hazard). Detta
innebär att data som ännu inte har beräknats behövs i en annan beräkning. Detta kan
lösas med hjälp av återkoppling d.v.s. att man sätter in extra ledningar som förbinder de
olika enheterna direkt, alltså man kan t.ex. få ett värde direkt från ALU:s utgångar
istället för att vänta tills resultatet har skrivits tillbaka till register. För att detta skall
funka så behövs en del extra ledningar och logik i kontrollenheten [HenPat1998].
Datakonflikter kan även lösas mera effektivt med dynamisk exekvering, vilket innebär
att instruktionernas ordning kastas om för att undvika datakonflikter [HenPat2003].
Dynamisk exekvering tas upp i kapitel 4.3.
11
4.2 Pipelining av funktionella enheter Det är viktigt att man kan utöka pipelinen för att fungera även på enskilda funktionella
enheter som t.ex. enheter för flyttalsberäkning och multiplikation, vilka båda är
operationer som kan ta ett stort antal klockcykler att utföra.
Att utgå från att alla flyttalsoperationer skulle göras i en klockcykel blir väldigt
opraktiskt. Det skulle betyda att detta enda steg skulle bli mycket långt och därför kräva
en långsam klocka, eftersom hela systemets hastighet är bestämt av det långsammaste
steget i pipelinen.
Man kan tänka sig att man har en likadan pipeline för flyttal som för vanlig
integerberäkning. Man gör 2 förändringar från denna.
1. Exekveringssteget (EXE) kan repeteras så många gånger som det behövs för
att slutföra flyttalsberäkningen.
2. Det kan finnas flera flyttalsenheter.
När man funderar på dessa problem så är det speciellt 2 saker som man bör ta i
betraktande: bundenhet (latency) och repetitionsintervall (repeat interval). Latency
definieras som antalet överlappande instruktioner mellan en instruktion som ger ett
resultat och en instruktion som använder samma resultat. Repetitionsintervall är antalet
cykler som måste passera mellan två instruktioner av samma typ. Typiska exempel på
latency och repetitionsintervall ges i fig3 (från [HenPat2003]).
fig3. Tabellen visar bundenhet (latency) och repetitionsintervall för olika typer av
instruktioner.
12
I det här fallet har integer aritmetiska operationer en latency som är 0, vilket betyder att
resultatet kan användas redan i nästa klockcykel. Load instruktioner har en latency som
är 1 eftersom resultatet av dem kan användas först efter att en clockcykel har passerat
emellan. Det är i den clockcykeln som minnet accesseras med den effektiva adressen
som beräknades i Ex-steget, och det är därför logiskt att resultatet av en LOAD finns
tillgängligt först efter detta steg
Latency för en pipeline definieras som en clockcykel färre än djupet på
exekveringsdelen av pipelinen (antalet steg från EXE steget till steget som producerar
ett resultat). Fig4 ger ett exempel på en processor med 3 olika flyttals exekverings-
enheter och en vanlig integerenhet. Man kan se att det finns ett samband mellan latency
för de olika flyttalsoperationerna och djupet på motsvarande enhets pipeline. Djupet på
pipelinen är alltid 1 steg större än latency för motsvarande operation. För att få en högre
klockfrekvens så måste designern lägga färre logiska operationer i varje steg. Man byter
alltså en högre klockfrekvens mot högre latency för operationer. Processorn i fig4 (från
[HenPat2003]) kan ha 4 flyttalsadditioner och 7 multiplikationer i samtidig exekvering i
pipelinen. Divisionsenheten kan ha endast en division i exekvering åt gången. Det
betyder att när en divisions operation har startat så måste man vänta 25 clockcykler,
tills hela operationen är färdig innan nästa divisions operation kan matas in i enheten.
Detta beror på flyttalsdivisionens höga repetitionsintervall; 25, som syns i fig3.
Division är därför en mycket krävande operation som bör undvikas så långt som
möjligt. I en konstruktion som i fig4 så kan olika konflikter (hazarder) uppstå.
1. Olika strukturella konflikter kan uppstå. Eftersom operationerna är av
varierande längd.
2. När instruktionerna är av varierande längd så kommer de att slutföras i annan
ordning än vad de var i den ursprungliga koden
[HenPat2003].
13
fig4. Så har kan det se ut om man utökar MIPS-hårdvaran med flerstegs funktionella
enheter för flyttalsmultiplikation –addition och –division.
4.3 Dynamisk Exekvering
Det finns två olika sätt att optimera en dator med pipelining. En av dessa är att göra det
statiskt, d.v.s. i mjukvaran. En annan teknik är att göra det dynamiskt, alltså i
hårdvaran. Dynamisk exekvering hör till den senare kategorin.
Dynamisk exekvering är en viktig teknik som med hjälp av hårdvaran kastar om
ordningen på instruktionerna för att undvika datakonflikter. Tekniken har en rad
fördelar. Den kan lösa konflikter som var okända vid kompileringen, och underlättar
kompilatorns arbete. Dessutom gör den möjligt att kod som var kompilerad för en viss
pipeline även kommer att fungera bra på någon annan pipeline. Nackdelen med
dynamisk exekvering är att hårdvaran blir mycket mera komplicerad.
Tänk dig följande exempel. Man vill addera ihop två operander från minnet och
spara tillbaka resultatet till en minnesplats. I vissa CISC arkitekturer skulle detta kunna
göras med en enda instruktion. I en typisk RISC arkitektur å andra sidan så krävs 4
instruktioner för att göra detta: 2 load, 1 add och en store. Dessa instruktioner kan inte
matas sekventiellt i de festa pipelines utan man blir tvungen att stanna pipelinen vid
14
konflikter. Man kan lösa detta antingen statiskt genom att påverka kompilatorn, eller
också kan det göras dynamiskt i hårdvaran. Men om den dynamiska scheduleringen
görs bra så får man god prestanda. Därför använder många nya processorer (t.ex.
Pentium III och 4) tekniken att ta in komplicerade instruktioner som mikroarkitekturen
sedan översätter till dynamiskt schedulerade RISC operationer.
Problemet med out-of-order execution (exekvering i oordning) är att den kan
leda till olika strukturella konflikter. Jag skall visa en enklare metod kallad
scoreboarding (scorekort) som håller reda på de olika instruktionerna för att undvika
strukturella konflikter. Iden med en scorekort är att hålla en frekvens om 1 instruktion
per klockpuls, genom att starta en operation så tidigt som möjligt. Så att när en
instruktion blir stannad (stalled) i pipelinen så kan ändå nästa instruktion starta,
förutsatt att den inte är beroende av någon annan instruktion i exekvering. För att hålla
reda på vilka instruktioner som kan starta har man ett scorekort. För att man skall ha
någon nytta av out-of-order execution så måste man ha flera instruktioner i sitt EXE
steg samtidigt, och flera funktionella enheter med hög latency.
Varje instruktion går genom scorekortet som konstruerar en tabell över
databeroenden. Detta steg kan sägas motsvara ID steget i en vanlig pipeline.
Scorekortet avgör sedan när en instruktion kan läsa sina operander och börja
exekveringen. Om scorekortet avgör att instruktionen inte kan börja sin exekvering så
observerar den alla förändringar i processen och bestämmer när en instruktion kan
starta. Scorekortet avgör också när instruktionen kan skriva sitt resultat till
destinationsregistret. Fig5 (från [HenPat2003]) visar hur en processor med scorekort
kunde se ut.
15
fig5. Bilder visar en implementering av score-kort (score card) för att åstadkomma en
enkel form av dynamisk exekvering.
De vertikala linjerna är kontrolllinjer. Som utgående från scorekortet kontrollerar hur
dataflödet sker över bussarna (de horisontala linjerna). Varje instruktion i scorekort-
systemet går genom 4 steg i exekveringen. Stegen är som följande.
1. Issue (utgång). Om en funktionell enhet för instruktionen är ledig och ingen
annan aktiv instruktion har samma destinationsregister styr scorekortet
instruktionen till rätt funktionella enhet, varefter scorekortet uppdaterar sina
interna tabeller.
2. Läsning av operanderna. Skorekortet håller reda på tillgängligheten av
källoperanderna. En källoperand är tillgänglig om ingen annan tidigare startad
operation kommer att skriva den. När källoperanderna är tillgängliga säger
scorekortet till den funktionella enheten att läsa operanderna och starta
exekveringen.
16
3. Exekvering. Enheten börjar sin exekvering. När den är färdig anmäler den det
till scorekortet.
4. Skriv resultatet. När scorekortet får reda på att operationen är färdig kontrollerar
den on det finns någon datakonflikt: den kontrollerar om någon tidigare
operation borde läsa registret innan det ändras.
Ett exempel på en sådan här konflikt kan illustreras med följande assemblerkod.
DIV f0, f2, f4
ADD f10, f0, f8
SUB f8, f8, f14
ADD instruktionen har en källoperand f8 som är samma som SUB-instruktioens
destinationsoperand. Men ADD operationen är fördröjd eftersom den är beroende av
den långa instruktionen DIV. Här kommer scoreboarden att fördröja SUB
instruktionens skriv skede tills ADD instruktionen har hunnit läsa (det gamla värdet på)
f8 [HenPat2003].
4.4 Branch prediction
Så kallade hopp (branches) kan även orsaka konflikter i en pipeline. Dessa hopp
uppstår alltid när man i källkoden har en loop eller if-sats. Hopp orsakar kontroll-
konflikter (control hazards). Antalet hopp som görs när man kör ett program är
vanligen ganska många, så det är nödvändigt att ha någon form av branch prediction
(uppskattning av hopp). Branch prediction metoder går ut på att i förtid kunna gissa
vart nästa hopp skall ske. På så sätt behöver man inte fördröja pipelinen om man har
gissat rätt. Jag skriver här om dynamisk branch prediction som är implementerad i
hårdvaran. I dynamisk branch prediction är uppskattningen beroende av hur
programmet beter sig under exekvering. Det finns även statiska branch prediction
metoder som görs av kompilatorn innan programmet startar.
Den enklaste formen av branch prediction är en branch prediction-buffer (hopp
uppskattnings buffert). Bufferten är en liten bit minne som är lagrar historik över ett
visst hopp. I det enklaste fallet har man bara en bit. Biten anger om det har gjorts ett
17
hopp eller inte inom den närmste tiden. Så om biten är satt utgår man från att hoppet
kommer att göras och man börjar ladda in nästa instruktion från hoppaddressen. Om
uppskattningen visade sig vara fel så inverterar man biten. Denna metod har visat sig
vara dålig, så därför använder man istället en 2 bitars buffert. I ett sådant system måste
uppskattningen missa 2 gånger förrän den ändras. Denna variant kommer att ge rätt
uppskattning oftare än varianten med 1-bits buffert [HenPat2003].
5. IA-32 arkitekturen
IA-32 arkitekturen är Intels processorfamilj, startande från 8086 år 1978. IA-32 är
världens populäraste arkitektur för desktop datorer. Senaste modellerna är Pentium 4
och Pentium M, speciellt utformad för bärbara datorer. Nyare Pentium datorer så som
Pentium II, III och 4 bygger på avancerade pipelining metoder [HenPat2005].
5.1 Instruktionsuppsättning och assemblerprogrammering
Instruktionerna i IA-32 kan indelas i: Generella instruktioner, flyttalsinstruktioner,
MMX-instruktioner, olika sorters SSE-instruktioner, 64-bits instruktioner och speciella
systeminstruktioner.
De generella instruktionerna är de vanliga instruktioner som kan användas för
att skriva enklare program utan desto mera optimering eller prestandakrav. De generella
intruktionerna är sådana som finns tillgängliga på alla Intels processorer. Uppsättningen
generella instruktioner innehåller: aritmetiska instruktioner (add sub etc.),
dataflyttnings instruktioner, logiska instruktioner, dataflöde- och stringoperationer.
Instruktionerna kan arbeta på data som finns i de generella registren EAX, EBX, ECX,
EDX, EDI, ESI, EBP, och ESP (se fig6 ) och data som finns i minnet. De kan också
operera på addresser som finns i minnet eller de generella registren. Även de kortare 16
bits segmentregistren: CS, DS, SS, ES, FS, och GS kan användas för att få fram
addresserna [IA32IntMan].
18
fig6. (från [HenPat2005]) Registren i IA-32 arkitekturen. 80386 processorn var den
första i IA-32 familjen som använde 32 bits instruktioner och register. I den gamla 16
bits arkitekturen, som återfanns i 8086, 8088, och 80286 så var de allmänna registren
namngivna som: AX, BX, CX, DX. Så när arkitekturen ändrades till 32 bit så behöll
man dessa namn, men man satte ett E framför dem för att ange att de var förlängda
(extended). Så registren kallas nu EAX, EBX, ECX etc. I 80386 arkitekturen finns 7
allmänna (general purpouse) register. Förutom de allmänna registren finns även 16
bits register som har hållits samma från den tidigare arkitekturen. Dessa är 16 bits
registren: CS, SS, DS, ES, FS och GS. Dessa är segmentregister. Till exempel så
innehåller CS registret en pekare till platsen i minnet där kodsegmentet börjar
[HenPat2005].
19
De aritmetiska, logiska, data flyttnings (data transfer) operationerna är alla två-
operands instruktioner. Operand 1 måste vara både destinationsoperand och första
källoperand. Detta register alternativt en minnesreferens som är destination kommer
alltså att skrivas över. Operand 1 ett kan vara antingen register eller en minnesreferens.
Operand 2 kan vara register, konstant eller minnesreferens. Det finns ingen instruktion i
vilken båda operanderna är minnesreferenser. Det finns 7 olika addresseringslägen
(addressing modes) i IA-32. En minnes operand kan använda vilka addresseringslägen
som helst, men det finns en begränsning på vilka register som får användas. T.ex. i
register indirekt läget där man har en adress i ett register så får man inte använda ESP
eller EBP registret [HenPat2005].
5.2 Mikroarkitekturen
”Mikoarkitektur är en metod för att designa komplicerade kontrollenheter. Den
använder en enkel hårdvarumotor som kan programmeras att implementera mer
komplicerade instruktionsuppsättningar. Mikroprogrammering används idag för att
implementera delar av en komplicerad instruktionsuppsättning som t.ex. i en Pentium
eller i en dator för specialanvändningsområden” [HenPat2005].
P6 är mikroarkitekturen som användes i Pentium Pro Pentium I, Pentium II och
Pentium III. Den är föregångare till NetBurst mikroarkitekturen som finns i Pentium 4.
P6 är en dynamiskt schedulerad processor. Som översätter varje IA-32 instruktion till
en serie mikrooperationer, liknande vanliga RISC operationer. En serie på upp till 3 IA-
32 instruktioner hämtas och översätts till mikrooperationer varje klockcykel. Om en IA-
32 instruktion kräver fler än 4 mikrooperarioner så är det implementerat med hjälp av
en mikrokodsekvens som genererar mikrooperationerna i flera klockcykler.
Mikrooperationerna är sedan exekverade med hjälp av en dynamisk schedulerad
pipeline.
20
5.3 Pipelinen i Pentium 4
Pipelinen i Pentium 4 är en sofistikerad dynamiskt schedulerad pipeline som kan utföra
i medeltal 3 mikrooperationer per klockcykel. När det gäller design av sofistikerade
processorer så är det svårt att skilja åt de olika delarna från varandra. De funktionella
enheterna, cache, register filen, instruktionsutgång (issue) och pipelienekontroll smälter
ihop till en enda enhet. Denna brukar allmänt kallas mikroarkitekturen. Fig7 (från
[HenPat2005]) visar en modell av mikroarkitekturen hos Pentium 4.
Fig7. Schematisk bild över mikroarkitekturen i Pentium4.
Pentium 4 har många förbättringar jämfört med sin föregångare III. Dessa är:
1. Pipelinen är dubbelt så djup (20 steg jämfört tidigare 10)
2. Mera funktionella enheter (7 tidigare 5)
21
3. Kan ha ett större antal operationer i samtidig exekvering (126 tidigare 40)
4. Bättre minneshantering
I modellen i fig7 kan man se många olika sorters köer. Där finns en Mikrooperationskö
(Microoperation que), Integerkö och en flyttals kö. De omfattande köerna tillåter 126
mikrooperationer att vara ”i flykt” samtidigt. I bilden ser det ut som om det skulle
finnas endast 6 funktionella enheter och inte 7, men flyttals enheten räknas som två
därför att den har en skild enhet för flyttning av flyttal . Load och store enheterna består
vardera av två olika delar. Första delen är ansvarig för addressberäkningen och den
andra för minnesaccess. Integer ALU enheterna kör på dubbel klockfrekvens jämfört
med vad den övriga processorn gör, vilket betyder att vardera integer enhet kan slutföra
två integer operationer per vanlig klockcykel. Multimiediaextensioner så som MMX
och SSE2 är även inbyggda i flyttalsenheten [HenPat2005].
5. Avslutning
Processorarkitektur är ett avancerat område i pågående utveckling. Det är många nya
begrepp att lära sig och även om man tror sig veta grunderna i datorarkitektur kan man
lätt känna sig borttappad. Också tillkommer ju det faktum att det finns många olika
sorters processorer och de flesta är väldigt olika, med olika instruktionsuppsättningar
etc. En sak som jag kom att tänka att tänka på är hur primitiva de gamla processorerna
verkar när man jämför med de avancerade konstruktioner som byggs i dag till desktop
datorer. Man är nästan förvånad att dessa datorer fungerade överhuvudtaget, men det
vet ju alla som har, eller har haft en gammal dator att den ofta fungerar till samma saker
bara lite långsammare.
I denna uppsats har skummat lätt på ytan av ämnet processordesign utan att gå
djupt in de avancerade sakerna som finns i den moderna processorn. Det finns mycket
annat som är viktigt när man designar och bygger processorer men som jag inte har
tagit upp. Sådana saker är beräkning av en processors prestanda på en teoretisk nivå
22
innan man bygger och mätning och testning av en redan färdig processor. Jag har heller
inte tagit upp något speciellt om de minnestekniker som finns.
Litteraturlista:
[DowSev1998] Kevin Dowd & Charles Severance: High Performance Computing,
O’ Reilly, 1998
[HenPat2003] John L. Hennessy & David A. Pattersson: Computer Architecture, A
Qantitative Approach, Morgan Kaufmann, 2003
[HenPat2005] John L. Hennessy & David A. Pattersson: Computer Organization &
Design, The Hardware/Software Interface, 3rd ed, Morgan Kauffmann, 2005
[HenPat1998] John L. Hennessy & David A. Pattersson: Computer Organization &
Design, The Hardware/Software Interface, 2nd ed, Morgan Kauffmann, 1998
[IA32IntMan] IA-32 Intel® Architecture Software Developer's Manual
<http://www.intel.com/design/pentium4/manuals/index_new.htm>