Činnost mikroprocesoru, aneb jde to i bez trpaslíků

27. 3. 2008
Doba čtení: 14 minut

Sdílet

V dnešní části seriálu o architekturách počítačů si vysvětlíme, jakým způsobem pracuje mikroprocesor. Především si (s využitím animací) ukážeme postup načítání instrukcí z operační paměti a jejich provádění za pomoci řadiče, aritmeticko-logické jednotky, pracovních registrů a pomocných registrů.

Obsah

1. Činnost mikroprocesoru bez záhad, aneb jde to i bez trpaslíků
2. Hodinový signál
3. Inicializace mikroprocesoru pomocí signálu Reset
4. Načítání instrukcí z operační paměti a role registru PC
5. Operační kódy instrukcí (opkódy), strojový kód a assembler
6. Podrobnější popis instrukčního souboru
7. Instrukce pro přesuny dat mezi registry
8. Obsah následující části tohoto seriálu

1. Činnost mikroprocesoru bez záhad, aneb jde to i bez trpaslíků

V předchozí části tohoto seriálu jsme si ukázali schéma velmi jednoduchého „učebního“ mikroprocesoru, na kterém si nyní budeme vysvětlovat jednotlivé funkce, které mikroprocesory při své práci provádí. Také jsme si vypsali takzvanou instrukční sadu tohoto mikroprocesoru, tj. operační kódy (opkódy) instrukcí spolu s jejich mnemotechnickými zkratkami, které náš jednoduchý procesor dokáže zpracovat. Dnes již začneme s podrobnějším popisem jednotlivých instrukcí, včetně vysvětlení způsobu provádění těchto instrukcí uvnitř mikroprocesoru, za využití všech interních bloků, které se v něm nachází, tj. aritmeticko-logické jednotky, mikroprogramového řadiče, pracovních registrů, pomocných registrů a interní sběrnice. Průběh jednotlivých kroků bude doplněn animacemi; ovšem možná budete zklamáni, protože se v nich žádní trpaslíci, kterými se tak často zaklínají někteří uživatelé (a také pár učitelů), nebudou vyskytovat. Učební mikroprocesor má následující architekturu:

pc0501

Interní schéma „učebního“ mikroprocesoru

2. Hodinový signál

Každá instrukce, kterou mikroprocesor provádí, je interně rozložena do jedné nebo několika jednodušších akcí, například do přenosu dat mezi pracovním registrem a pomocným registrem, vykonáním aritmetické či logické operace v ALU, zvýšení hodnoty registru PC o jedničku či zápisu dat do operační paměti. Jednotlivé akce musí být vzájemně synchronizovány, k čemuž slouží takzvané hodinové signály přiváděné do mikroprocesoru pomocí vstupu nazývaného celkem příhodně Clock, popř. CLK či φ. Samotné generování hodinového signálu s obdélníkovým průběhem (jedná se tedy o binární signál) je prováděno oscilátorem postaveným například na astabilním či monostabilním klopném obvodu, Schmittově klopném obvodu doplněném krystalem, popř. také na oscilátorech harmonických signálů doplněných Schmittovým klopným obvodem, který z původně sinusových průběhů vytvoří žádoucí obdélníkový signál. Nejjednodušším generátorem je Schmittův klopný obvod doplněný o zpětnovazební odpor a kondenzátor zapojený mezi vstupní signál a zem. Hodnota R a C potom určuje periodu:

pc0502

Velmi jednoduchý oscilátor založený na Schmittově klopném obvodu

Některé mikroprocesory a zejména mikrořadiče mají oscilátor zabudovaný přímo na čipu a většinou pro svoji činnost vyžadují pouze připojení externího krystalu pro dosažení přesné taktovací frekvence – krystal je totiž poměrně velká a přitom cizorodá součástka (navíc s mechanicky namáhanými částmi), která se přímo do mikroprocesoru, tj. na vlastní polovodičový čip, špatně integruje. Starší mikroprocesory vyžadovaly více hodinových signálů označovaných například φ1 a φ2, které byly interně použity v řadiči pro generování příkazů pomocí klopných obvodů typu JK.

Na tomto místě je vhodné podotknout, že samotná frekvence hodinového signálu vcelku nic nevypovídá o výkonnosti mikroprocesoru, tj. může se stát, že mikroprocesor s frekvencí hodinového signálu 200 MHz bude výkonnější než mikroprocesor s frekvencí 500 MHz. V úvahu je nutné brát jak šířku zpracovaných dat, tak i průměrný počet strojových instrukcí zpracovaných v jednom taktu (popř. naopak počet taktů nutných pro zpracování jedné instrukce), dostupnost některých operací, samotnou architekturu použitého mikroprocesoru (vektorové zpracování instrukcí, VLIW, SIMD) atd.

pc0503

Stále poměrně jednoduchý (a nepřesný) oscilátor vytvořený z logických hradel

Navíc se ukazuje, že sázka některých firem na uvádění výkonu svých mikroprocesorů pomocí hodnoty frekvence hodinového signálu (a nikoli na jiných jednotkách, například syntetizovaných benchmarcích) se často obrací proti svým tvůrcům, zejména při nástupu mikroprocesorů s více jádry (cores) – to je však téma, kterým se budeme zabývat v některé z dalších částí tohoto seriálu (ostatně se ukazuje, že lidé často a rádi porovnávají vlastnosti různých výrobků pomocí jediného čísla, které však vůbec nemusí výrobek dostatečně popisovat: výkon motorů v automobilech se často uvádí pouze v KW, vlastnosti fotoaparátu pouze údajem o počtu megapixelů atd.).

pc0504

Další možnosti zapojení jednoduchých oscilátorů (bez krystalu)

3. Inicializace mikroprocesoru pomocí signálu Reset

Mikroprocesor je velmi složitý integrovaný obvod, který uvnitř obsahuje poměrně velké množsví navzájem propojených kombinačních i sekvenčních logických obvodů. Při zapnutí mikroprocesoru, tj. přivedení napájecího napětí a hodinového signálu, se jednotlivé obvody uvniř mikroprocesoru obecně nachází v neurčeném stavu. Samozřejmě je nutné, aby se mikroprocesor (jako celek) po zapnutí dostal do přesně stanoveného výchozího stavu. Většinou také potřebujeme mikroprocesor kdykoli restartovat, tj. dostat ho do výchozího stavu, ve kterém se nacházel po zapnutí. K těmto funkcím slouží signál Reset, na který reaguje řadič mikroprocesoru. U jednodušších počítačů bývá signál Reset generován tlačítkem doplněným o monostabilní integrovaný obvod, který zajistí automatické vygenerování tohoto signálu po připojení napájení (většina mikroprocesorů totiž vyžaduje, aby signál Reset trval určitou minimální dobu, například deset taktů hodinového signálu).

Ve chvíli, kdy řadič detekuje signál Reset, provede několik operací. Především sám sebe nastaví do výchozího stavu, ve kterém může na signál Reset adekvátně reagovat. Posléze vyšle na interní sběrnici mikroprocesoru sekvenci řídicích signálů, pomocí kterých nastaví obsah pracovních registrů na požadované výchozí hodnoty. Pracovní registry A a B jsou vynulovány, oba dva příznaky, tj. Carry flag a Zero flag jsou taktéž vynulovány, programový čítač PC je nastaven na adresu 0×0000, tj. na první buňku operační paměti´, a ukazatel na vrchol zásobníku SP naopak na adresu 0×ffff, tj. poslední buňku operační paměti.

Poznámka: veškeré adresy i operační kódy instrukcí budou zapisovány v hexadecimální soustavě, přičemž budu dodržovat „céčkovský“ způsob zápisu s prefixem 0×. Vzhledem k šestnáctibitové šířce všech registrů nesoucích adresu, tj. registrů PC a SP, je adresový rozsah mikroprocesoru roven 0–216-1, tj. hexadecimálně 0×0000 až 0×ffff.

pc0505

Reakce řadiče mikroprocesoru na příchod signálu Reset

4. Načítání instrukcí z operační paměti a role registru PC

Jelikož je náš ukázkový mikroprocesor postaven na von Neumannově architektuře, je celá jeho činnost řízená programem zapsaným v operační paměti. Mikroprocesor je pomocí adresové sběrnice, datové sběrnice a řídicí sběrnice připojený k paměťovému subsystému, tj. jednomu či několika čipům, na nichž je vytvořena paměť typu RWM/RAM (pro čtení i zápis) a paměť typu ROM (pouze pro čtení). O paměťovém subsystému a různých technologiích pamětí si ještě něco řekneme v navazujících částech tohoto seriálu, dnes si však musíme ukázat, jakým způsobem mikroprocesor může načítat údaje uložené v těchto pamětech, především nás zajímá čtení programového kódu, tj. jak instrukcí tak i dat, které jsou k instrukcím přidružena. Po stránce vnější komunikace mikroprocesoru s pamětí je situace jednoduchá: procesor na adresovou sběrnici zapíše požadovanou adresu, ze které potřebuje instrukci číst a na řídicí sběrnici pošle příkaz pro čtení (R/Read). Po předem známé době paměť vypíše požadovaný údaj na datovou sběrnici, odkud ho mikroprocesor přečte.

Nyní tedy zbývá vyřešit, jaké adresy bude mikroprocesor zapisovat na adresovou sběrnici. Von Neumann mimo jiné předepsal, že procesor má instrukce vykonávat postupně tak, jak jsou zapsány v paměti a jediný způsob, jak tuto posloupnost instrukcí změnit, je použití příkazů pro skoky (popř. použití přerušení). Mikroprocesor si pamatuje adresu instrukce, která se má přečíst z operační paměti, v registru nazvaném PC (Program Counter). V případě, že není použita instrukce skoku, se hodnota tohoto registru postupně zvyšuje o hodnotu odpovídající délce právě přečtené instrukce. Jinými slovy – pokud má instrukce délku jednoho bytu, je PC zvýšen o jedničku, pokud je instrukce delší, například dva byty, je PC zvýšen o dvojku. Počáteční hodnota PC po restartu mikroprocesoru je rovna nule, tj. mikproprocesor začíná načítat program od začátku (nulté adresy) operační paměti (jiné mikroprocesory však mohou používat odlišnou startovací adresu).

Na následující animaci je ukázáno, jakým způsobem mikroprocesor zvyšuje hodnotu registru PC o jedničku. Vzhledem k tomu, že je náš mikroprocesor opravdu velmi jednoduchý, neobsahuje specializovaný obvod, který by toto zvyšování prováděl autonomně (například paralelně s jinou operací), ale je zapotřebí použít univerzální ALU, která obsahuje požadovanou instrukci INC, tj. zvýšení hodnoty prvního pomocného registru o jedničku. Toto řešení je sice jednoduché a šetří zdroje (tranzistory, hradla), na druhou stranu však může znamenat zpomalení provádění instrukcí, což nás však prozatím nemusí příliš trápit (také nás netrápí zbytečné „rozředění“ operačních kódů instrukcí a jejich adresních částí, jak si ostatně ukážeme v následujících třech kapitolách).

pc0506

Způsob zvýšení hodnoty registru PC o jedničku pomocí ALU

5. Operační kódy instrukcí (opkódy), strojový kód a assembler

Mikroprocesor zpracovává program zapsaný ve strojovém jazyku neboli strojovém kódu. Jedná se o binární formát složený z jednotlivých operačních kódů instrukcí doplněných o adresní část, popř. konstanty. Každý mikroprocesor, resp. každá navzájem kompatibilní vývojová řada mikroprocesorů, má svůj vlastní strojový kód, který není obecně přenositelný na ostatní mikroprocesory. Operační kód instrukce jednoznačně určuje, jakou operaci má mikroprocesor provést, pomocí adresní části se specifikuje, s jakými operandy (registry, místy paměti, periferními zařízeními) se má daná operace provést, a konečně konstanty mohou určovat hodnotu, která se zúčastní aritmetické operace, absolutní či relativní adresu, na kterou se má provést skok, hodnotu posunu, která se přičte k adrese uložené v registru atd. Program zapsaný ve strojovém kódu může vypadat po výpisu v hexadecimální soustavě a doplněním sloupce s adresami následovně:

0000: B81300
0003: CD10
0005: 6800A0
0008: 07
0009: 33FF
000B: B106
000D: C706750100E0
0013: BE4001
0016: B520
0018: 33C0
001A: 8BE8
001C: 93
001D: 8BC5
001F: D3F8
0021: F7E8
0023: 50
0024: 8BC3
0026: D3F8
0028: F7E8
002A: 268905
002D: 8BC5
002F: D3F8
0031: C1FB05 

Což zajisté není příliš čitelné. Z důvodu snadnější tvorby programů byl vyvinut jazyk symbolických instrukcí (JSI), někdy také nazývaný jazyk symbolických adres (JSA), ovšem nejčastěji se setkáme s poněkud nepřesným názvem assembler či assembly language. Jedná se o jazyk, ve kterém je program zapsán pomocí mnemotechnických zkratek instrukcí a ne jejich číselnými kódy. Také je možné používat takzvaná návěští (labels), což jsou vlastně pojmenované adresy, na kterých může ležet buď začátek nějaké funkce, zpracovávaná data nebo cíl skoku. Kromě toho mnoho assemblerů podporuje i pseudoinstrukce a také makra, která jsou mnohdy poměrně vysokoúrovňová. Výše uvedený program by v assembleru mikroprocesoru 80386 v šestnác­tibitovém režimu vypadal následovně (v levé části je pro porovnání zobrazen i strojový kód, který je generovaný z assemblerovského zdrojového kódu):

0000: B81300                       mov       ax,00013
0003: CD10                         int       010
0005: 6800A0                       push      0A000
0008: 07                           pop       es
0009: 33FF                         xor       di,di
000B: B106                         mov       cl,006
000D: C706750100E0                 mov       w,[00175],0E000
0013: BE4001                       mov       si,00140
0016: B520                         mov       ch,020
0018: 33C0                         xor       ax,ax
001A: 8BE8                         mov       bp,ax
001C: 93                           xchg      bx,ax
001D: 8BC5                         mov       ax,bp
001F: D3F8                         sar       ax,cl
0021: F7E8                         imul      ax
0023: 50                           push      ax
0024: 8BC3                         mov       ax,bx
0026: D3F8                         sar       ax,cl
0028: F7E8                         imul      ax
002A: 268905                       mov       es:[di],ax
002D: 8BC5                         mov       ax,bp
002F: D3F8                         sar       ax,cl
0031: C1FB05                       sar       bx,005 

Poněkud matoucí může být to, že program napsaný v assembleru se překládá překladačem nazvaným assembler, v praxi však je z okolností patrné, o čem se mluví: „píšu to v assembleru“ vs. „přeložil jsem ten program assemblerem“.

pc0507

Načtení operačního kódu instrukce do řadiče

Na rozhraní mezi assemblerem a vyššími programovacími jazyky (například Céčkem, Pascalem atd.) leží takzvaný autokód. Jedná se vlastně o velmi jednoduchý programovací jazyk, ve kterém je však možné používat pouze ty objekty, se kterými se pracuje v samotném assembleru. Mj. to znamená, že sice existuje možnost zápisu jednoduchých aritmetických výrazů:

A=A+B   (odpovídá add A, B v assembleru) 

nebo podmínek:

if A go to L1:L2:L3  (když A<0, skok na L1, když A=0 skok na L2, jinak skok na L3) 

ale žádné vyšší operace či datové typy již není možné použít. Autokód se v současnosti příliš často nepoužívá, místo toho se (v odůvodněných případech) kombinuje zápis části programu v assembleru s programem napsaným v některém plnohodnotném vyšším programovacím jazyce, například v céčku. V minulosti byla snaha o vytvoření jakéhosi hybrida mezi céčkem a assemblerem pro architekturu x86 – jednalo se o jazyk C–, který byl poměrně vynalézavý, například znal operátor >< – ovšem dnes už pravděpodobně není aktivně vyvíjen (poslední verzi si pamatuji ještě z dob DOSu).

6. Podrobnější popis instrukčního souboru

V předchozí části tohoto seriálu byl uveden celý instrukční soubor, tj. sada instrukcí, kterou náš ukázkový mikroprocesor dokáže provádět. Jedná se o celkem 32 instrukcí rozdělených do sedmi skupin:

Kód instrukce (hex) Mnemotechnická zkratka instrukce Význam
Aritmetické instrukce
00 ADD součet obsahu registrů A a B
01 ADC součet obsahu registrů s přenosem
02 SUB rozdíl obsahu registrů A a B
03 SBB rozdíl obsahu registrů s výpůjčkou
04 INC zvýšení obsahu registru A či B o 1
05 DEC snížení obsahu registru A či B o 1
Logické instrukce
06 AND operace bitového součinu nad všemi korespondujícími bity registrů A a B
07 OR operace bitového součtu nad všemi korespondujícími bity registrů A a B
08 XOR operace bitové nonekvivalence nad všemi korespondujícími bity registrů A a B
09 COM negace všech bitů jednoho z registrů A či B
Posuvy a rotace
0a RL rotace obsahu registru A či B doleva
0b RLC rotace obsahu registru A či B doleva přes příznak přenosu
0c RR rotace obsahu registru A či B doprava
0d RRC rotace obsahu registru A či B doprava přes příznak přenosu
0e ASR aritmetický posun obsah registru A či B doprava
Testování a porovnání
0f CMP aritmetické porovnání obsahu registrů a ovlivnění příznaků
10 TEST bitové porovnání obsahu registrů a ovlivnění příznaků
Přesuny mezi pamětí a registry
11 LD načtení konstanty či obsahu adresy z paměti do registru A či B
12 ST uložení obsahu registru A či B na danou adresu paměti
13 MOV přesun dat mezi registry
14 PUSH uložení obsahu registru A či B na zásobník
15 POP obnovení obsahu registru A či B ze zásobníku
Skokové a návratové instrukce
16 JMP nepodmíněný skok na zadanou adresu
17 CALL volání podprogramu
18 RET návrat z podprogramu
19 IRET návrat z přerušení (interrupt)
1a JC podmíněný skok za předpokladu, že je nastaven příznak přenosu (carry flag)
1b JNC podmíněný skok za předpokladu, že je vynulován příznak přenosu (carry flag)
1c JZ podmíněný skok za předpokladu, že je nastaven příznak nulovosti (zero flag)
1d JNZ podmíněný skok za předpokladu, že je vynulován příznak nulovosti (zero flag)
Nezařazené zbývající instrukce
1e NOP neprovádí se žádná operace, mikroprocesor přejde na další instrukci
1f HALT mikroprocesor se zastaví a čeká na příchod externího přerušení

V prvním sloupci výše uvedené tabulky je uveden takzvaný operační kód instrukce, což je v tomto případě hexadecimální hodnota bytu načítaného z operační paměti. Ve druhém sloupci je zapsáno jméno instrukce, neboli mnemotechnická zkratka anglického názvu instrukce. U jednotlivých procesorů, popř. výrobních řad procesorů, jsou použity různé délky mnemotechnických zkratek, od dnes již málo používaných jednopísmenných zkratek (L-load, A-add, R-move to register) přes pravděpodobně nejčastěji používané třípísmenové zkratky (ADD, SUB-subtract, MOV-move, JMP-jump) po názvy instrukcí složených z celých anglických slov, nejenom jejich zkratek (ADD, LOAD, STORE, MOVE, JUMP). V následující kapitole bude popsána instrukce MOV sloužící pro přesuny dat mezi registry A a B.

7. Instrukce pro přesuny dat mezi registry

Mezi často používané instrukce patří instrukce sloužící pro přesun dat mezi jednotlivými pracovními registry mikroprocesoru. Vzhledem k tomu, že náš ukázkový mikroprocesor obsahuje pouze dva pracovní registry označené A a B, jsou k dispozici pouze dvě instrukce pro přesun dat: první slouží pro přesun hodnoty z registru B do registru A a druhá pro opačný přesun, tj. z registru A do registru B. Instrukce pro přesun dat má mnemotechnickou zkratku mov se základním operačním kódem 0×13. Rozhodnutí o cílovém registru se v assembleru rozlišuje zápisem instrukce:

mov A,B    ; přesun z registru B do registru A
mov B,A    ; přesun z registru A do registru B 

V závislosti na tom, ze kterého registru se data čtou a do kterého registru se zapisují, se musí rozšířit i samotná instrukce o adresní část. V případě, že byte následující za operačním kódem ve strojovém kódu obsahuje hodnotu 0×01, bude se provádět přesun z registru A do registru B (cílem je B), v případě hodnoty 0×00 bude přesun opačný, tj. z registru B do registru A (cílem je A). Možná vás napadlo, že vzhledem k tomu, že náš mikroprocesor obsahuje pouze 32 instrukcí, které lze kódovat pomocí pěti bitů (25=32), je možné onen jediný bit rozhodující o tom, která ze dvou přenosových operací se skutečně použije, vložit přímo do operačního kódu. Je to myšlenka správná a prakticky všechny mikroprocesory mají svoji instrukční sadu „hutnější“ než náš mikroprocesor, ovšem pro účely snazšího čtení strojového kódu ponechám operační kód a adresní část v samostatných bytech, i když to podstatným způsobem program prodlužuje:

Strojový kód Význam
13 00 mov A,B
13 01 mov B,A

U některých mikroprocesorů, které obsahují větší množství pracovních registrů, samozřejmě stoupá i počet různých „cest“, kterými mohou data putovat. Například u mikroprocesoru Intel 8080 je celkem 63 operačních kódů z 256, tj. celá čtvrtina, rezervovaná právě pro přesuny dat, přičemž bylo možné kódovat i instrukce typu mov b,b, tj. přesun z registru do toho samého registru (v podstatě se jedná o jinak zakódovanou instrukci nop). Samotná implementace přesunu je uvnitř mikroprocesoru celkem jednoduchá, jak ostatně ukazují následující dvě animace.

pc0508

Způsob provedení instrukce mov A, B

Ještě dodám, že existují i mikroprocesory, které nemohou pro přesuny použít interní sběrnici – v tomto případě jdou veškerá data přes ALU, která musí obsahovat operaci, která pouze převede vstupní data na výstup bez jejich úpravy. Tato zdánlivá nelogičnost má svoje opodstatnění, protože se zjednoduší jak řízení celého mikroprocesoru, tak i jeho interní struktura.

pc0509

Způsob provedení instrukce mov B, A

bitcoin školení listopad 24

8. Obsah následující části tohoto seriálu

I v další části seriálu budeme pokračovat v popisu funkcí, které provádí mikroprocesor při zpracování instrukcí, přesněji řečeno operačních kódů (opkódů), načítaných z operační paměti. Především si vysvětlíme všechny aritmetické a logické instrukce a jejich použití (spolu s instrukcemi skoku a příznakovým registrem) pro řízení běhu programu.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.