Megoldás Digitális technika II. (vimia111) 4. gyakorlat: Processzorok alapvető jellemzői Elméleti anyag: Mikroprogramozott vezérlő tervezése o Adatstruktúra tervezése, vezérlő és feltétel jelek felvétele o Vezérlő struktúra és méretek választása (mi elsősorban a horizontális, lépugrik típusú vezérlőt használjuk) o Folyamatábra (vagy állapotgráf) felvétele o Mikroprogram készítése, címkiosztás, kódolás, ROM programozása, kipróbálás Mikroprocesszorok/mikrovezérlők felépítése o Adatstruktúra tervezése, jellemző megoldások o Alpvető adatstruktúra felépítések, utasítás formátum, programméret o Stack (0R), Akkumlátoros (1R), és általános 2R ill. 3R adatfeldolgozók o Műveleti hatékonyság elemzése a Fibonacci generátor alapján Programozható logikai áramkörök o A programozható logikai eszközök típusai (PAL, PLD, FPGA) o Jellemző komplexitási tulajdonságok, programozási technológiák o Az alap logikai építőelem típusai o Egyéb erőforrások (I/O, huzalozás) A Verilog HDL elemei o A HDL nyelvek szerepe, célja o A használat lapvető egysége: a modul o Az interfész lista kialakítása Irodalom: A tárgy honlapján található FPGA_Digit_II bemutató A tárgy honlapján található Verilog nyelvvel kapcsolatos anyagok Benesóczky Zoltán: Digitális tervezés funkcionális elemekkel és mikroprocesszorokkal, egyetemi tankönyv, MK55033, 103-112. Gyakorló példák: 4.1. Készítsen mikroprogramozott vezérlő egységet, amely a START-tal való elindítás után nagyság szerint sorbarendezi egy RAM tartalmát, rendezés után a 0-ás címre kerül a legkisebb szám, a maximális címre pedig a legnagyobb szám. Célszerű az előadáson megismert „buborék” algoritmust használni. a. Először olyan adatstruktúrát készítsen, amely képes két egymás utáni cím tartalmának nagyság szerint rendezésére. Készítse el ehhez a részhez a vezérlő programot és próbálja is ki. b. Következő lépésben egészítse ki az adatstruktúrát a teljes rendezéshez szükséges további egységekkel és készítse el a teljes mikroprogramot! A feladat megoldásának dokumentációja megtalálható a RENDEZ_RAM_mintadoku_2011.pdf anyagban, maga a kapcsolás pedig RAM_rendezes.dwm
1
Ebben próbáljuk meg a két cella-kiolvasást és ha kell, a fordított visszaírás részletet megnézni. A következő ábra a könyvtári kapcsolás módosítását mutatja a fenti vizsgálathoz. Beiktattuk a Töréspontot és leállási feltételt fogalmaztunk meg a x1100 címre Ezzel egy RAM cella-pár eseményeit nyomon lehet követni: van tartalomcsere vagy nincs. RAM_rendezes_torespont.dwm Az alábbi idődiagramon egy RD0-RD1, WR0-WR1 sorozat látszik
A teljes példa részletes kidolgozása megtalálható a RAM_RENDEZES projektben! Ott a RAM kezdotartalom.map fájlban található a kezdeti RAM tartalom, ha valaki menetközben „megrendezi” a RAMot, akkor ezt célszerű egy új rendezés előtt betölteni. Maga a teljes rendezés sokáig (10-20 percig) tart, ezen lehet spórolni, ha az adatstruktúrát úgy módosítjuk, hogy ne F-et töltsön a 2. számlálóba, hanem pl, 4-5-öt. 2
4.2. Regiszter alapú HW STACK egység tervezése Az előadáson szerepelt regiszterbázisú stack használata, mint általános célú adatstruktúra pl. kalkulátorokban. A feladat regiszter alapú STACK egység tervezése, 4 szintre, 4 bites regiszterekkel. A stack szinkron, órajelvezérelt regisztereket tartalmaz, vezérlőjelei CLK, PUSH, POP, RST. (A RST nem lenne szükséges, hiszen a stack használata nem igényli, de esetleg teszteléskor hasznos lehet), A stack adat interfészei INPUT[3:0], OUTPUT[3:0]. A terv alapja egy 4 üzemmóddal rendelkező regiszter. Az üzemmódok a bemeneti 4x1-es multiplexerrel választhatók ki. Ennek megfelelően 4 adat tölthető a regiszterbe: Nulla (S1S0=00), saját tartalma (S1S0=01), az előző szint (S1S0=10), vagy a következő szint (S1S0=11).
Figyeljük meg az adatok mozgását. Minden regiszter az őt megelőző ill. követő szinttel van kapcsolatban. A LEVEL_0 az állandó bemenet és az állandó kimenet is egyben. A szintek száma tetszőlegesen bővíthető az adott minta szerint.
3
Megjegyzés: Tekinthetjük az egészet egy 16 bit méretű, adatsoros shiftregiszternek is, ahol a SHR és SHL műveletek 4 bites adatméret egységben értelmezettek. A bővíthetőség ebben az esetben is egyértelmű. A STACK 3 vezérlő bemenettel rendelkezik, a bemenőjelek értelmezését a vezérlési táblázat specifikálja. A bal oldali kis kombinációs logika a 4 üzemmódú STACK regiszter egység vezérlő jeleit állítja elő, az egyedi PUSH, POP, RST jelek alapján. A RST jel prioritása a legnagyobb, a másik két jel (PUSH és POP) azonos prioritású. Az együttes PUSH és POP nem okoz változást a STACK tartalmában. A tervezői fájlok: STACK4x4.dwm, STACK_REG_v2.dwm, ezeket mellékeltem. Érdemes megemlíteni, hogy nagyobb méret esetén hatékonyabb a memória alapú megoldás, ahol egy címszámláló végzi a STACK tetejének kezelését. Ebben az esetben (ha a STACK felfelé növekszik) akkor a PUSH művelet egy pre-inkremens címzésű írás, az POP művelet pedig egy post-dekremens olvasás. 4.3. Egészítsük ki a STACK-et egy EMPTY és FULL jelzéseket biztosító logikával. A STACK használata során a legutolsó adatot kivéve nincs explicit információ a STACK-ben lévő adatokról, továbbá ezek mennyiségéről. Alapvetően ez csak lesz akkor érdekes, amikor esetleg a STACK túlcsordulna, viszont jellegéből adódóan ezt csak akkor vesszük észre, amikor az adatok kivételével elérjük az „alulcsordulás” állapotot. Az állapotjelző kiegészítés lehetséges verziói: a, Egy 3 bites fel/le számláló, a max. 4 és min. 0 értékeknél megállítva b, Egy 5 bites shiftregiszter, szintén szélső állapotban megállítva. Indulásnál, illetve később a számláló 000 (az SHR 10000) állapotában aktiválja az EMPTY jelet, jelezve az üres állapotot. A számláló 100 (az SHR 00001) állapotában kiadja a FULL jelet. Ezek megjelenése még nem hiba, tehát rendszer szinten felhasználható megelőző ellenőrzésre. Ha azonban érvényes üres STACK esetén POP műveletet hajtunk végre, az azonnali hibát eredményez. Ha tele STACK esetén PUSH műveletet adunk ki, akkor az szintén hibát
4
eredményez, de nem azonnal, csak amikor az ezutáni működés során (esetleg sorozatos POP/PUSH műveletek után) egyszer újra visszaáll az üres állapot, és a rendszer még egy POP műveletet adna ki. Tehát az igazán precíz megoldás az lenne, hogy a FULL esetén kiadott PUSH parancs beállít egy belső ERROR flag-et, amit vagy azonnal aktivál (STACK ERROR), vagy csak akkor, amikor a végrehajtott műveletekben 4-gyel több POP művelet fordult elő, mint PUSH ( ERROR „HIBÁS ADAT”). STACK_szamlalo_v2.dwm:
4.4. 3 címes regisztertömb belső felépítése A RISC CPU-k adatstruktúrájának jellemző eleme a 3 regiszter címes címzést (1 írási cím, 2 olvasás cím) biztosító regisztertömb, 8-16-32 bit adatszélességgel és 8-16-32 mélységű méretben. Az alkatelem használatával a két operandusú műveleteket közvetlenül, felesleges adatmozgatások nélkül lehet elvégezni, Rdest = Rsrc1 FUN Rsrc2, utasítás formátum: FUN_ DEST_ SRC1_ SRC2. Tervezzünk meg egy 4 bit széles, 4 regiszterből álló 3 cím elérésű egységet. Az interfész jelek: CLK, WR_ADR[1:0], RD_ADR_A[1:0], RD_ADR_B[1:0], WR_EN, WR_DATA[3:0], RD_DATA_A[3:0], RD_DATA_B[3:0]. A terv 4 db élvezérelt DFF-ból álló 4 bites regisztert tartalmaz. A RST jelet nem használjuk, mivel a processzorok regisztereire a RST alapvetően nincs hatással, tehát a programozó nem tételezheti fel, hogy a regiszterek induláskor bármilyen alapértékkel rendelkeznek. Mivel a DW DFF-ok-nak nincs CE jelük, és nem akartam túl sokat rajzolni, ezért a bemeneti MUX-os adatválasztás helyett az órajelet kapuztam a WR_DEC makróban. Ez nem szép megoldás, de most fogadjátok el. Ha a táblára felrajzoljátok, akkor inkább szinkron töltéssel rendelkező regisztereket rajzoljatok, és a dekóder ezeket vezérli, míg az órajel közös és globális, azaz nem kapuzott. Szóval a REG_FILE 3 regiszter címet képes egyszerre kezelni, tehát támogatja az olyan utasításkészletet, amelyben 3 operandus szerepel, vagyis Rdest = Rsrc1 FUN Rscr2. Ez hasznos, mert nagyon ritkán kell csak regiszter másolásokat végeznünk. Ugyanakkor 32 méretű regiszter tömb esetén ehhez 3x5 bit, azaz 15 utasításbit kell, ami csak 32 bites utasításformátum mellett engedhető meg. A terv megtalálható a REG_tomb_v2.dwm fájlban.
5
Megjegyzés1: A fenti megoldásban ugyanazt a címet használva mind a WR_ADR, mind a RD_ADR_A bemeneten is, a szintén elterjedt 2 címes regisztertömböt kapjuk, ahol a művelet eredménye mindig felülírja az egyik operandust : Rsrc1 = Rsrc1 FUN Rsrc2. Itt a programba gyakran kénytelenek vagyunk extra adatmozgatásokat beépíteni (ha pl. szeretnénk megőrizni a Rsrc1 eredeti értékét). Ugyanígy egy ilyen megoldásnál gondolnunk kell a SUB kivonás (A-B) ALU művelet mellett a fordított kivonás RSUB (B-A) ALU művelet megvalósítására is, vagy a (B-A) kivonás 2 utasítást igényel (1 regisztermásolást és egy kivonást). Megjegyzés2: A fenti megoldásban a WR_ADR címet használva a RD_ARD_A bemeneten is és a RD_ADR_B címet elhagyva a szintén elterjedt 1 címes regisztertömböt kapjuk, ami az un. akkumulátor típusú adatstruktúrák jellemző elem. Ebben az esetben a művelet a következő lehet: ACC = ACC FUN Rsrc. Ez olcsó, de rengeteg extra adatmozgatást kíván a regiszterek és az akkumulátor között. A regiszterek írása gyakran csak az Rdst = ACC áttöltéskor lehetséges, esetleg még az Rdst = Rdst +1, vagy Rdst-1 esetekben. Megjegyzés3: Hangsúlyozzuk, hogy az olvasás a cím megjelenésére azonnali, közvetlen, aszinkron végrehajtódik (kombinációs jelutak), míg az írás parancs az adott címen az órajelciklus végén, a felfutó élre (szinkron) hajtódik végre. A tervezői fájlok: REG_tomb_v2.dwm, WR_DEC_macro.dwm mellékelve. 4.5. ALU egység tervezése Az adatstruktúra másik fontos eleme az ALU. A szükséges művelethalmaz (aritmetikai, logikai, shift) kiválasztása gondos analízist igényel, a hatékonyságot egyértelműen meghatározza. Tervezzünk egy ALU egységet, ami a következő műveleteket biztosítja: FUN[2:0] 000 001 010
Funkció A+B A-B A+1
6
Leírás Összeadás Kivonás Inkrementálás
011 100 101 110 111
A Adatátmásolás A and B Bitenkénti AND A or B Bitenkénti OR A xor B Bitenkénti XOR not B Bitenkénti NOT
A triviális megoldás a 8 funkció önálló realizálása és egy nagyméretű 8 bites adatokon dolgozó 8 bemenetű multiplexer használata. Rajzoljuk fel vázlatosan, de magyarázzuk el, hogy ez nagyon gazdaságtalan (ez szerepelt az előadáson is). Egy jobb megoldást eredményez az alábbi összeadóra és elő-feldolgozóra alapuló struktúra. Ennek megfelelően a kimeneti eredményt mindig az összeadó kimenete adja, és a bemeneti operandusokat A[7:0], B[7:0] egy átalakítás után vezetjük az összeadó IA[7:0], IB[7:0] bemeneteire.
A fenti blokkvázlat alapján az előfeldolgozó által szükséges műveletek a belső (IA[7:0], IB[7:0]) adatkimenetek és a Cin jel generálását jelentik. Amennyiben az eredetileg tervezett notA művelet helyett a notB műveletet valósítjuk meg, a hálózat egyszerűbb lehet (mivel ez a részfeladat része az A-B műveletnek is). Így a műveleti tábla az elő-feldolgozó funkcióit is részletezve a következő: FUN[2:0] Funkció Leírás IA IB Cin 000 A+B Összeadás A B 0 001 A-B Kivonás A not B 1 010 A+1 Inkrementálás A 0 1 011 A Adatátmásolás A 0 0 100 A and B Bitenkénti AND 0 A and B 0 101 A or B Bitenkénti OR 0 A or B 0 110 A xor B Bitenkénti XOR 0 A xor B 0 111 not B Bitenkénti NOT 0 notB 0 Egyszerűen kiolvasva: IA = A, ha FUN[2] = =0, egyébként 0, tehát ez egy 2 bemenetű ÉS kapu minden bitre. Cin = 1 ha FUN == 1 vagy FUN= 2, tehát ez kettő db három bemenetű ÉS + 1VAGY kapu . IB = Ez már bonyolultabb, de azért megtervezhető. 5 bemenet, 1 kimenet minden bitre.
7
FUN[2:0] IBx, x=3…0 000 Bx 001 not Bx 010 0 011 0 100 Ax and Bx 101 Ax or Bx 110 Ax xor Bx 111 not B Megjegyzés: A műveletek kiválasztása véletlenszerűen, egy példa alapján történt. Megemlíthető, hogy nem feltétlenül optimális, mivel ha az utasításban tudunk közvetlenül adatot megadni (és ez általában teljesül, mint konstans operandus), akkor az A+1 vagy a notA (notB) felesleges. Az inkrementáláshoz használható a B bemeneten az 1, mint konstans, a negálás pedig az A XOR 0xFF, azaz csupa egyes konstans. Tehát egy ALU funkciókészlet kialakítása gondos tervezést igényel. 4.6. Feltételjelek az előző ALU-hoz A CPU vezérlőegysége a program következő utasításának címét feltételes utasítás esetén pl. az aktuális ALU művelet eredménye alapján határozza meg. Az aritmetikai típusú ALU műveletekben mindig kettes komplemens számábrázolást tételezünk fel, mind a bemeneten, mind a kimeneten. A tipikus feltételjelek a következők: Z C N V
Az ALU művelet eredménye nulla Az ALU művelet végrehajtásakor átvitel keletkezett Az ALU művelet eredményének előjele negatív Az ALU művelet során túlcsordulás keletkezett
Tervezzük meg a feltétel jelek logikai hálózatát! 1. 2. 3. 4.
Z = RES[7:0]==0, azaz a hálózat egy 8 bemenetű NOR kapu. C= Cout4, az ALU ADDER átvitelbit közvetlenül felhasználható N= RES[7], az eredmény legnagyobb helyiértékű bitje közvetlenül felhasználható. V= ? Mikor van túlcsordulás kettes komplemensű számokkal végzett műveleteknél?
Túlcsordulás csak aritmetikai műveletek esetén fordulhat elő. Azonos előjelű operandusok esetén: Összeadásnál is és kivonásnál is, ha az operandusok előjele azonos, de az eredmény előjele ettől eltérő, akkor túlcsordulás van (POS + POS = NEG, vagy NEG + NEG = POS). Eltérő előjelű operandusoknál a helyzet nem ilyen egyértelmű. Összeadásnál soha nem keletkezik túlcsordulás (POS + NEG vagy NEG + POS eredménye mindig helyes), ekkor soha nem lépünk ki a számábrázolási tartományból. Kivonásnál a NEG –POS vagy a POS–NEG művelet eredménye előjel szerint tetszőleges lehet, de sajnos túlcsordulás ezzel együtt is előfordulhat. (Pl. -100 – (+5) = -105, -100 – (+50) = -150, de ez kisebb lenne mint -128, tehát = +106). Vagyis nem elegendő az előjel eltérése, az operandusok pozíciója és értéke is fontos. Ha az előjel azonos az első operandus előjelével, akkor a művelet eredménye helyes.
8
Összefoglalóan a kettes komplemens ADD/SUB áramkörben az V túlcsordulás bitet a legegyszerűbben a Cout3 XOR Cout4 hálózattal állíthatjuk elő, vagyis az utolsó előtti és az utolsó bitpozíciók átvitelbitjeit kell használnunk. A példához tartozó DW fájlok: ALU_teszt_v2.dwm:
Az ALU_macro_v2.dwm belső felépítése:
Ebben az ELŐFELDOLGOZÓ: 9
és az ÖSSZEADÓ:
10