Vysoké učení technické v Brně Fakulta informačních technologií
DIPLOMOVÁ PRÁCE
2004
Pavel FALTÝNEK
Vysoké učení technické v Brně Fakulta informačních technologií
DIPLOMOVÁ PRÁCE Podpora výuky hardware na bázi FPGA
2004
Pavel FALTÝNEK
Zadání 1. Navrhněte a implementujte komplexní sadu HW komponent využitelných pro demonstraci a výuku, pro práci s FPGA a návrhovým SW. 2. Komponenty zvolte vhodně tak, aby se vzájemně doplňovaly, tvořily celistvý systém a pokryly co nejširší spektrum problémů, které vznikají při výuce v tomto směru. 3. Jako nosnou část zvolte komplexní příklad pokrývající všechny fáze návrhu hardware – procesor. Vhodně zvolte instrukční sadu, využijte nějaký generický simulátor pro simulaci vašeho procesoru, zajistěte kompilaci programu do spustitelné binární formy. 4. Procesor implementujte v FPGA a na vhodném programu demonstrujte správnou činnost.
i
Prohlášení Prohlašuji, že jsem tuto diplomovou práci vypracoval samostatně pod vedením Dr. Ing. Otto Fučíka. Další informace mi poskytl Bc. Filip Höfer, autor programového nástroje nsim. Uvedl jsem všechny literární prameny a publikace, ze kterých jsem čerpal.
ii
Abstrakt Tato práce uvádí čtenáře do základní problematiky při návrhu procesorového systému pro programovatelný hardware – FPGA. Úvodem jsou zmíněny motivační impulsy k tomuto přístupu, stručně je probrána cílová platforma FPGA a možnosti návrhu formou implementace v jazyce pro popis hardware (VHDL). Po teoretickém úvodu do principů a architektury procesoru a zpracování instrukcí je popsáno několik cest, kterými je možno směrovat následující návrh. Pro každou z možností je uvedena jedna volba s patřičným odůvodněním rozhodnutí. V připraveném prostředí je pak proveden návrh jednotlivých komponent s respektováním vazeb na okolí. Současně jsou diskutovány možnosti řízení navrhovaných komponent, které se ve výsledku odrážejí ve vznikající instrukční sadě. Část prostoru je pak také věnována softwarovým prostředkům potřebným jak k vlastní implementaci, tak k práci s nově vzniklým systémem. Klíčová slova Xilinx FPGA, VHDL, Procesor, ALU, assembler, nsim, Liberouter
iii
Abstract This work describes basic problem solutions rising from processor system design for FPGA. In the introduction there are remarked motivation inducements for chosen platform, which is shortly discussed in next text as well as necessary system language support. Implementation language in this work is VHDL for hardware design and C for support software. Next passage discusses common processor architectures and instruction processing. Hereafter there are discussed possible directions that can lead following design line. Selected directions determine implementation ways of designed components. These ways also implies an instruction set design, that is simultaneously considered. Before the conclusion there are mentioned software tools that are developed for the designed system or that are used for developing the system. Keywords Xilinx FPGA, VHDL, Processor, ALU, assembler, nsim, Liberouter
iv
Obsah Zadání
i
Prohlášení
ii
Abstrakt, Klíčová slova
iii
Abstract and Keywords
iv
1 Úvod
1
2 Prostředky návrhu a realizace 2.1 Programovatelný hardware . . . . . . . . . . . . . . . 2.1.1 Historický vývoj programovatelného hardware . 2.1.2 Technologie FPGA . . . . . . . . . . . . . . . . 2.2 Jazyk pro popis hardware . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
1 1 2 2 4
3 Procesor 3.1 Základní koncepce . . . . . . . . 3.2 Zřetězené zpracovávání instrukcí 3.3 Linky zřetězeného zpracování . . 3.4 Závislosti a konflikty . . . . . . . 3.4.1 Datové závislosti . . . . . 3.4.2 Řídící závislosti . . . . . . 3.4.3 Strukturní závislosti . . .
B assembler B.1 Název . . . . . B.2 Souhrn voleb . B.3 Popis . . . . . . B.4 Volby . . . . . B.5 Příklad použití B.6 Gramatika . . . C nsim C.1 Název . . . . . C.2 Souhrn voleb . C.3 Popis . . . . . . C.4 Volby . . . . . C.5 Příklad použití
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . .
47 47 47 47 47 48 51
. . . . .
52 52 52 52 53 53
D Liberouter
58
E Obsah CD E.1 ASM . E.2 C . . . E.3 nsim . E.4 TEX . E.5 VHDL
60 60 60 60 60 61
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
vi
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
1
Úvod
V této práci bude čtenář obeznámen s jedním z možných postupů při návrhu systému v programovatelném zařízení FPGA. V první části jsou uvedeny motivační pohnutky vedoucí k preferenci tohoto způsobu vývoje a realizace číslicových zařízení a systémů. Následuje úvod do pojmů FPGA a programovatelného hardware obecně a stručné seznámení s vývojem této technologie. Dále jsou uvedeny možnosti popisu chování resp. programování této technologie s důrazem na jazyk VHDL. Následující část nastiňuje některé principy při sestavování procesoru ze základních jednotek, způsoby jejich vzájemného propojení a z toho plynoucí důsledky. Propojení jednotek má přímý vliv na zpracování instrukcí procesorem, které je probíráno dále. Tato kapitola předchází části věnované návrhu jednoduchého procesorového systému v programovatelném poli FPGA. Zde jsou rozebrány základní přístupy, které se obecně při návrhu používají a jsou voleny další směry, kterými se bude tento konkrétní návrh ubírat. Postupně jsou probrány jednotlivé komponenty potřebné k sestavení procesorového systému a zároveň jsou analyzovány jejich požadavky na řízení, které se promítají do návrhu instrukční sady. Na závěr je uvedena nezbytná kapitola shrnující použité programové vybavení ať již hotové, či pro tento účel implementované. Následuje krátký popis zařízení sestaveného z navržených komponent a možností k dalšímu rozšiřování a zdokonalování tohoto prvku. V přílohách jsou pak uvedeny technické dokumentace a poznámky.
2 2.1
Prostředky návrhu a realizace Programovatelný hardware
V současnosti se stává nutností mít možnosti pro rychlý a efektivní návrh a výrobu hardware, který je schopen provádět požadovanou činnost. Rostou nároky na výpočetní výkon, jež je ke zdárnému splnění zadaných úloh potřebný. Přicházejí požadavky na řešení složitějších a náročnějších úkolů, nejlépe přímo v hardware. Jedná se např. o akceleraci zpracovávání signálů (zvukových, obrazových, měřených dat apod.), urychlování algoritmicky složitých výpočtů, zpracování dat v expandujícím prostředí síťových technologií apod. Lze namítnout, že k řešení nastíněných problémů je ideálním zařízením univerzální počítač. Toto tvrzení nelze zavrhnout, v některých situacích však můžeme s klasickým počítačem standardu PC narazit na jistá omezení. Vzhledem k jeho základní filosofii univerzálního prostředku je třeba PC dovybavovat externími komponentami, jiné komponenty nahrazovat, ale téměř vždy dojdeme k situaci, kdy některé části nebudou pro danou třídu úloh dostatečně výkonné či budou zcela chybět. Naopak jiné komponenty jsou k řešení zadaného problému využity jen zřídka či vůbec. Typicky problémovými místy standardních počítačů bývají nedostatečně propustné sběrnice, slabá místa v komunikačních rozhraních mezi hardwarem a softwarem – problémy zahlcování při obsluze přerušení, slabá nebo žádná podpora paralelního provádění výpočtů, nedostatečná flexibilita použitých hardwarových komponent. V neposlední řadě se jako nevýhoda obecného počítače může jevit vyšší prostorová náročnost, i když minimalizace postupuje i v těchto oblastech kupředu. Energetická náročnost a nutnost odběru vznikající tepelné energie jistě také nejsou zanedbatelnými. Z předchozích úvah by tedy mohlo vyplynout rozhodnutí preferující univerzální počítače na řešení úloh nevyžadujících speciální prvky a podmínky, které s touto platformou vystačí. V ostatních případech pak nezbývá, než se poohlédnout po řešení „na míruÿ. To by mohlo znamenat navržení prototypů a výrobu speciálních zařízení na zakázku. 1
Návrh a výroba nového hardware ovšem není nejlevnější záležitost. V době obrovského vývoje a rychlého morálního opotřebení v této oblasti se nemusí vůbec vyplatit. Pokud není záměrem sériově vyrábět desetitisíce kusů, kdy by bylo možné nechat si zakázkově vyrobit konkrétní prvky, nezbývá než zařízení sestavit ze součástí či modulů, které jsou momentálně k dispozici na trhu a jsou dostupné. Uvážíme-li navíc fakt, že v návrhu zařízení mohou být nedostatky či dokonce chyby a k jejich eleiminaci bude potřeba vyrobit několik testovacích prototypů, opět se zvyšuje celková cena zařízení. Řešením výše nastíněných problémů by mohl být hardware, který by bylo možno nastavit tak, aby prováděl úlohu, která je zrovna potřebná, aby bylo možno levně zkoušet a testovat různé návrhy a verze řešení apod. Tyto myšlenky pravděpodobně daly vzniknout modernímu programovatelnému hardware. Princip univerzálního hardware totiž není úplně nový. Samozřejmě prošel svým vývojem. 2.1.1
Historický vývoj programovatelného hardware
První obvody obsahovaly dva typy logických sítí, jež vhodně propojeny mohly realizovat libovolnou funkci. Typicky se jednalo o součet součinů. Tedy vstupní proměnné kombinovány hradly AND, výstupy těchto fukcí pak sečteny hradly OR. U těchto obvodů byly části zpočátku programovatelné pouze jednou, pomocí přepalovaní propojek, to prováděl výrobce (technologie PROM). Jednalo se o jednu ze sítí AND/OR. Druhou síť si pak mohl naprogramovat uživatel. Zde se používala technologie EPROM, tzn. bylo technicky možno uvést obvod do nenaprogramovaného stavu, pomocí UV záření. Představiteli takovýchto typů obvodů byly PAL/PLA (Programmable Logic Array) a PLD (Programmable Logic Device). Později je nahradily obvody s programovací technologií EEPROM, kde již bylo přeprogramování mnohem snažší, pomocí napěťových úrovní. Nazývaly se GAL (Generic Array Logic). Ovšem stále platilo, že pro zavedení funkce byl nutný programátor a speciální předpis, který určoval, jak bude který konkrétní výstup – pin pouzdra obvodu – svázan s ostatními výstupy či vstupy. Technologie EEPROM byla již velkým pozitivem, ale možnosti a schopnosti obvodů byly stále omezené. Rozšířením obvodů PLD je třída CPLD (Complex PLD), která zvyšuje počet buněk v logických sítích a přidává nové schopnosti. Rozšířením PAL/PLA vznikly FPLA (Field Programmable Logic Array). Obsahují složitější celky, které je možné vzájemně propojit. Další vývoj dospěl k technologii FPGA (Field Programmable Gate Array), která se také používá v současné době. Těmito obvody se tedy v následujícím textu budeme zabývat podrobněji. 2.1.2
Technologie FPGA
Obvody typu FPGA obsahují funkční bloky a propojovací sítě. Pomocí programovacího předpisu, lze tyto bloky mezi sebou propojovat a vytvářet tak požadovanou funkci. FPGA se programuje technologií RAM1 , tzn. přeprogramování obvodu je otázkou okamžiku. Nevýhodou této technologie oproti výše popisovaným je to, že obvod je třeba automaticky programovat po připojení napájení, před startem aplikace. Z jiného pohledu to může být výhodou, protože FPGA (resp. jejich části) lze přeprogramovávat za běhu. To může vést k tomu, že dle konkrétních požadavků daných stavem aplikace a vnějších vstupů se může zařízení samo dle potřeby přeprogramovat. Tento trend přenáší rovinu řízení obecného procesoru softwarem do hardware, kdy vlastně jakýsi software – rozuměj programovací předpis pro FPGA, určuje to, co se 1
Narozdíl od CPLD, které nabízejí programování technologií FLASH, kdy konfigurace obvodu zůstává zachována i po odpojení napájení
Obrázek 1: Vnitřní struktura FPGA čipu Virtex II bude v hardware odehrávat. Aby možností bylo více, i hardware může být (a často bývá) řízen programem ve smyslu software. Tzn. v FPGA je „naprogramovánÿ procesor, specifický pro řízení dané úlohy. K dispozici má program, dle kterého se řídí. Zde je vidět obrovská flexibilita, jednak v tom, že je možné dynamicky měnit program pro řízení procesoru, ale také v tom, že v případě potřeby je možné nahradit celý procesor. Ať již třeba za jiný optimalizovaný, či za pouhý stavový automat nebo jinou komponentu. Technologii FPGA začala vyrábět a prosazovat firma Xilinx. V podstatě určila na tomto poli sadu neoficiálních standardů, které jsou obecně platné a podle nichž se více či méně orientují i ostatní výrobci těchto prvků. Z dalších významnějších výrobců jmenujme například firmu Altera. Xilinx v současnosti dospěl již ke čtvrté generaci FPGA. Dále popisované vlastnosti jsou zhruba platné pro všechny generace, s tím, že jednotlivé prvky architektury se mohou lišit co do počtu, velikostí, kapacit, rychlostí apod. na jednom čipu. Xilinx FPGA podporují nejrůznější technologie komunikačních úrovní, obsahují (dle typu) od jednotek tisíc až po deset miliónů ekvivalentních hradel. Zapouzdřují paměťová pole různých typů, rychlé multiplexery, násobičky, rychlé řetězce přenosové logiky, digitální hodinové manažery pro korekce úpravy a distribuci hodinových signálů, vstupně/výstupní buňky s možnostmi DDR2 časování, speciální hodinové a propojovací sítě, třístavové sběrnice atd. Podrobné informace o každém obvodu lze nalézt v konkrétním katalogovém listě či manuálu výrobce. Pro příklad uveďme organizaci obvodu FPGA řady Virtex II. Obvod je rozčleněn v pravidelnou strukturu, jak je patrno z obrázku 1. Základním stavebním prvkem jsou konfigurovatelné logické bloky (CLB). Každý obsahuje čtyři další části (slice) a dva třístavové budiče. Každý slice pak zapouzřuje dva funkční generátory, dva klopné obvody typu D a další kom2
Dual Data Rate – přenos dat je synchronizován oběma hranami hodin
3
binační logiku. Z našeho pohledu nejdůležitějšími částmi jsou právě tyto „slicesÿ, resp. jejich obsah. Funkční generátory jsou vlastně tabulky generující logickou funkci čtyř proměnných. V podstatě se jedná o rychlou paměť 16x1 bit, tzn. 16 paměťových pozic o šířce dat jednoho bitu. Skutečně také každý funkční generátor lze nakonfigurovat jako plnohodnotný paměťový prvek. V terminologii Xilinx FPGA – Distributed RAM. Distribuovaná v tom smyslu, že je rozložena po celém čipu a tudíž dostupná v každém místě bez dlouhých zpoždění. Je vhodná pro ukládání menšího množství dat. Kromě logické fukce či distribuované paměti lze funkční generátor nakonfigurovat ještě jako posuvný registr. Další významnější položkou FPGA Virtex je bloková paměť (Block RAM) umístěná mezi čtveřicí CLB a násobičkou. Je konfigurovatelná co do šířky slova i velikosti a je vhodná pro ukládání větších objemů dat než distribuovaná paměť. D-klopné obvody z jednotlivých CLB bloků jsou vhodné pro realizaci registrů, čítačů apod. – obecně sekvenčních prvků. Ostatními prvky se zde nebudeme zaobírat, laskavý čtenář nechť nahlédne do příslušného katalogového listu (datasheet). Pro náš případ viz. [5].
2.2
Jazyk pro popis hardware
V předchozí části bylo uvedeno, co je možné použít pro výstavbu nového hardwarového zařízení, je-li zvolena technologie programovatelného hardware. Je nutné však také vědět jak daný obvod naprogramovat. Existuje několik způsobů, kterými se dá dospět k cíli. Zde bude zvolen jeden konkrétní a tím bude jazyk pro popis hardware. Konkrétně VHDL (VHSIC HDL – Very High Speed Integrated Circuit Hardware Description Language). Tento jazyk je standardizován organizací IEEE, což zaručuje širokou kompatibilitu a jednotnost. Periodicky jsou prováděny revize standardu3 . VHDL má široké výrazové možnosti a je třeba rozlišovat, k čemu bude zapsaný kód určen. Pomocí VHDL lze totiž jednak popsat hardwarové prvky, ale také dalšími jazykovými konstrukcemi lze zapisovat obecné algoritmy, provádět testování hardware, jeho simulaci apod. To přináší velkou výhodu homogenního prostředí pro různé činnosti. V hlavních směrech rozlišujeme tedy VHDL kód syntetizovatelný a simulační. Syntézou se rozumí převod VHDL kódu do binárního předpisu, který je určen k naprogramování obvodu FPGA. Pouze omezená podmnožina jazykových konstrukcí je syntetizovatelná (tedy převoditelná do reálného hardware). Samozřejmě tato množina není pevná, záleží na tom kterém nástroji (překladači/syntezátoru), které konstrukce (a jakým způsobem) je schopen reflektovat v hardware. Existují ustálené typické zápisy či konstrukce hardwarových prvků, ve kterých syntezátory bezpečně poznají, o co se jedná. Samozřejmě čím blíže k hardwaru autor napíše VHDL kód, tím menší prostor dává syntezátoru k omylu či spekulacím. Mnohé vývojové nástroje obsahují pro své syntezátory přímo šablony jazykových či jiných konstrukí. Uživateli tak ani nemusí znát/pamatovat přesnou syntaxi či chování prvku, prostě jej vloží do kódu a má zaručen správný výsledek. Při popisování jazykem VHDL můžeme volit mezi dvěma způsoby: • Behaviorální popis – určuje chování entity. Toto chování je popsáno algoritmem, je zde kladen důraz na to jak je daný problém řešen. • Strukturní popis – určuje strukturu entity. Tzn. z čeho je entita složena. To jak se chová, je dáno jednak propojením jednotlivých částí a jednak vlastním chováním těcto částí. VHDL návrhář se nemusí striktně pohybovat na jedné z těchto hranic. Může podle potřeby přecházet od jedné k druhé, či psát kód hybridní, tzn. „někde uprostředÿ. Typickým příkladem 3
V současnosti se můžeme setkat se standardy VHDL87, VHDL93, VHDL2004
4
může být dekompozice návrhu na ucelené části, návrh rozhraní mezi těmito částmi, jejich behaviorální zápis. Složení celého hlavního projektu pak bude zapsáno strukturně propojením dílčích celků. Obecně platí, že strukturní popis má blíž k hardware, protože je možno systém dekomponovat až na základní stavební jednotky a ty pak přímo mapovat na zdroje v FPGA. Výhodou je optimální využití prostředků, ale zápis může být na první pohled nepřehledný a nebude z něj patrná funkčnost, resp. chování systému. Pro další podrobnější průzkum jazyka VHDL a jeho možností lze doporučit knihu [2].
3
Procesor
Tento text se zabývá návrhem a implementací komplexnějšího hardwarového zařízení – procesoru. V následující části budou uvedeny základní přístupy a koncepce k implementaci a fungování takových prvků. Procesor je zařízení, které transformuje zadaná vstupní data podle definovaného předpisu na data výstupní. Z matematického hlediska vlastně realizuje funkci mnoha vstupních proměnných, jejímž výsledkem jsou další proměnné výstupní. Tato transformace je definována programem, což je posloupnost řídících pokynů pro vykonání konkrétních činností, které je procesor schopen realizovat. Tyto pokyny se nazývají instrukcemi a jim asociované činnosti/operace jsou přesně definovány. Vstupem do procesoru chápeme data uložená v pamětech, ať jsou to již paměti externí, interní nebo paměti vyrovnávací (cache). Dále to mohou být přímé, relativně izolované vstupy, jako jsou například přerušení, vstupní datové či komunikační porty apod. Tyto prostředky mohou být jistým způsobem také do paměti mapovány. Výstup transformační funkce bývá často totožný, resp. má velkou společnou část, s množinou vstupů. Výsledky se totiž taktéž zapisují do paměti, přenášejí se přes vstupně/výstupní porty apod.
3.1
Základní koncepce
Moderní procesory, resp. mikroprocesory, mají za sebou již relativně bouřlivý vývoj, který přinesl mnohá zdokonalení, jak v oblasti výkonnosti, tak i v takových pragmatických oblastech jako je široká dostupnost, či vlastní velikost a spotřeba energie. Vývoj procesorů se v ranných počátcích rozdělil na dvě hlavní linie, které definují vnitřní uspořádání. Jedná se o koncepce RISC4 vs. CISC5 , tj. procesory s redukovanou resp. komplexní instrukční sadou. Koncepce CISC s velkým množstvím relativně složitých instrukcí se průběžně vyvinula jako reakce na požadavky pro podporu operací prováděných ve vyšších programovacích jazycích. Do instrukčního souboru byly přidávány další a další operace, které však nebyly využívány příliš často. Instrukce CISC používají mnoho adresovacích režimů. Pro jejich vykonávání se typicky používá mikroprogramový řadič, jež je řízen mikroprogramem, kde má každá instrukce definovánu sekvenci mikrooperací. Jak je uvedeno v [1] výkonnost procesoru je úměrná počtu instrukcí vykonaných za jednotku času, resp. délce doby provádění určitého programu Tc , což je čas provádění N instrukcí. Doba Tc je dána vztahem: Tc = 4 5
1 · N · CP I f
Reduced Instruction Set Computer Complex Instruction Set Computer
5
1 f
je doba jednoho taktu hodin definovaných synchronizačním kmitočtem f , N je celkový počet vykonaných instrukcí a CP I je průměrný počet taktů potřebných k vykonání jedné instrukce (Clocks Per Instruction). Konkrétní počet taktů jedné instrukce závisí na složitosti jejího mikroprogramu. Průměrný počet taktů CPI je pak dán trváním použitých instrukcí v běžícím programu. Zkoumáním aplikačních programů se ukázalo, že po 95% času výpočtu programu je použito jen 25% instrukcí z dostupné sady. Tímto zjištěním vznikly požadavky na redukci instrukčního souboru. Vykonávání složitých instrukcí bylo převedeno z paměti mikroprogramu do programu aplikačního a zůstala sada jednoduchých elementarních instrukcí, které se ukázaly jako nejčastěji používané. Tento přístup je nazýván RISC a jeho základními rysy jsou: • malý počet jednoduchých instrukcí se stejnou dobou vykonávání • ideálně je v každém taktu dokončena instrukce (tedy CP I = 1), reálně je pak CP I > 1 • instrukce mají pevnou délku a jsou jednotně kódovány • procesor je řízen pevnou logikou, neobsahuje mikroprogramový řadič • datové operace probíhají pouze nad registry • paměťové operace probíhají pouze pomocí vyhrazených instrukcí přesunů (Load/Store) • větší počet registrů Tyto rysy se prosadily i v CISC architekturách. Takové procesory se vně jeví jako CISC, mají rozmanitou a komplexní instrukční sadu, která je však interně zpracovávána v RISC jádře. Load/Store architektura RISC koncepce umožňuje efektivnější zřetězené zpracování instrukcí. Způsob zpracovávání se nemalou měrou podílí na celkové výkonnosti procesoru, na niž jsou kladeny neustále vyšší nároky.
3.2
Zřetězené zpracovávání instrukcí
Podle způsobu zpracovávání instrukcí můžeme procesory rozdělit na: • subskalární – instrukce jsou načítány a prováděny sekvenčně, tzn. doba provádění programu je dána součtem časů trvání jednotlivých instrukcí. Tyto časy se pohybují od několika jednotek do desítek taktů. Vývojově nejstarší a nejjednodušší přístup. • skalární – instrukce jsou načítány sekvenčně, ale zavedením několika výkonných jednotek je zpracování instrukcí zřetězeno. Vykonávání několika instrukcí se překrývá, či probíhá paralelně, pokud to dovolí závislosti mezi jednotlivými instrukcemi. • superskalární – instrukce jsou paralelně jak načítány, tak prováděny. Skupiny instrukcí je třeba buď vhodně nplánovat kompilátorem anebo zajistit jejich vhodné naplánování přímo v procesoru. Klasické zpracování typické pro subskalární procesory je ilustrováno na obrázku 2. Zahájení výkonávání instrukce je možné až po úplném dokončení instrukce předchozí. Skalární a superskalární procesory zavádějí pro zvýšení výkonu do své činnosti řetězení a/nebo replikaci výkonných jednotek. Zřetězené zpracování je založeno na principu rozkladu jedné instrukce na více sekvenčních kroků. Tyto kroky trvají v ideálním případě stejnou dobu a při vykonávání 6
i1 i2 i3
1 S1
2 S2
3 S3
4 S4
5
6
7
8
S1
S2
S3
S4
9
10
11
12
S1
S2
S3
S4
Obrázek 2: Klasické zpracování instrukcí ve čtyřech stupních S1 –S4 S1 S2 S3 S4
1 i1
2 i2 i1
3 i3 i2 i1
4 i4 i3 i2 i1
5 × × × i2
6 i5 i4 i3 −
7 i6 i5 i4 i3
8
9
10
i6 i5 i4
i6 i5
i6
Obrázek 3: Zřetězené zpracování instrukcí ve čtyřech stupních S1 –S4 ; tabulka vytížení stupňů každého kroku jsou použity samostatné výpočetní jednotky. Tím je dosaženo možnosti mít v jednom taktu rozpracováno více instrukcí současně. Každá rozpracovaná instrukce je v jiné fázi vykonávání. V ideálním případě vždy jedna fáze zpracování potřebuje využívat právě jednu jednotku řetězu. Činnost řetězené linky je patrná v obrázku 3 a 46 . Do řádků tabulky jsou zaznamenávány jednotlivé stupně linky (výkonné jednotky) – obrázek 3 nebo jednotlivé instrukce – obrázek 4. Ve sloupcích je zaznamenán postupující čas. Z obrázku 3 je zřejmé plnění linky načítanými instrukcemi s postupným přesunováním zpracovávání jedné instrukce mezi jednotkami S1 až S4 . V pátém taktu pak jednotka čeká na dokončení instrukce i2 („×ÿ) a zpracování následujících instrukcí je pozastaveno. Značení („−ÿ) znamená nevyužití jednotky v daném taktu. V obrázku 4 je pak stejná situace ilustrována jako využití jednotlivých stupňů linky v daném taktu s ohledem na prováděné instrukce. Ve srovnání se situací na subskalárním procesoru (obrázek 2), kde byly ve 12 taktech vykonány 3 instrukce, je na řetězených linkách skalárního procesoru proveden dvojnásobek instrukcí ve srovnatelném čase. Průměrný počet taktů na instrukci je u neřetězeného zpracování CP I = 4, u řetězené linky ¯ pak CP I = 10 6 = 1, 6. 6
Převzato z [1]
i1 i2 i3 i4 i5 i6
1 S1
2 S2 S1
3 S3 S2 S1
4 S4 S3 S2 S1
5
6
S4 × × ×
− S3 S2 S1
7
8
9
10
S4 S3 S2 S1
S4 S3 S2
S4 S3
S4
Obrázek 4: Zřetězené zpracování instrukcí ve čtyřech stupních S1 –S4 ; tabulka zpracování instrukcí
7
3.3
Linky zřetězeného zpracování
Princip linek zřetězeného zpracování je založen na rozčlenění obvodové logiky, zajišťující vykonávání instrukcí, do několika na sobě nezávislých bloků. Bloky jsou od sebe odděleny záchytnými registry. Tím je dosaženo jednak snížení kombinačního zpoždění logiky a jednak oddělení nezávislých funkčních částí od sebe. Registry jsou synchronní, tedy mění svůj obsah s hranou hodin. V ideálním případě mají kombinační sekce stejné či blízké zpoždění. Je-li t zpoždění kombinační logiky a td zpoždění oddělovacího registru, bude pro nezřetězenou linku interval mezi jednotlivými výsledky t. Zřetězená linka s k stupni dodá první výsledek až za dobu k · T , ale následujících N − 1 výsledků dodá již s periodou T , tj. periodou hodin synchronizujících oddělovací registry. T je určena kobinační sekcí s nejdelším zpožděním τ a zpožděním registru td . T ≥ τ + td Pro zjednodušení můžeme předpokládat, že všechny bloky kombinační logiky mají stejné zpoždění, tedy t k tj. celkové zpoždění kombinační logiky t rovnoměrně rozděleno mezi všechny stupně k. Definujme ukazatel zrychlení S jako poměr délky sekvenčního τ=
T1 = N · t a zřetězeného Tk = (k + N − 1) · (τ + td ) zpracování N vstupních položek. Nedochází-li k zastavování linky je Sk =
T1 N ·t = Tk (k + N − 1) · (τ + td )
Zrychlení Sk se blíží počtu stupňů k pro velký počet položek, kdy N → ∞ a pro zanedbatelné zpoždění oddělovacích registrů v porovnání se zpožděním logiky td τ . Pro malá N , tedy nízké počty položek se projeví doba naplňování a vyprazdňování linky, kdy nepracují všechny stupně linky. Definuje se tedy účinnost linky Ek jako poměr zrychlení na počet stupňů linky Ek =
Sk k
Členění provádění instrukce na fáze a označení jednotlivých fází závisí na typech operací, které jsou v procesoru dostupné, na implementaci výkonných jednotek apod. Typické přístupy k implementaci zřetězeného zpracování jsou patrné z obrázku 5.
3.4
Závislosti a konflikty
Cílem zřetězeného zpracování je dosažení průměrné hodnoty CP I = 1, tzn. co takt, to dokončená instrukce. Z praktického hlediska je tato situace nedosažitelná, protože instrukce na sobě často závisejí a před započetím vykonávání další instrukce musí být dokončena jedna či více instrukcí předchozích. To vyžaduje pozastavování linky, resp. vkládání tzv. prázdných taktů. Závislosti mohou být datové, řídící a strukturní. Ze závislostí mohou vznikat konflikty, které je potřeba následně řešit. 8
IF
ID
EX
WB
IF
ID
EX
CA
WB
IF
ID
AG
CA
EX
IF
ID
AG
E/C
WB
Tradiční procesory RISC bez jednotky FP Dřívější procesory MIPS WB
Tradiční procesory CISC Novější procesory CISC, soudobé procesory RISC
Význam zkratek: IF . . . Instruction Fetch, načtení instrukce ID . . . Instruction Decode, dekódování instrukce EX . . . Execute, provedení instrukce WB . . . Write Back, zpětný zápis výsledků CA . . . Cache Access, přístup do vyrovnávací paměti AG . . . Address Generation, generování adresy E/C . . . Execute/Cache Access, sloučení obou operací Obrázek 5: Typické fáze při zřetězeném zpracování instrukcí 3.4.1
Datové závislosti
U tří typů datových závislostí může dojít ke konfliktu. Jedná se o závislosti RAW (Read After Write), WAR (Write After Read) a WAW (Write After Write). Mějme pětistupňovou linku a následující sled instrukcí: a)
b)
i1 : i2 :
load R1, R3 add R2, R1
; R1 ⇐ MEM[R3] ; R2 ⇐ R2 + R1
IF
i1 : i2 :
add R5, R6 sub R4, R5
; R5 ⇐ R5 + R6 ; R4 ⇐ R4 - R5
IF
ID IF
EX ?ID
CA! −↓
WB EX
CA
ID IF
EX! ID?
CA EX
WB CA
WB
WB
V případě a) je instrukce i2 závislá na dokončení instrukce i1 a zapsání výsledku do registru R1. i1 by tedy ve fázi ID (zde dekódování a načítání operandů, označeno otazníkem) musela dva takty počkat, až bude zapsán nový obsah R1 ve fázi WB(i1 ). Očekávaná hodnota je však k dispozici o takt dříve na výstupu vyrovnávací paměti (fáze CA, označeno vykřičníkem), čili je možné vytvořit tzv. zkratku a přímo propojit dvě závislé jednotky. Výstup paměti cache a vstup ALU. Tato technika se nazývá předávání dat (fáze vyznačena šipkou). Pomáhá řešit, resp. mírnit konflikty, ale znamená další technické prostředky navíc (širší sběrnice, multiplexery, komparátory). V situaci b) je podobný případ, ovšem jedná se o předávání dat v rámci jedné jednotky (ALU). Z výstupu – po skončení fáze EX(i1 ) na vstup – před započetím fáze EX(i2 ). V tomto případě je předáváním dat konflikt zcela odstraněn. 3.4.2
Řídící závislosti
Tyto závislosti jsou typické pro skokové instrukce. Instrukce prováděné po podmíněném skoku jsou závislé na výsledku testu podmínky. Jsou-li prováděné skoky relativní, je nutné také vy9
počíst adresu instrukce následující. K tomuto výpočtu je vhodné využít samostatnou jednotku (sčítačku), aby adresa byla k dispozici co nejdříve. Mějme situaci: b: b + 1: t:
Jsou-li výsledek testu podmínky spolu s cílovou adresou skoku známy ve fázi ID(b) a podmínka je pozitivní, před prováděním správné instrukce dojde k načtení instrukce bezprostředně po skoku. Tento přístup je realizací pevné negativní predikce skoku. Bude-li podmínka negativní (nebude platit), skok se neprovede a bude se pokračovat ve zpracovávání instrukce z adresy b + 1. U podmíněných skoků je však situace spíše opačná, tzn. skoky se s velkou pravděpodobností provedou, čili by byla na místě predikce inverzní. To ovšem předpokládá, že adresa skoku musí být známá před načítáním instrukce z adresy b + 1, což je jistá komplikace. U moderních procesorů se používá sofistikovanějších skokových prediktorů s vysokou účinností, které minimalizují špatnou predikci a tím pokutové takty při vykonávání skokových instrukcí. Podmínka skoku je často založena na výsledku operace předchozí instrukce ať již ve formě testování příznaků či testování obsahu naposled zapisovaného registru. Pro minimalizaci čekacích taktů je zde možné také využít techniky předávání dat uvedené v předchozí sekci. 3.4.3
Strukturní závislosti
Jedná se o závislost několika instrukcí na prostředcích stejné jednotky ve stejném okamžiku. Typicky vznikají u jednotek s více exekučními stavy, kdy instrukce následují bezprostředně či s malým odstupem za sebou. Tyto závislosti nebudou v kontextu této práce dále rozebírány. Podrobný rozbor problematiky je možné najít v [1].
4
Návrh procesoru pro FPGA
V této části bude nastíněno, jak je možné postupovat při návrhu jednoduchého procesoru pro FPGA. Níže uvedený text je inspirován seriálem článků Kena Chapmana ze společnosti Xilinx (viz. [3], části 1–5). Ken Chapman implementoval programovatelný stavový automat (PSM) – jak jej sám nazval, vysoce optimalizovaný pro architektury Xilinx FPGA/CPLD. Implementace tohoto PSM není nejvhodnější pro studijní účely, protože z důvodů vysoké optimalizace je provedena strukturně. Jsou zde instanciovány přímo konkrétní zdroje specifických FPGA a velice promyšleně zakomponovány do výsledného celku. Zde bude zpočátku uveden obecnější popis, implementace jednotlivých částí bude provedna převážně na behaviorální úrovni a teprve procesor jako celek bude sestaven strukturně z vyrobených dílů. Výsledkem nebude sice perfektně optimalizovaný hardwarový návrh, ale pro počátek to nebude na škodu, protože bude zřejmé, co jednotlivé části návrhu mají za úkol a jakým způsobem toho docílí. Před úvahami nad dekompozicí procesoru na jednotlivé bloky, je vhodné vybrat hlavní směry, kterými se bude návrh ubírat. Jednak v oblasti rozdělení paměti procesoru a jednak v oblasti uspořádání ústředního výpočetního systému procesoru.
4.1
Von Neumannova koncepce
Při tomto uspořádání je u procesoru k dispozici jedna hlavní paměť, která obsahuje jak program (instrukce), tak data programu (proměnné, vstupní a výstupní data, apod.). Prolínání 10
těchto dvou druhů dat v jedné paměti může být – nahlíženo z různých úhlů – bráno jako výhoda i jako nevýhoda. Principiálně je totiž možné za běhu programu provádět přímé změny v kódu programu7 . Pokud jsou tyto změny záměrné a cílené, může to být jedna z technik kterou lze obohatit možnosti programování daného procesoru. V horším případě může k těmto změnám docházet náhodou či omylem, kdy programátor udělá při psaní kódu chybu. Takové chyby jsou většinou špatně odhalitelné, protože program se při ladění (pokud je toto možné) nechová očekáváným způsobem. Z jiného (hardwarového) pohledu je třeba určit, která část paměti má být interpretována jako data, která jako program, resp. definovat místo, odkud bude startován program a spoléhat na jeho korektní činnost. Tzn. předpoládat, že není možné, aby se řízení dostalo do části paměti, kde se nacházejí data. Pokud by tato situace nastala, v lepším případě by došlo k zastavení procesoru (neplatný instrukční kód), v horším případě by pak chování procesoru bylo náhodné – podle toho, do které instrukce by byla datová oblast interpretována. Další možností je pak označení paměťových oblastí nějakým příznakem, který udává spustitelnost uloženého bloku a tedy jej lze identifikovat jako kód programu.
4.2
Harvardská koncepce
Při tomto uspořádání jsou u procesoru k dispozici dvě paměti (resp. dvě oddělené paměťové části). Paměť programu je v podstatě pamětí ROM (tzn. je určena pouze pro čtení) a paměť dat je pak dostupná jak pro čtení tak zápis dat. Programová paměť samozřejmě není (resp. nemusí nutně být) fyzicky realizována technologií ROM. Označení „ jen pro čteníÿ je nutné chápat jako „ jen pro čtení, za běhu programuÿ. Samozřejmě možnost jednoduchého zavedení libovolného programu do paměti je – mírně řečeno – výhodou. Při použití Harvardské koncepce tedy odpadají možnosti modifikace programové paměti při běhu programu a je tím odstraněn jeden potencionální zdroj chyb při programování. Také striktní oddělení programu a dat je chápáno jako výhoda, protože jsou přesně definovány a navzájem odděleny datové cesty, kterými jsou vedena instrukční či datová slova. Toto členění napomáhá jednoduššímu návrhu cílového hardware. Z výše uvedeného tedy plyne, že v kontextu této práce bude preferována Harvardská koncepce organizace paměti. Druhou nastíněnou možností, kde lze volit směr dalšího návrhu je organizace centrálního výpočetního systému procesoru. Zjednodušeně řečeno, je nutné definovat zdroje dat a cíle výsledků pro výpočetní (aritmeticko-logickou) jednotku. Pro tento účel jsou k dispozici tři základní možnosti.
4.3
Registrová architektura
K dispozici je konečný počet registrů, ve kterých jsou uloženy zdrojové operandy operace. Výsledek bude uložen opět do jednoho z registrů. Každá instrukce může použít libovolný/é registr(y) ke své činnosti. Registrová architektura vede k jednoduššímu programování, protože dostatečný počet registrů umožní ukládat proměnné výpočtu lokálně. Šetří se tím také čas, který je potřeba pro přístup do paměti. Nevýhodou registrové architektury je nutnost explicitního uvedení identifikace registru/ů v instrukčním kódu. Tím narůstá šířka instrukčního slova. Např. při sadě o 16ti registrech je k zakódování identifikátoru potřeba čtyř bitů. 7
„Pamětnícíÿ v PC sféře jistě vzpomenou na časy tzv. polymorfních virů, které měnily svá těla za běhu a tím maskovaly svůj výskyt v paměti.
11
Bude-li operace vyžadovat dva zdrojové registry a jeden cílový, pak jen pro adresaci operandů je potřeba 12 bitů z instrukčního kódu. Příklad: R = (A + B) ∗ (C − D). Vyhodnocení výrazu je jednoduché a přímé. Každý z operandů A až D je předpokládán v registrech R0 až R3 . Pak jsou s výhodou využity dva další registry R4 a R5 pro dílčí součty a poslední registr R6 jako výsledek. Zápis by tedy mohl vypadat například takto: R4 <= R0 + R1 ; (A + B) R5 <= R2 − R3 ; (C − D) R6 <= R4 ∗ R5 ;
4.4
Akumulátorová architektura
Je podobná předchozí registrové v tom smyslu, že opět je k dispozici sada registrů. Počet registrů bývá nižší než u předchozí architektury. Řešení s akumulátorem se snaží zmírnit potřebu velké části instrukčního slova k adresování operandů. Jeden ze zdrojových operandů je vždy určen implicitně a je umístěn v tzv. akumulátoru, což je další registr se speciálním významem a zapojením. Akumulátor slouží zároveň jako registr výsledku. Z toho plyne, že v instrukčním kódu bude potřeba adresovat pouze jeden zdrojový registr. Nevýhodou této architektury je složitější přístup k programování. Je třeba psát program tak, aby byl před každou operací jeden zdrojový operand v akumulátoru a zároveň počítat s akumulátorem jako výsledkem. Při nevhodném programovacím stylu může docházet k častým a zbytečným přenosům mezi registry a akumulátorem, resp. mezi pamětí a akumulátorem, dovoluje-li to architektura procesoru. To má samozřejmě negativní vliv na rychlost řešení prováděného algoritmu. Příklad: R = (A + B) ∗ (C − D). Pro jednoduchost je opět předpokládána dostupnost operandů A až D v registrech R0 až R3 . Vzhledem k nutnosti umístit jeden operand v akumulátoru a výsledek převzít z akumulátoru je zápis poněkud složitější. Jedna z možností by mohla vypadat takto: ACC <= R2 ; (C) ACC <= ACC − R3 ; (C − D) R4 <= ACC; ACC <= R0 ; (A) ACC <= ACC + R1 ; (A + B) ACC <= ACC ∗ R4 ;
4.5
Zásobníková architektura
Co do využití hardwarových zdrojů nejefektivnější výpočetní architektura. Pro operandy a výsledek využívá zásobníku. Dva záznamy na vrcholu zásobníku jsou výpočetní jednotkou vyjmuty (POP), provedena operace a výsledek je uložen do zásobníku (PUSH). Tento způsob je realizací tzv. postfixové notace (někdy též nazývané Polskou notací), kdy operační znaménko následuje až za uvedením zdrojových operandů. Zápis výrazů v postfixové notaci není 12
v běžném povědomí považován za příliš přirozený a pro programátora může být zátěží při psaní programu. Příklad: R = (A + B) ∗ (C − D). Zde žádné registry nejsou k dispozici. Předpokladem jsou operace PUSH a POP, které mohou přistupovat k zásobníku a paměti. Na adresách A až D v paměti jsou uloženy operandy A až D a na adresu R se má uložit výsledek. Pro operace je použito označení ADD, SUB, MUL s intuitivním významem. Výraz zapsaný v postfixové notaci: R = AB + CD − ∗ a zápis jeho vyhodnocení: PUSH(A); PUSH(B); ADD; PUSH(C); PUSH(D); SUB; MUL; POP(R); Ač se zdá, že akumulátorová architektura je přijatelným kompromisem, co do šířky potřebné k adresaci operandů či složitosti a srozumitelnosti programu, zde bude preferována architektura registrová, vedoucí ke komfortnějšímu a intuitivnějšímu zápisu programu uživatelem procesoru. Tím je programátor, který bude psát užitečný kód k vyřešení zadané úlohy.
4.6
Datová šířka
Nelehkým úkolem při návrhu je stanovit, s jakou šířkou dat (obecně) se bude uvnitř procesoru operovat. Asi prvotní ukazatel, který naznačuje cestu, je dispozice s hardwarovými prostředky. Návrhář může disponovat externími pamětmi nebo interními paměťovými prvky v FPGA. Externí součásti se mohou jevit jako nevýhodné, protože cesty signálů při propojování s FPGA jsou v porovnání s integrovanými komponentami poměrně dlouhé, což zhoršuje časovací možnosti návrhu. Navíc nemusí externí prvky dosahovat potřebné/požadované rychlosti, kterou by použité připojení a vnitřní uspořádání systému dovolily. Pokud je to možné, pak je vhodnější využít možností nabízených na čipu FPGA. Samozřejmě při vysokých nárocích na úložný prostor šance využití integrovaných řešení klesá (při zachování konstantních nákladů). Také není-li v čipu žádná vhodná paměťová komponenta, nezbývá než užít součásti externí. Dlužno podotknout, že na současných čipech Xilinx FPGA se nalézá dostatečná paměťová kapacita, a tedy připojováním externích pamětí se tento text zabývat nebude. V „běžném digitálním světěÿ se šíře datovýh sběrnic pamětí pohybují v mocninách dvou. Je principiálně možné, navrhnout a implementovat procesor vnitřně třináctibitový, ale je to spíše případ kuriózní, kdy (pokud opravdu nejsou k dispozici paměti o šíři datového slova 13 bitů) bude docházet k jistému nevyužití hardwarových prostředků. K dalším rozvahám budou tedy připadat v úvahu „přirozenáÿ čísla 4, 8, 16, 32 apod. Při volbě šíře instrukčního slova existují principiálně dvě cesty. Buďto budou všechny instrukce zabírat stejný paměťový prostor, tedy šířka instrukčního slova bude pevná, nebo podle potřeby budou mít různé instrukce různou šíři. Každá instrukce pak zabere v paměti programu jiný prostor. Jaké jsou výhody či nevýhody uvedených možností? Pevná šířka znamená jednodušší načítání a dekódování instrukce. Zpracování je uniformní a pro každou instrukci zabere stejný časový úsek. Naproti tomu variabilní šířka instrukčního slova poskytuje optimální využití paměti. Jednoduché instrukce, které vystačí s několika málo bity kódu mohou být řešeny 13
úsporněji. Ovšem daní za tuto úsporu je fakt, že v každém instrukčním slově musí být jistým způsobem zakódován počet dalších paměťových pozic nutných k donačtení celé instrukce. Postupné načítání zabere různou dobu a délka vykonávání instrukce je závislá na délce jejího operačního kódu. Tento fakt komplikuje následné optimalizace zpracovávání instrukcí, které je možné rozčlenit na fáze a zpracovávat zřetězeně. Řetězení instrukčního zpracování může mnohem zefektivnit činnost procesoru. V ideálním případě by totiž po zaplnění řetězící linky (pipeline) byla v každém následujícím taktu dokončena jedna instrukce. Podrobněji bylo o této problematice pojednáno v části 3.2. Vzhledem k uvedeným výhodám a jednodušší implementaci tedy bude dále jako výhodnější uvažováno řešení založené na pevné šířce instrukčního slova. V tom případě je nutné do této šířky zanést operační kód instrukce a případné identifikátory operandů a výsledku. Jsou-li potřebné. Pro instrukce skoku je nutné do instrukčního kódu vhodně zavést i možnost udání adresy odkazující do paměti programu, resp. adresy okolí aktuální hodnoty programového čítače, má-li se jednat o skoky relativní. Vycházíme z předpokladu, že instrukční slovo ponese všechny informace potřebné k vykonání příslušné operace v procesoru atomicky. Tím se rozumí z vnějšího pohledu, tzn. že k smysluplnému vykonání operace definované instrukcí I, není nutné předchozí provádění jiné instrukce J. Nechť existuje fiktivní procesor, který má pevné, 16 bitů široké instrukční slovo a je schopen obsluhovat datovou paměť 64kx8 bitů. Tzn. adresní sběrnice paměti má šířku 16 bitů, datová sběrnice pak 8 bitů. Procesor pro nastavení adresy přístupu k paměti využívá dvou osmibitových registrů. Jeden registr P určuje číslo stránky v paměti, druhý registr O pak offset buňky v rámci stránky. Nechť existují instrukce LOAD a STORE, které do/ze cílového/zdrojového registru R nahrají data z/do paměti na adrese dané složením stránkového a offsetového registru P:O. Instrukce LOAD a STORE bez předchozí inicializace těchto registrů ztrácejí logický smysl. Proto ani z vnějšího pohledu není operace poskytovaná těmito instrukcemi atomická. Dalšími instrukcemi je potřeba vhodně inicializovat registry adresy P a O. Uvedený příklad adresování je ukázkou kompromisu kdy při pevné šířce instrukčního slova je možné obsluhovat adresní prostor přesahující možnosti této šíře. Ovšem na úkor rychlosti resp. počtu instrukcí programu, kdy je třeba použít více instrukcí pro jednu vysokoúrovňovou operaci. Za tuto cenu zůstává zachována jednotná délka zpracování instrukce a šířka instrukčního kódu, což umožňuje vhodné hardwarové optimalizace. Nyní je čtenář zhruba obeznámen s možnostmi, které je dobré vzít v potaz při volbě šíře instrukčních slov. Další již poněkud jednodušší rozvažování se týká šíře slova v datové paměti. Pokud mají mít operace vykonávané „paměťovýmiÿ instrukcemi atomické, je třeba aby celou adresu do paměti dat bylo možno obsáhnout v instrukčním slově. Techniky naznačené v předchozím příkladě nebudou aplikovány. Konečný rozbor a rozvržení instrukcí bude probráno v následující části.
4.7
Kódování instrukcí
Zde budou naznačeny úvahy o tom které operace by měl navrhovaný procesor být schopen provádět a jakými typy instrukcí bude nutné jej vybavit. • Elementárními operacemi, které by jistě neměly chybět, jsou operace logické. Tzn. aplikace základních funkcí AND, OR, XOR na bitové úrovni. Mohlo by se zdát, že chybí 14
funkce NOT realizující negaci všech bitů operandu. Pro tuto operaci poslouží funkce XOR8 . • Přenášení dat mezi jednotlivými registry a vkládání konstant do registrů bude zajištěno operacemi přesunu. • Pro základní početní operace by měly být definovány instrukce realizující operace součtu a rozdílu. Aby bylo možné jednodušeji počítat s většími čísly, než které bude možno obsáhnout v registrech mohou aritmetické operace být rozšířeny o další funkce součtu a rozdílu, které budou brát v úvahu přenos z nižšího řádu. • Další skupinou z oblasti logických operací by mohly být instrukce realizující bitové posuny a rotace, se kterými je na elementární úrovni možno omezeně provádět celočíselné násobení a dělení mocninami dvou. • K řízení toku programu je potřeba definovat operace skoků. • Z předpokladu použití datové paměti vychází i potřeba instrukcí realizujících operace nad touto pamětí. Typicky pro načtení resp. uložení datového slova z paměti do registru resp. z registru do paměti. • Vzhledem možným požadavkům na interakci s okolím by bylo vhodné, aby procesor také obsahoval instrukce pro vstupně/výstupní operace. Tzn. nastavení a přečtení hodnot z pinů čipu či komunikace s jinými komponentami v čipu. Podle výše uvedených požadavků je možné instrukce rozdělit dle typu a počtu operandů do několika skupin: • Instrukce se dvěma zdrojovými operandy a cílovým operandem. Zde patří instrukce aritmetické, první skupina logických. • Instrukce s jedním zdrojovým operandem a jedním cílovým operandem. Jsou to instrukce přesunu, operace s pamětí, vstupně/výstupní operace. • Instrukce s jedním operandem, který je zdrojovým a zároveň cílovým. Do této skupiny náleží instrukce logických posunů a rotací. • Instrukce skoku, které obsahují jeden zdrojový operand představující adresu či offset do paměti programu. U těch instrukcí, kde by to mělo smysl, by bylo vhodné, aby jako jeden ze zdrojových operandů mohl být uveden opearand přímý, tj. konstanta. To se týká skupiny aritmetických, první skupiny logických, přesunových, paměťových a vstupně výstupních. Nyní jsou stanoveny požadavky na instrukce. Je možné začít s úvahami nad vlastním kódováním instrukcí. Jak již bylo uvedeno, každá instrukce ponese veškeré informace potřebné k provedení přisouzené operace ve svém instrukčním kódu. Nejvyšší nároky jsou kladeny na první skupinu. Ta vyžaduje zapouzdření tří adres pro jednotlivé operandy. V části 4.6 bylo řečeno jakým způsobem a podle jakých kritérií je vhodné se orientovat při volbě šířky a velikosti jednotlivých částí navrhovaného systému. Nechť tedy datová šířka 8
Funkce XOR (se dvěma vstupy) dovoluje realizovat tzv. řízený invertor. Jeden vstup je použit pro proměnnou, druhým je řízen typ realizované funkce. Tj. buď identita nebo inverze. Okamžitě zřejmé je toto chování z pravdivostní tabulky.
kód instrukce/skupiny instrukcí identifikace registru horní/spodní bity konstanty bity pro další použití
Obrázek 6: Kódování identifikátorů operandů pro skupinu instr R, [KR] je stanovena na 8 bitů. Tedy i registry budou široké 8 bitů. Počet registrů nechť je v rámci možností zvolen co nejvyšší. Znamená to přímé programování a prostor pro uložení více lokálních proměnných. Zkusme zvolit čtyřbitový identifikátor registru. Z toho plyne, že bude k dispozici 16 registrů k všeobecnému použití. Vyžaduje-li některá z instrukcí dva zdrojové a jeden cílový operand a má-li být možno jeden ze zdrojových operandů nahradit přímou konstantou, pak nejhorší možný (= nejdelší) případ instrukčního kódu zabere dohromady na určení operandů 4 + 4 + 8 = 16 bitů. Pro ostatní skupiny instrukcí, popř. s vypuštěním instrukcí s přímými operandy, by mohlo být pro instrukční kód dostatečných 16 bitů. Pokud by byla potřebná širší instrukce, pak nejbližší vyšší hranice by byla 24, pravděpodobněji však 32 bitů, což se zdá již zbytečné s ohledem na ostatní skupiny. Variabilní šířka instrukčního slova byla zamítnuta. Pokusme se tedy snížit šířku instrukce na 16 bitů. Toho lze dosáhnout podobně, jako eliminuje akumulátorová architektura nevýhody registrové. Prohlášením jednoho z operandů instrukce zároveň zdrojovým i cílovým. V instrukčním kódu se tak tímto rozumným kompromisem ušetří 4 bity a bude možno použít přímých operandů. Tedy skupina instrukcí s operandy R, K resp. R, R využije pro adresaci operandů bity z instrukčního slova jak je znázorněno na obrázku 6. R označuje registr, K pak konstantu. Pro instrukce s jedním operandem, resp. pro skokové instrukce je situace jednodušší. Jedná se o obdobné rozvržení s tím, že některé bity kódu nebudou využity. Resp. jsou volné pro použití k jiným účelům. Pro jednoduchost nechť je stanoveno, že velikost programové a datové paměti bude shodná. Pro adresu do paměti nechť je k dispozici oněch 8 bitů, které jsou u jiných skupin instrukcí použity pro kódování přímého operandu – konstanty. Z toho plyne maximální velikost programu a dat 256 záznamů. Rozvržení adresních částí instrukčního kódu je patrné na obrázku 7. Na kódování operace jsou nyní k dispozici minimálně čtyři bity. U některých skupin se pak vyskytují volné bity, které s výhodou budou využity později při definici jednotlivých částí procesoru.
4.8
Komponenty procesoru
Na úvod shrnutí dosavadní návrhů a předpokladů o vznikjícím procesoru. Jedná se o osmibitový procesor s 16ti všeobecnými registry s oddělenými pamětmi programu a dat. Datová paměť má maximální kapacitu 256 bajtů. Programová pak může uchovávat 256 instrukcí. 16
Instrukce s adresou A: CCCC VVVV AAAA
AAAA
Instrukce s operandem R: CCCC RRRR VVVV
VVVV
Legenda: CCCC RRRR AAAA VVVV
... ... ... ...
kód instrukce/skupiny instrukcí identifikace registru horní/spodní bity adresy (konstanty) bity pro další použití
Obrázek 7: Kódování identifikátorů operandů pro skupiny instr A a instr R
REG D Q
+1
instr_addr C inc
clk
Obrázek 8: Základní princip programového čítače. Aktuální hodnota registru je inkrementována a uložena zpět. Instrukční slovo je široké 16 bitů a kromě operačního kódu obsahuje identifikaci zdrojových a cílových operandů či adres, kdy první zdrojový operand je zároveň i cílovým. 4.8.1
Programový čítač
Pro správnou činnost procesoru musí být zřejmé, ze které adresy v programové paměti se bude načítat instrukce. K tomu je zapotřebí mechanismus, obecně známý jako programový čítač (PC). Ten dodává na výstupu aktuální adresu instrukce a po jejím načtení z paměti programu je obsah PC zvýšen o jedničku. Realizovat PC je možno více způsoby. Zvolíme kombinaci registru pro uchování adresy a přičítačky „+1ÿ (obrázek 8). Stejně vhodným řešením by mohl být čítač vpřed. Logickým počátkem programu je adresa 0. Na tu je programový čítač po resetu inicializován. V programovém modelu je však požadováno, aby bylo možno provádět skoky, tzn. že musí být zajištěna možnost do PC nastavit libovolnou adresu v rámci paměti programu. Toho lze docílit rozšířením jednoduchého čítače o možnost paralelního vstupu nové adresy z programu. To lze zajistit např. předřazením multiplexeru, který bude vybírat buď starou hodnotu z registru PC nebo adresu z programu. Dlužno podotknout, že při vstupu adresy z programu (ze skokové instrukce) není žádoucí, aby byla adresa inkrementována. Je potřeba zajistit, aby signál inc byl vhodně řízen.
17
D clk
Q
Tclk
C
CE
0 prog_addr
+1
D
instr_addr
1
prog
Q
C clk
inc
Obrázek 9: Rozšíření programového čítače o vstup adresy z programu. Realizace skoku. Přídavný T-clock obvod pro rozčlenění instrukce na stavy. Dekódování podmínky skoku. Má-li být možné program efektivně větvit, jsou potřebné také skoky podmíněné. Zde pak záleží na hodnotě nějaké podmínky (příznaku), která určí, zda se skok provede či nikoli. K vyhodnocení podmínky a následnému určení nové adresy programu je potřebný jeden hodinový takt navíc. Tzn. že skokové instrukce vyžadují delší čas provádění než instrukce ostatní. Aby byla zachována konstantní doba vykonávání instrukce, zavede se tzv. T-clock. Tzn. jedna instrukce bude trvat dva hodinové cykly resp. jeden T-cyklus. Generátor T-clock se dá jednoduše realizovat klopným obvodem typu T (toggle). V podstatě se jedná o vydělení hlavního hodinového signálu dvěma. Programový čítač pak bude měnit stav každý T-cyklus. Rozšíření programového čítače o skoky a klopný obvod pro výrobu T-fází je na obrázku 9. Z vyšších programovacích jazyků je programátor zvyklý běžně používat různé funkce, procedury podprogramy apod. Použití podprogramů šetří pamětí, protože není nutné opakované konstrukce ukládat za sebe do paměti, ale použije se jeden blok. Nechť jsou tedy v navrhované instrukční sadě definovány také instrukce pro skok do a návrat z podprogramu. V podstatě jde o další instrukce skoku, pro které je již připraven programový čítač. Menší problém je v tom, že při volání podprogramu je třeba uložit současnou adresu, která je při návratu obnovena. Má-li být možno navíc volat podprogram i z podprogramu, je nutné pro ukládání návratových adres zásobník. Maximální počet prvků v tomto zásobníku definuje maximální hloubku zanoření či rekurze v podprogramech. Přetečení (požadavek PUSH nad plným zásobníkem) či podtečení (požadavek POP nad prázdným zásobníkem) je z programovacího hlediska kritickou chybou a je otázkou, zda dále pokračovat ve vykonávání kódu programu. Nechť je tedy možno vyjít z předpokladu, že existuje zásobník, který je řízen signály PUSH a POP. Vkládána je do něj aktuální adresa z programového čítače. Hodnota získaná ze zásobníku operací POP je novou adresou určenou ke vložení do programového čítače. Jelikož je potřeba se při návratu odkázat na místo za voláním, je tato adresa standardním způsobem inkrementována. Programový čítač doplněný o další multiplexer pro podporu podprogramů je uveden na obrázku 10. 18
Tclk
jump_addr ret_addr
CE
0 0
prog_addr
+1
D
instr_addr
1
C
1 inc
Q
prog
inc
clk
Obrázek 10: Finální verze programového čítače s podporou skoků, volání podprogramů a návratů z podprogramů. instrukce neskoková JMP CAL RET
inc 1 0 0 1
prog 0 1 1 1
pop 0 0 0 1
push 0 0 1 0
Tabulka 1: Hodnoty řídících signálů pro programový čítač a zásobník návratových adres (call stack) při vykonávání skokových instrukcí. Při rozboru skokových instrukcí z úvah vyplynulo, že je nutné zavést několik dalších signálů k ovládání programového čítače. Instrukce přímého skoku nechť je označena JMP jako jump, volání podprogramu jako CAL od call a návrat z podprogramu jako RET, ze slova return. Pro každou instrukci nechť je zavedena maximálně třípísmenná mnemotechnická značka. Řízení signálů pro tyto instrukce je zřejmé z tabulky 1. Jak bylo uvedeno dříve, v instrukčním kódu jsou k dispozici volné bity pro další použití. Toho lze s výhodou využít ke kódování některých řídících signálů. Dalšími informacemi, které by bylo výhodné zakomponovat do instrukčního kódu je příznak podmíněnosti skoku a identifikace podmínky, při jejímž splnění bude skok proveden. Nechť lze skoky podmiňovat na základě příznaků nastavených při výpočtech v aritmeticko-logické jednotce (ALU). Tyto příznaky jsou Z (zero) – výsledek operace je nulový a C (carry) – při operaci došlo k přenosu do dalšího řádu. Výsledný instrukční kód pro skokové operace může vypadat jak je uvedeno v obrázku 11. Ostatní informace/řízení signálů, které se nevešly do instrukčního kódu bude muset obstarat/zajistit instrukční dekodér. Podrobnosti viz. část 4.8.4. 4.8.2
Zásobník návratových adres
V části 4.8.1 byl odkaz na zásobník, který je zodpovědný za uchovávání návratových adres. Ty jsou využívány instrukcí RET, která vrací řízení programu na místo, odkud se volal podprogram, aniž by bylo nutné explicitně udávat bod, kam se má skočit. To je pro programátora
19
ttt
u
n
Legenda: ttt u n f p q aaaaaaaa
f
... ... ... ... ... ... ...
p
q
aaaaaaaa
kód definující instrukce skoků nepodmíněnost, 1 : nepodm., 0 : podm. neg. podmínky, 1 : negativní, 0 : pozitivní příznak pro podmínku skoku, 0 : Zero, 1 : Carry; provést pop na zásobníku návratových adres provést push na zásobníku návratových adres adresa do paměti programu
Obrázek 11: Obsah instrukčního kódu skokových operací.
instr_addr push Tclk pop
CNT UP top
DN Q C
MEM ret_addr Din Do WE Addr C
clk
Obrázek 12: Zásobník návratových adres pohodlné při psaní čitelného a jednoznačného programu. Za vkládání instrukcí do zásobníku je zodpovědná instrukce CAL. Při jejím provádění instrukční dekodér rozpozná požadavek na operaci PUSH a aktivuje příslušný signál zásobníku. Zásobník může být implementován paměťovým polem k jehož adresním vodičům je připojen čítač vpřed/vzad. Směr změny čítače určuje kód operace (PUSH – vpřed, POP – vzad). Vstupní brána paměťového pole je trvale připojena na výstup registru programového čítače. Výstupní data paměťového pole pak reprezentují prvek z vrcholu zásobníku, tj. poslední uloženou návratovou adresu. Možné schématické znázornění je vidět na obrázku 12. Jako paměťové pole lze využít distribuovanou paměť na čipu (více viz. části 4.8.5 a 4.8.6). Počet položek v paměti – maximální hloubku zanoření – je třeba volit s ohledem na možnosti čipu a zamýšleného použití procesoru. To záleží také na typu programů, které budou zpracovávány. Nechť maximální počet záznamů v zásobníku 32, s možností případného rozšíření. Spouštěním úloh v simulačním modelu pak je možné ověřit, zda toto množství bude dostatečné či nikoli. 4.8.3
Aritmeticko-logická jednotka
Početní operace jsou základem mnohých úloh, proto bude dále podrobněji probrán návrh aritmeticko-logické jednotky, která je ústředním výpočetním prvkem procesoru. Podle požadavků na instrukce, definovaných na počátku oddílu 4.7, se dají instrukce rozdělit do čtyř 20
základních skupin dle funkce a přiřadit operacím konkrétní zkratky: • aritmetické (A) součet: bez přenosu ADD s přenosem ADC
rozdíl: bez přenosu s přenosem
SUB SBC
• logické – se dvěma operandy (L2) přesun MOV součin AND součet OR ex. součet XOR • logické – s jedním operandem (L1) posun vlevo/vpravo: nasouvání 0 SL0 SR0 nasouvání 1 SL1 SR1 nasouvání carry SLA SRA nasouvání LSB/MSB SLX SRX rotace vlevo/vpravo ROL ROR • vzdálený přenos (I/O) paměťové: načti registr z paměti ulož registr do paměti
LDR STR
vstupně/výstupní: načti registr z portu zapiš registr na port
INP OUT
Podle počtu a typu operandů je možné dělení na instrukční skupiny: • instr R, K (A, L2, I/O) • instr R, R (A, L2, I/O) • instr R (L1) Rozdělení do skupin usnadní následné kódování instrukcí tak, aby bylo využito co nejvíce volných bitů k řízení potřebných signálů přímo z instrukčního kódu bez účasti (resp. s minimální účastí) instrukčního dekodéru. K uskutečnění aritmetických operací je nutná na první pohled jedna sčítačka a jedna odčítačka. Odčítačka je však totožná se sčítačkou, použije-li se k zobrazení záporných čísel tzv. dvojkový doplněk. Pak je možné pro tyto aritmetické operace použít identickou část čipu. Dvojkový doplněk se realizuje negací operandu (jedničkovým doplňkem) a přičtením jedničky. Z toho ovšem plyne, že je nutné použít další sčítačku na realizaci doplňku. Vyjdeme-li však z definice úplné sčítačky nabízí se zajímavé řešení. Úplná sčítačka sice generuje součet dvou vstupních operandů (A, B), ale k tomuto součtu je ještě přičten bit přenosu z předchozího řádu (carryin ) navíc je generován případný přenos do dalšího řádu (carryout ). Tedy: (S, carryout ) = A + B + carryin Viz. obrázek 13. Rozdíl čísel A − B se realizuje následujícím způsobem: Provede se bitová negace čísla B, vznikne B. Následně se provede součet A + B + carryin 21
A
carry_in Cin A S
B
B
S
Cout
carry_out
Obrázek 13: Úplná sčítačka
add/sub carry_in Cin A
A B
S
0
B
1
S
Cout
carry_out
Obrázek 14: Rozšíření sčítačky o dvojkový doplněk – odčítání
22
instrukce ADD ADC SUB SBC
add/sub 0 0 1 1
přenos použit 0 1 0 1
carryin 0 C 1 ¯ C
Tabulka 2: Řízení hodnoty vstupu carryin dle typu operace určeného dvojicí (add/sub, „přenos použitÿ). instrukce MOV AND OR XOR
kód operace 00 01 10 11
Tabulka 3: Přiřazení kódu logickým operacím kde carryin := 1 reprezentuje potřebné „dokončení realizace doplňku čísla Bÿ. Přídavná logika pro sčítačku by pak mohla vypadat jako na obrázku 14. Jaké použít řešení v případě, že se bude provádět instrukce SBC, která při odečítání pracuje i se vstupním přenosem z předchozího řádu? Vyjdeme-li z předpokladu, že rozdíl byl definován jako ¯ +1 A − B := A + B pak rozdíl, s respektováním vstupního přenosu o hodnotách 0 a 1, bude vypadat takto: ¯ + 1) − 1 = A + B ¯ +0 A − B − 1 = (A + B ¯ + 1) − 0 = A + B ¯ +1 A − B − 0 = (A + B Třetí člen v levých stranách výrazů je přenos vstupující do rozdílu z předchozího řádu. Je-li roven 1, pak carryin u výkonné scítačky (poslední člen v pravých stranách) bude nastaven na 0 a pro hodnotu přenosu 0 bude pak carryin = 1. Tedy platí následující: ¯ + C¯ A − B − C := A + B Z předchozích úvah vyplývá, že pro všechny čtyři aritmetické operace ADD, ADC, SUB a SBC je možné využít jednu úplnou sčítačku s malým množstvím přídavné logiky, která řídí vstup carryin . Pro ucelenou představu jsou zdroje řízení a hodnoty vstupu carryin uvedeny v tabulce 2. Kompletní sčítačka/odčítačka je pak na obrázku 15. Logické operace jsou již jednodušší. Z praktických dovodů je do této skupiny zařazena i operace přesunu (MOV). Jedná se vlastně o funkci identity, tzn. výsledek je totožný s argumentem. Definujme kód, dle kterého ALU rozpozná o kterou instrukci se jedná. Viz. tabulka 3. Realizace logických operací je snadná, neb se jedná o elementární funkce podporované přímo v hardware. Budou se tedy základními hradly provádět paralelně a dle typu operace bude multiplexerem vybrán výsledek, viz. obrázek 16.
23
include_carry (ADC, SBC) add/sub
0
C
1
carry_in
A
A
B
Cin S
0
B
1
Cout
carry_out
Obrázek 15: Finální sčítačka/odčítačka
src2 src1
MOV 00 logic_result AND 01 10 OR 11 XOR select
Obrázek 16: Realizace logických operací
24
S
instrukce LDR STR INP OUT
typ operace ls/io 0 0 1 1
čtení/zápis rd/wr 0 1 0 1
Tabulka 4: Přiřazení kódu operacím vzdálených přenosů skupina L2 A I/O druhý typ: instr R, R
kód 00 01 10 11
Tabulka 5: Přiřazení kódu skupinám instrukcí typu instr R, K Další probíranou skupinou budou instrukce vzdálených přenosů. Přeskočené instrukce logických posunů a rotací budou probrány později a to z toho důvodu, že přenosové instrukce náleží do stejných dvou skupin (děleno dle typu operandů) jako operace aritmetické (A) a již probrané logické (L2). Skupina je pojmenována „vzdálené přenosyÿ, protože má za úkol transferovat data z/do paměti dat, resp. z portu či na port. Pojmem port rozumíme jeden či více signálů, které mohou být vyvedeny i mimo čip a je tudíž možné jejich prostřednictvím zajistit komunikaci s okolním světem. Aby mohla být tato komunikace snadněji rozšířena, bude zavedena ještě identifikace či adresa portu. S ohledem na již definované vlastnosti systému si stanovme, že adresa portu může být maximálně 8 bitů široká, stejně tak port samotný bude odpovídat šířce dat v procesoru, tedy bude osmibitový. K dispozici je 256 adres portů. Pro externí zařízení tedy je možné poskytnout tři až osmibitové sběrnice – adresní (výstup z čipu), vstupní data na port a výstupní data na port. Zda a kolik signálů bude vyvedeno záleží na konkrétní aplikaci. Je také možné vstup a výstup na port sloučit do jedné třístavové sběrnice. Ušetří se alokované piny na pouzdře čipu. Pro vzdálené přenosy je potřeba do instrukčního kódu zadefinovat následující informace: • paměťová nebo portová operace – rozlišuje přístupy na paměťové a portové, což znamená přepínání adresy a dat mezi pamětí a portovými registry • čtení nebo zápis (ze strany procesoru) Přiřazení kódů – hodnot signálů – k jednotlivým instrukcím je možno vidět v tabulce 4. Definice celého instrukčního kódu pro všechny tři výše uvedené skupiny instrukcí bude mírně složitější. Existují totiž v každé skupině dva typy instrukcí dle operandů – instr R, K a instr R, R. Pro první typ jsme omezeni na čtyři volné bity, které musí definovat jak skupinu instrukcí, tak přídavné informace o instrukci. Zaveďme si tedy kódy pro skupiny instrukcí (typu instr R, K) do tabulky 5. Zbývající dva volné bity nechť určují přídavné informace, jež zároveň definují požadovanou operaci. Pro instrukce typu instr R, R je již situace jednodušší. Na konci instrukčního slova jsou k dispozici další 4 bity. Nechť tedy horní čtveřice udává typ 25
Instrukce s operandy R, K: gg op RRRR KKKK Instrukce s operandy R, R: tttt RRRR RRRR gg Legenda: gg op tttt RRRR KKKK
... ... ... ... ...
KKKK
op
kód skupiny instrukcí kód operace instrukce kód definující typ instr R, K identifikátor registru (zdrojového, cílového) horní/spodní bity konstanty
Obrázek 17: Instrukční kódy skupin L2, A, I/O instrukcí a spodní čtveřice pak určí skupinu a operaci. Výsledné složení instrukčního kódu bude vypadat dle obrázku 17. Nyní zpět k logickým operacím posunu a rotace (L1). Na první pohled se může zdát složité a zbytečné pro tyto operace definovat takovou širokou množinu instrukcí. Všimneme-li si však principu, kterým se tyto operace realizují, zjistíme, že toto množství instrukcí (podobně jako u ALU) využívá jednu základní část hardware a variace jsou řešeny minimální přídavnou logikou. Viz. obrázek 18. Základním výkonným prvkem je síť multiplexerů, která dle zvoleného směru operace (vlevo/vpravo) vybere do výsledku levého či pravého souseda původního bitu operandu. Tím lze efektivně docílit posunu operandu o jeden bit. Na jedné straně výsledku je pak získán vysouvaný bit (shift-out), který se buď zahodí nebo přesune do příznaku carry či je určen jako bit nasouvaný (shift-in). Např. z definice rotací plyne poslední varianta. Právě hodnota či zdroj nasouvaného bitu určuje další možnosti pro tuto skupinu instrukcí. Pro posuny je možné nasouvat bitovou konstantu 0/1 (SL0, SL1, SR0, SR1), hodnotu příznaku carry (SLA, SRA) nebo rozšiřovat výsledek o nejnižší resp. nejvyšší bit operandu (SLX, SRX) či vysouvaný bit ztotožnit s nasouvaným (ROL, ROR). Volbu nasouvaného bitu zajistí další multiplexer. Pro posunové/rotační instrukce tedy bude nutné do instrukčního kódu zavést informace o: • směru operace (vlevo/vpravo), • zdroji nasouvaného bitu, • hodnotě nasouvaného bitu, jedná-li se o bitovou konstantu. Naznačené možnosti shrnuje tabluka 6. Instrukční kódy operací posunu a rotace by pak mohly vypadat jako na obrázku 19. Touto skupinou instrukcí je završen návrh aritmeticko-logické jednotky a jejího řízení. Jako v ostatních částech, i zde bylo snahou při specifikování instrukčního kódu využít volných bitů k přímému řízení signálů ovládajících funkčnost probírané komponenty.
26
carry MSB (7) LSB (0) constant
00 01 10 11
bit numbers 76543210 6543210si
left
76543210 si7654321
right
shift_in 7
6
src1
5
4
3
2
1
6
5
4
3
2
1
0
1 0
1 0
1 0
1 0
1 0
1 0
1 0
7
6
5
2
1
4 3 shift_result
1 0
0 left/right
Obrázek 18: Posunovací/rotovací jednotka
instrukce SLA ROL SLX SL0 SL1 SRA SRX ROR SR0 SR1
směr lef t/right 0 0 0 0 0 0 0 0 0 0
nasouvaný bit shif tin carry MSB LSB konstanta konstanta carry MSB LSB konstanta konstanta
bitová konstanta 0 1 0 1
Tabulka 6: Informace k zavedení do instrukčního kódu operací posunu/rotace (L1)
27
tttt
RRRR
nasouvaný bit carry MSB LSB konstanta Legenda: tttt RRRR VVVV d ss b
... ... ... ... ... ...
VVVV
d
ss
b
ss 00 01 10 11
kód identifikující instrukce posunu/rotace identifikátor registru s operandem volné bity pro další použití směr operace, 0:vlevo, 1:vpravo identifikace nasouvaného bitu bitová konstanta (pro ss = 11)
Obrázek 19: Instrukční kódy skupiny L1 – posuvy/rotace 4.8.4
Instrukční dekodér
V předchozích částech byly podrobně probírány a sestavovány jednotlivé instrukční kódy. Několikrát byla uvedena poznámka, že zvolená technika je výhodná v zájmu instrukčního dekodéru. Nyní přichází na řadu činnost inverzní. Tedy z načtených instrukčních kódů, vydekódovat informace potřebné pro provádění instrukcí. Nebude zde podrobně probírána implementace této jednotky, protože se jedná v principu pouze o kombinační síť složenou z komparátorů, multiplexerů a tabulkových funkcí (hradel). Kompozice instrukčních kódů sestavená v předchozích částech je tímto prvkem opět rozebrána na základní elementy. Je realizována transformace instrukčního kódu na hodnoty všech signálů, kterými lze ovlivnit činnost jednotlivých částí procesoru. Pokud je to možné, jsou dané signály řízeny přímo z bitů slova instrukce. Zjednodušuje se tím logika potřebná k dekódování. 4.8.5
Registry
Sada registrů je speciálnější případ paměti. Ke své činnosti potřebuje dvě brány ke čtení a jednu pro zápis – dva zdrojové operandy a jeden cílový. Konvenční typy pamětí disponují jedním portem pro čtení/zápis. Samozřejme pokud by nebyla jiná možnost, pak by bylo možno pro realizaci registrů využít i jednoportové paměti, avšak na úkor rychlosti (až tři paměťové přístupy v jedné instrukci). Popř. by se dalo využít přímo registrů hardwarových, tzn. tvořených sadou D-klopných obvodů. Takto vyrobené registry jsou ovšem z pohledu hardwarových zdrojů „drahéÿ. Znamenalo by to – pro tento konkrétní případ – 16 ∗ 8 = 128 D-klopných obvodů. Řešením na míru je v FPGA tzv. dvouportová (dual-port) paměť. Tato možnost je dostupná jak pro distribuované tak blokové paměťové prvky. Znamená to, že k obsahu paměti lze v jednom okamžiku přistupovat nezávisle dvěma branami. Zápis je možný pouze z jedné, což ovšem není na škodu. Pro registrovou sadu tedy najdou vhodné využití distribuované paměťové prvky. Na adresu brány 0 (čtení zápis), budou přivedeny trvale bity zdrojového/prvního cílového 28
Obrázek 20: Začlenění paměťových prvků v systému registru z instrukčního kódu. Vstupní data portu 0 budou napojena na výsledek z ALU, který je možno přepínat s daty z paměti resp. I/O portu. Tím je zajištěna možnost přenosu dat z okolí do registrů. Na adresu brány 1 (pouze čtení) připojíme identifikátor druhého zdrojového registru. Zjistíme jej instrukčním dekodérem dle typu instrukce. Zapojení registrové sady do systému můžeme prozkoumat na obrázku 20. V případě potřeby jiných vlastností (počet bran, současný zápis na více branách apod.) nebude zbytí a registry bude nutno implementovat „ručněÿ N-ticemi D klopných obvodů s multiplexery na zápisových vstupech a demultiplexery na výstupech. Tímto způsobem je možno realizovat paměť s libovolným počtem souběžných čtecích/zápisových portů, ovšem cena takového prvku v FPGA je poměrně vysoká. Čtení z distribuované paměti je asynchronní, neb se jedná o kombinační funkci, tedy data jsou k dispozici za dobu propagace přivedené adresy a po ustálení přechodových jevů. To je jednak výhodou, protože není třeba hodinové hrany k zahájení čtení, jednak nevýhodou, protože propagační zpoždění se přičítá k celkovému zpoždění kombinační cesty. Zápis je pak již potřeba synchronizovat zápisovým hodinovým signálem. Ten je totožný s hlavními hodinami a je trigrován povolovacím vstupem. Povolení k zápisu do registrů vydává instrukční dekodér na základě prováděné instrukce a fáze vykonávání. 4.8.6
Paměti
Paměti jsou nezbytnou součástí navrhovaného procesoru. Jedná se o již zmíněné registrové pole, paměť programu a paměť dat. Paměť programu uchovává sadu instrukcí tvořících program. Je nutno ji naplnit smyslu29
plnými hodnotami před samotným spuštěním procesoru. Náhodná data by měla za příčinu náhodné chování procesoru. V tomto případě není řešena detekce chybného instrukčního kódu, tedy procesor by „něcoÿ dělal, ale o užitečnosti takového programu by se dalo pochybovat. Paměťové prvky v FPGA je možno inicializovat při programování tak, že již při prvním použití budou poskytovat požadovaná platná data. „Rozumnéÿ syntezátory jsou pak schopny inicializovat paměti např. nulovými daty, pokud se o to návrhář nepostará. Konkrétní instrukční kód ”0000000000000000” je platnou instrukcí s významem MOV R0, 0. Je to sice modifikační instrukce tzn. nedá se považovat za NOP (No OPeration), ale není destruktivní co se týče okolí procesoru (I/O, datová paměť). Realizace datové paměti opět závisí na konkrétních podmínkách a možnostech v čipu. V FPGA se dá předpokládat dostupnost blokové paměti, která je pro tuto úlohu ideální. Adresa je přiváděna z programového čítače, data jsou synchronně přenášena na vstup instrukčního dekodéru. Viz. obrázek 20. Vzhledem k potřebné velikosti, můžeme pro datovou paměť využít stejného typu zdrojů jako pro registry, tedy distribuované paměťové prvky. V použití blokové paměti nám také nic nebrání, pouze musíme mít na zřeteli, že block-RAM vyžaduje synchronizaci i pro čtení dat a musíme s tím počítat při generování signálu povolení zápisu9 do registru, abychom zapisovali platná data.
5
Programová podpora a implementace
Aby byl návrhář schopen navržené komponenty procesoru implementovat, potřebuje, kromě znalosti zvolených programovacích jazyků, také sadu podpůrných programových nástrojů. Tyto nástroje překlenou cestu od zdrojového VHDL tvaru či ještě dřív – popisu chování – až k binárnímu formátu vhodnému k zavedení do hardwaru.
5.1
assembler
Prvním nástrojem, jehož potřeba se nabízí již po přečtení části 4.7 o kódování instrukcí je assembler. Tedy prostředek, který umožňí překlad programu z mnemonického zápisu do binární podoby, aby uživatel navrženého procesoru nemusel zapisovat program přímo v této méně pohodlné formě. Pro tyto potřeby byl jednoduchý nástroj implementován. Vychází právě ze sekce 4.7. Hlavním úkolem je překlad mnemonického zápisu programu do binární podoby, která je jednoduše začlenitelná do vývojového řetězce. Konkrétně to znamená, že je možné výstup integrovat přímo do zdrojových VHDL souborů. Vstup, tedy uživatelský program, je syntakticky kontrolován dle gramatiky, následně převeden do binární formy a volitelně integrován do VHDL zdroje. Další úlohou assembleru je konverze mezi vlastním a nsim (viz. 5.2) formátem zdrojového souboru. Jedná se v podstatě pouze o přizpůsobení volnějšího, obecnějšího zápisu možnostem nsimu. Konkrétní údaje a příklady použití jsou uvedeny v příloze B.
5.2
nsim
V rámci projektu Liberouter10 vznikl kompilační a simulační nástroj nsim. Název programu vznikl ze spojení „nanoprocessor simulatorÿ, kdy pojmem nanoprocessor je označována proce9 10
Tento signál generuje instrukční dekodér, podrobnosti lze zjistit ve zdrojových souborech. Podrobnější informace v příloze D
30
sorová struktura v programovatelném hardware (FPGA). Komplexní informace o zmíněném programu lze nalézt v [4]. Zde bude zmíněna funkčnost potřebná pro tento text. Doplňující informace a příklady použití jsou uvedeny v příloze C. nsim lze s výhodou použít pro účely ověření správnosti či funkčnosti návrhu sémantiky (popisu chování) instrukční sady. Dalším jeho účelem je schopnost ladění (interpretace) činnosti programů napsaných v nově vznikajícím instrukčním souboru. Užití je vhodné jak před započetím implementace komponent ve VHDL, tak i až po úplném dokončení a odladění zařízení v FPGA. Jedná se o obecnější nástroj. V kontextu této práce najde využití hlavně jako softwarový simulátor navrhovaného procesoru. Výhodou tohoto přístupu je možnost vyzkoušení programových konstrukcí bez účasti hardware s relativně komfortním rozhraním a odstíněním od implementačních (hardwarově specifických) detailů. nsim byl navrhován s ohledem na generické použití, tzn. je potřebné mu jistou cestou sdělit jednak binární popis a členění jednotlivých instrukcí v sadě a jednak popsat chování těchto instrukcí. Tím je vytvořen v software jakýsi virtuální model procesoru, na němž je možné spouštět programy, provádět základní průzkum jejich chování a vliv na hostitelský procesor resp. jeho jednotlivé komponenty. To jsou převážně prvky paměťového charakteru resp. prvky na paměťovou reprezentaci převedené (rozuměj datové typy ve vyšším programovacím jazyce). Popis kódování instrukcí má relativně jednoduchou syntaxi, která je velice podobná definici makra v jazyce C. Sémantická část instrukce je pak přímo makrem v jazyce C a je s kódovou částí spjata. Pro samotný překlad do binární formy není potřebná. Uplatní se až při simulaci. V rámci definice chování instrukcí je možno využívat základní globální proměnné, které jsou definovány nsimem. Hlavními reprezentanty těchto proměnných jsou programový čítač ip a kód instrukce code1, z nehož je při popisu chování možno získat části operačního kódu instrukce. nsim také obsahuje vestavěný zásobník, který je s výhodou použitelný pro blokové struktury v programech, typicky instrukce typu CALL a RETURN (volání a návrat z podprogramu). Základní filosofie nsimu spočívá v parametrickém vybudování celého interpretu. Parametrem konstrukce je soubor definice instrukcí, který se podílí na vzniku zdrojového souboru interpretu. Vybudováním se pak rozumí zpracování zdrojového souboru preprocesorem a kompilátorem do spustitelné formy. Tento přístup předpokládá stálou přítomnost zdrojových souborů a kompilátoru, což se v některých případech může jevit jako komplikace či nevýhoda. Vstupem takto „na míruÿ vybudovaného nástroje je program v mnemonickém zápisu instrukcí. Výstupem je pak volitelně holý binární tvar programu odpovídající definovaným kódům či interakce programu s uživatelem při simulaci a ladění. nsim provádí jednoduchou transformaci vstupu metodou podobnou preprocesingu maker v jazyce C. Z toho plynou pro zápis simulovaného programu jistá omezení: • jeden mnemonický znak instrukce nemůže být následován více druhy operandů. Proto je nutné např. instrukce MOV R0,0 a MOV R0,R1 rozdělit a transformovat na zápisy MOVC R0,0 a MOVR R0,R1, kde symboly Rn či jiné musejí být předem definovány jako čísla, resp. části instrukčního kódu. • ve zdrojovém programu jsou prováděny minimální syntaktické kontroly, tudíž je náchylnější na „chyby z nepozornostiÿ • návěští (label) není povoleno na stejném řádku jako instrukce Tato omezení jsou známa, uvedena v dokumentaci nebo vyplývají ze způsobu implementace nástroje. V současnosti je před dokončením druhá generace programu, která znamená 31
ucf
pcf
twr TRCE
vhd
edf SYNTH
ngd NGDBUILD
map.ncd MAP
par.ncd PAR
map.ngm
bit
mcs
BITGEN PROMGEN nga
sim.sdf
NGDANNO
sim.vhd
NGD2VHDL
Obrázek 21: Překlad zdrojových VHDL souborů do binární podoby vhodné pro zavedení do hardware FPGA. Popis zkratek viz. tabulky 7 a 8. významné zdokonalení a tyto nedostatky již neobsahuje. Ke konverzi mezi běžným zápisem programu, akceptovaným gramatikou B.6 a zápisem akceptovaným nsimem je možné použít speciální volbu při volání assembleru (viz. sekce B).
5.3
modelsim
Nástroj vhodný pro simulaci komponent či procesoru zapsaných ve VHDL. Umožňuje jak simulaci funkční, tzn. ověření správnosti návrhu po stránce algoritmické, tak simulaci časovou, tzn. zavedení předpokládaných zpoždění do systému a jeho následnou simulaci. modelsim je hlavním nástrojem při ladění (tzn. simulaci a verifikaci) činnosti zapsaného VHDL kódu.
5.4
Syntéza
Syntézou se rozumí převod VHDL zápisu do formy bližší cílové technologii. Do této činnosti bývá často začleňován i souhrn následných akcí vedoucích k vygenerování binárního souboru pro inicializaci (naprogramování) hardware. Transformační sekvence zdrojových textů v jazyce VHDL do dalších podob vedoucích k cíli je patrná z obrázku 21. Důležitými etapami jsou: • převod VHDL → EDIF – zde je možné získat prvotní informaci o zpožděních na vysyntetizovaných logických stupních. Syntezátor je také schopen nabídnout zobrazení vytvořené logiky jak na úrovni RTL (Register Transfer Level) tak na úrovni primitiv cílové technologie. Zde je možné tedy provést základní verifikaci návrhu co do požadované struktury. Např.: zda byly správně rozpoznány a vysyntetizovány modely komponent v FPGA či zda jiné obecné komponenty byly transformovány v očekávané struktury apod. • mapování – vytvoření relace mezi vysyntetizovanými jednotkami a dostupnými zdroji v cílové technologii. • rozmístění a propojení – namapované jednotky jsou rozloženy po čipu na konkrétní pozice a propojeny. Zde je možné získat druhotné časové informace, které jsou s vysokou pravděpodobností velmi blízké reálným zpožděním v cílové platformě. Z tohoto bodu je také iniciována časová simulace (viz. 5.3). 32
SYNTH
...
NGDBUILD
...
MAP PAR
... ...
TRCE BITGEN PROMGEN
... ... ...
NGDANNO
...
NDG2VHDL
...
Syntezátor Leonardo spectrum. Z VHDL zdroje (vhd) vytváří tzv. netlist ve formátu EDIF (edf). Tj. nízkoúrovňový popis navržených obvodů. Převod EDIF netlistu (edf) a uživatelských omezujících podmínek (ucf) do nativního popisu databáze Xilinx (ngd) reprezentující logický návrh. Mapování logického návrhu (ngd) na prvky z Xilinx FPGA. Place & Route – rozmístění namapovaných komponent (map.ncd) po čipu a jejich propojení. Získání časového reportu po PAR Generování bitové mapy (bit) pro programování FPGA. Převod bitové mapy (bit) do formátu pro paměť EPROM (mcs). Anotace namapovaného (map.ncd) nebo rozmístěného a propojeného (par.ncd) návrhu. Převod návrhu (nga) do VHDL formy (sim.vhd) a specifikace zpoždění (sdf) pro následnou simulaci po mapování (MAP) či rozmístění a propojení (PAR).
Tabulka 7: Význam zkratek názvů nástrojů použitých v obrázku 21.
vhd
...
edf
...
ucf
...
ngd pcf ncd ngm
... ... ... ...
nga
...
twr sdf
... ...
VHDL, VHSIC HDL, Very High Speed Integrated Circuit Hardware Description Language; Jazyk pro popis hardware. EDIF, Electronic Design Interchange Format; nízkoúrovňový formát pro popis návrhů elektronických obvodů. User Constraints File; Popis uživatelem zadaných omezujících podmínek. Native Generic Database; Physical Constraints File; Native Circuit Description; Native Generic Mapped; Struktura, názvy a hierarchie komponent návrhu po fázi mapování. Native Generic Annotated; Anotovaný návrh po mapování či rozmístění a propojení vhodný k zpětnému převodu do vyššího jazyka k simulaci. Timing report; Standard Delay Format; Popis zpoždění jednotlivých komponent návrhu po zpětné anotaci a převodu do VHDL. Nutné pro správnou simulaci po fázi mapování či rozmístění a propojení.
Tabulka 8: Význam zkratek názvů souborů použitých v obrázku 21.
33
instr_addr_bus
instr_bus
result
=0? zero_we
prog
Z
inc
carry_out
ROM
instr
DEC
carry_in zero_in
reg_we src1
CNT
carry_we
C
0 0
REG
src2
opd1
ALU
src2_dout
const
0 1 const_src2 mem_rd mem_wr push instr_addr
0 1
1 0
1 1 invalid
alu_result
opd2
mem_addr
pop
CSTACK ret_addr
RAM
mem_dout
mem_din io_rd io_wr port_din
port_id
I/O port_dout
port_dout
port_din port_id io_read io_write
Obrázek 22: Základní (strukturní, kombinační) propojení komponent v procesor Zde použitý syntézní a překladový systém je založen na podpoře z projektu Liberouter. Tento projekt používá pro syntézu nástroj Leonardo Spectrum. Pro následné činnosti jsou použity programy z kolekce Xilinx ISE. Automatizovaně, sestavením potřebných závislostí v souboru Makefile, je možné překládat design s minimálním úsilím uživatele. Stejný postup je prováděn i v případě řízení „commandlineÿ aplikací systémem tlačítek a menu z GUI aplikace Xilinx ISE.
5.5
Implementace
Procesor, jehož architektura a komponenty byly diskutovány v sekcích 4 a 4.8, vznikl propojením jednotlivých částí strukturním způsobem. Základní propojení je patrné z obrázku 22. Programový čítač CNT generuje adresu pro instrukční paměť ROM, případně pro zásobník návratových adres CSTACK. Z paměti programu ROM vychází slovo instrukce, které je dekódováno instrukčním dekodérem DEC na jednotlivé řídící signály a identifikátory operandů. 34
Jednotka alu call stack data ram instruction decoder program counter program rom register set processor
Tabulka 9: Zpoždění jednotlivých komponent a celkového zapojení procesoru.
Ze souboru registrů REG jsou na základě identifikace zdrojově-cílového a zdrojového registru vybrány hodnoty operandů. Ty jsou, podle typu instrukce, přivedeny na aritmeticko/logickou jednotku ALU resp. datovou paměť RAM či registry vstupně/výstupních portů I/O. Výsledek operace resp. data z těchto jednotek jsou přivedeny na vstup resgistrové sady REG pro zápis výsledku a příznaků. Každá instrukce je rozčleněna na dva vykonávací takty (bez překrytí), tedy CP I je konstantní a je rovno dvěma. Vzhledem k tomu, že programový čítač a paměť instrukcí jsou synchronní a v instrukční sadě je zavedena potřeba skoků, je nutný minimálně jeden další takt pro určení následující adresy instrukce. Instrukce je načtena, dekódována a částečně provedena v prvním taktu, programový čítač je aktualizován v druhém taktu. Zároveň jsou prováděny akce související s dokončováním instrukce. Jedná se o zápisy do paměti resp. vstupně/výstupních portů. Vlastní kombinační zpoždění jednotlivých komponent a zpoždění celkového zapojení je vidět v tabulce 9. Výhodou tohoto uspořádání je jednoduchost propojení. Nevýhodou může v jistém případě být vysoké zpoždění kompletu. Je dáno přímým propojením několika čistě kombinačních prvků za sebou a přímo určuje maximální pracovní kmitočet. Z tohoto důvodu je možné zapojit jednotlivé komponenty způsobem patrným v obrázku 23, kdy jsou mezi jednotlivými komponentami umístěny záchytné registry. Tento přístup sníží kombinační zpoždění kompletu na hodnotu, která je dána zpožděním nejpomalejší komponenty. Každá instrukce bude vykonávána tolik taktů, kolik bude v cestě zapojeno registrů. S přídavnou logikou potřebnou pro řízení a detekci konfliktů je možné takto upravenou strukturu vylepšit do řetězené jednotky s překrývaným zpracováním instrukcí. CP I je pak závislé na daném překrývání instukcí při zpracovávání a množství zastavování linky při konfliktech. Viz. část 3.2.
6
Závěr
Cílem této práce bylo obeznámení čtenáře s jedním ze způsobů návrhu modulárního systému pro programovatelná logická pole FPGA. Po stručném úvodu do technologie a prostředků implementace byl probrán návrh jednotlivých komponent a jejich vazba na řízení zároveň dala vzniknout instrukční sadě použité ve finálním celku. Navržený systém byl definován a simulován v nástroji nsim, který byl po dohodě s autorem mírně upraven. Tyto úpravy směřovaly pouze k jednoduššímu a přímočařejšímu použití programu. Následně byla provedena implementace jednotlivých komponent v jazyce VHDL, kdy každá komponenta z daného souboru byla samostatně simulována a laděna v nástroji 35
instr_addr_bus
instr_bus
result
inc ret_addr
=0?
prog
prog
CTRL
carry_out
inc zero_we
Z
ROM
instr
DEC
carry_in zero_in
reg_we src1
CNT
carry_we
C
0 0
REG
src2
opd1
ALU
src2_dout
const
0 1
instr_addr
const_src2 mem_rd mem_wr push pop
0 1
1 0
1 1 invalid
alu_result
opd2
mem_addr
CSTACK ret_addr
RAM
mem_dout
mem_din io_rd io_wr port_id
port_din
I/O port_dout
port_dout
port_din port_id io_read io_write
Obrázek 23: Rozšíření zapojení o záchytné registry (ilustrativně). Zpětné vazby mezi jednotkami musí být ošetřeny vhodným počtem zpožďovacích registrů, řídící jednotka CTRL je propojena se zápisovými/nulovacímy vstupy registrů. Povoluje inkrementaci programového čítače PC, popř. vyhodnocuje konflikty a řídí zřetězené zpracování.
36
ModelSim. Prvky označené jako funkční byly propojeny do výsledného celku – procesoru, na němž bylo možné v simulaci ověřit funkčnost jednoduchého programu. Pro komfortnější práci byl následně implementován asembler se schopnostmi přímých zásahů do zdrojových VHDL souborů. Tento přístup usnadnil ladění a zvýšil čitelnost a názornost celého systému. Po této části nastala fáze syntézní, kdy jednotlivé komponenty byly syntetizovány a analyzovány co do zpoždění kombinačních cest, případně byly optimalizovány nebo byly předefinovány konstrukce ryze simulační. Následně byla provedena syntéza celého systému vzniknuvšího propojením daných komponent. Z nastíněného postupu jasně vyplývá nesporná výhoda dekompozice komplexního celku na samostatné menší části, se kterými je možné operovat nezávisle. Již při návrhu potřebného rozhraní mezi dílčími komponentami se vynoří některé problémy či požadavky, které pak lze snadno řešit. Části systému lze implementovat postupně a každou dokončenou komponentu lze s vhodnou přípravou testu simulovat nezávisle tak, jako by byla začleněna v již funkčním celku. Podpora simulačního software také nabízí řadu výhod. Před samotnou implementací v cílovém jazyce je k dispozici model na abstraktnější úrovni, který umožní návrháři odhalit mnohé nedostatky a udělat si rámcovou představu o následné implementaci. Ta je pak podrobena dalším následným simulačním cyklům na nižší úrovni a konfrontována s abstraktním modelem. Průchod těmito vývojovými cykly minimalizuje pravděpodobnost výskytu závažných nedostatků. Obecně pak platí, že časová investice vložená do abstraktního modelu je menší než ta, kterou si vyžádají následné úpravy či opravy ve finálním produktu.
Reference [1] Václav Dvořák a Vladimír Drábek. Architektura procesorů. VUTIUM, Brno, Antonínská 1, 1999. [2] Peter J. Ashenden. The Student’s Guide to VHDL. Morgan Kaufmann Publishers, Inc., San Francisco, California, http://www.mkp.com, 1998. [3] Ken Chapman. Creating Embedded Microcontrollers (Programmable State Machines). Xilinx, http://support.xilinx.com/xlnx/xweb/xil_tx_home.jsp, July 2002. [4] Filip Höfer. Packet Analysis for IPv6 Router Implemented by a PCI Acceleration card. Liberouter, http://www.cesnet.cz/doc/techzpravy/2003/ipv6pktanalysis/ ipv6pktanalysis.pdf, May 2003. [5] Xilinx, http://www.xilinx.com/xlnx/xweb/xil_publications_index.jsp. Virtex-II 1.5V Field-Programmable Gate Arrays, September 2002.
37
A
Přehled instrukcí
Značka, operace
Využité příznaky, kód instrukce, Zero Carry
15
14
13
12
11
10
9
8
7
6
nastavené příznaky 5
4
3
2
1
0
Zero Carry
MOV RDST , Konst Input Output RDST ⇐ Konst Z ⇐ RDST ≡ 0 Přesun přímého operandu (konstanty) Konst do cílového registru RDST . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
0
0 0
0
15
14
12
13
RDST
11
10
9
Konst
8
7
6
5
4
3
2
1
0
Zero Carry
0 0 0 1 RDST Konst AND RDS1 , Konst Input Output RDS1 ⇐ RDS1 and Konst Z ⇐ RDS1 ≡ 0 Logický součin přímého operandu (konstanty) Konst a obsahu registru RDS1 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
0 0 1 0 RDS1 Konst OR RDS1 , Konst Input Output RDS1 ⇐ RDS1 or Konst Z ⇐ RDS1 ≡ 0 Logický součet přímého operandu (konstanty) Konst a obsahu registru RDS1 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
0 0 1 1 RDS1 Konst XOR RDS1 , Konst Input Output RDS1 ⇐ RDS1 xor Konst Z ⇐ RDS1 ≡ 0 Exlusivní logický součet přímého operandu (konstanty) Konst a obsahu registru RDS1 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
ADD RDS1 , Konst 0 1 0 0 RDS1 Konst RDS1 ⇐ RDS1 + Konst Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický součet přímého operandu (konstanty) Konst a obsahu registru RDS1 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu. ADC RDS1 , Konst 0 1 0 1 RDS1 Konst RDS1 ⇐ RDS1 + Konst + C Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický součet přímého operandu, konstanty Konst, obsahu registru RDS1 a příznaku Carry do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu.
38
Značka, operace Využité příznaky, kód instrukce, nastavené příznaky Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry SUB RDS1 , Konst 0 1 1 0 RDS1 Konst RDS1 ⇐ RDS1 − Konst Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický rozdíl obsahu registru RDS1 a přímého operandu (konstanty) Konst do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu. Zero Carry
15
14
13
12
11
Zero Carry
15
14
13
12
11
10
9
8
7
6
5
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
4
3
2
1
0
Zero Carry
SBC RDS1 , Konst 0 1 1 1 RDS1 Konst RDS1 ⇐ RDS1 − Konst − C Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický rozdíl obsahu registru RDS1 , přímého operandu (konstanty) Konst, a příznaku Carry do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu.
LDR RDST , Addr Input Output RDST ⇐ RAM[Addr] Z ⇐ RDST ≡ 0 Přesun dat z datové paměti RAM z přímé adresy Addr do cílového registru RDST . Příznak Zero je nastaven, je-li výsledek operace nulový. 1 0 0 0
Zero Carry
15
14
13
12
RDST
11
1 0 0 1
10
9
Addr
8
7
6
5
RSRC
4
3
2
1
0
Zero Carry
Addr
STR RSRC , Addr Input Output RSRC ⇒ RAM[Addr] Přesun dat z ze zdrojového registru RSRC do paměti dat na přímou adresu Addr. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
1 0 1 0 RDST Port INP RDST , P ort Input Output RDST ⇐ Input[P ort] Z ⇐ RDST ≡ 0 Přesun dat ze vstupního portu P ort do cílového registru RDST . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
1 0 1 1
11
10
9
RSRC
8
7
6
5
4
3
OUT RSRC , P ort Input RSRC ⇒ Output[P ort] Přesun dat z ze zdrojového registru RSRC do výstupního portu P ort.
39
2
1
0
Zero Carry
Port
Output
Značka, operace
Využité příznaky, kód instrukce, Zero Carry
15
14
13
12
11
10
9
8
7
6
nastavené příznaky 5
4
3
2
1
0
Zero Carry
1 1 0 0 RDST RSRC 0 0 0 0 MOV RDST , RSRC Input Output RDST ⇐ RSRC Z ⇐ RDST ≡ 0 Přesun obsahu zdrojového registru RSRC do cílového registru RDST . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
AND RDS1 , RS2 Input Output RDS1 ⇐ RDS1 and RS2 Z ⇐ RDS1 ≡ 0 Logický součin obsahu prvního RDS1 a druhého zdrojového registru RS2 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
1 1
0 0
15
13
14
12
RDS1
11
10
9
RS2
8
7
6
5
0 0 0
4
3
2
1
1
0
Zero Carry
OR RDS1 , RS2 Input Output RDS1 ⇐ RDS1 or RS2 Z ⇐ RDS1 ≡ 0 Logický součet obsahu prvního RDS1 a druhého zdrojového registru RS2 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
1 1
0 0
15
13
14
12
RDS1
11
10
9
RS2
8
7
6
5
4
0 0 1
0
3
0
2
1
Zero Carry
1 1 0 0 RDS1 RS2 0 0 1 1 XOR RDS1 , RS2 Input Output RDS1 ⇐ RDS1 xor RS2 Z ⇐ RDS1 ≡ 0 Exlusivní logický součet obsahu prvního RDS1 a druhého zdrojového registru RS2 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
ADD RDS1 , RS2 1 1 0 0 RDS1 RS2 0 1 0 0 RDS1 ⇐ RDS1 + RS2 Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický součet obsahu prvního RDS1 a druhého zdrojového registru RS2 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu.
ADC RDS1 , RS2 1 1 0 0 RDS1 RS2 0 1 0 1 RDS1 ⇐ RDS1 + RS2 + C Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický součet obsahu prvního zdrojového registru RDS1 , druhého zdrojového registru RS2 a příznaku Carry do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu.
40
Značka, operace Využité příznaky, kód instrukce, nastavené příznaky Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry SUB RDS1 , RS2 1 1 0 0 RDS1 RS2 0 1 1 0 RDS1 ⇐ RDS1 − RS2 Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický rozdíl obsahu prvního RDS1 a druhého zdrojového registru RS2 do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
SBC RDS1 , RS2 1 1 0 0 RDS1 RS2 0 1 1 1 RDS1 ⇐ RDS1 − RS2 − C Input Output Z ⇐ RDS1 ≡ 0 C ⇐ přenos do 8. řádu Aritmetický rozdíl obsahu prvního zdrojového registru RDS1 , druhého zdrojového registru RS2 a příznaku Carry do cílového registru RDS1 . Příznak Zero je nastaven, je-li výsledek operace nulový. Příznak Carry je nastaven, dojde-li k přenosu z nejvyššího řádu. 1 1 0 0 RDST RADDR 1 0 0 0 LDR RDST , RADDR Input Output RDST ⇐ RAM[RADDR ] Z ⇐ RDST ≡ 0 Přesun dat z datové paměti RAM z nepřímé adresy v registru RADDR do cílového registru RDST . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
13
12
1 1
0 0
15
13
11
10
9
8
7
RSRC
6
5
4
RADDR
1
0
1 0 0
3
2
1
3
0
Zero Carry
STR RSRC , RADDR Input Output RSRC ⇒ RAM[RADDR ] Přesun dat z ze zdrojového registru RSRC do paměti dat na nepřímou adresu z registru RADDR . Zero Carry
14
12
11
10
9
8
7
6
5
4
2
1
Zero Carry
1 1 0 0 RDST RPORT 1 0 1 0 INP RDST , RP ORT Input Output RDST ⇐ Input[RP ORT ] Z ⇐ RDST ≡ 0 Přesun dat ze vstupního portu uvedeného v registru RP ORT do cílového registru RDST . Příznak Zero je nastaven, je-li výsledek operace nulový. Zero Carry
15
14
1 1
13
12
0 0
11
10
9
RSRC
8
7
6
5
RPORT
4
3
2
1 0
1
0
1 1
Zero Carry
OUT RSRC , RP ORT Input Output RSRC ⇒ Output[RP ORT ] Přesun dat z ze zdrojového registru RSRC do výstupního portu uvedeného v registru RP ORT .
41
Značka, operace
Využité příznaky, kód instrukce, Zero Carry
15
14
13
12
11
10
1 1 1 1
9
8
7
6
nastavené příznaky 5
0 0
4
3
2
1
0
Zero Carry
Addr
JMP Addr Input Output P C ⇐ Addr Nepodmíněný skok na adresu Addr. Programový čítač P C je aktualizován hodnotou Addr. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
1 1 1 1 0 1 Addr CAL Addr Input Output CST ACK ⇐ PUSH(P C) P C ⇐ Addr Volání podprogramu na adrese Addr. Současná hodnota programového čítače P C je uložena do zásobníku návratových adres CST ACK a programový čítač P C je aktualizován hodnotou Addr. Zero Carry
15
14
13
12
11
10
1 1 1 1
9
8
1
0
9
8
7
6
5
4
3
2
1
0
Zero Carry
Addr
RET Input Output P C ⇐ POP() + 1 Návrat z podprogramu. Programový čítač P C je aktualizován hodnotou vyjmutou z vrcholu zásobníku CST ACK a zvýšenou o jedničku. Zero Carry
15
14
13
12
11
10
7
6
5
4
3
2
1
0
Zero Carry
JMP Z, Addr 1 1 1 0 0 0 0 0 Addr if (Z): Input Output P C ⇐ Addr endif Podmíněný skok na adresu Addr. Programový čítač P C je aktualizován hodnotou Addr, je-li nastaven příznak Zero. CAL Z, Addr Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 1 0 0 0 0 1 Addr if (Z): Output CST ACK ⇐ PUSH(P C) Input P C ⇐ Addr endif Podmíněné volání podprogramu na adrese Addr. Je-li nastaven příznak Zero, současná hodnota programového čítače P C je uložena do zásobníku návratových adres CST ACK a programový čítač P C je aktualizován hodnotou Addr. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
RET Z 1 1 1 0 0 0 1 0 Addr if (Z): Input Output P C ⇐ POP() + 1 endif Podmíněný návrat z podprogramu. Je-li nastaven příznak Zero, programový čítač P C je aktualizován hodnotou vyjmutou z vrcholu zásobníku CST ACK a zvýšenou o jedničku.
42
Značka, operace Využité příznaky, kód instrukce, nastavené příznaky Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry JMP N Z, Addr 1 1 1 0 1 0 0 0 Addr if not(Z): Input Output P C ⇐ Addr endif Podmíněný skok na adresu Addr. Programový čítač P C je aktualizován hodnotou Addr, je-li vynulován příznak Zero. CAL N Z, Addr Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 1 0 1 0 1 Addr 0 if not(Z): Output CST ACK ⇐ PUSH(P C) Input P C ⇐ Addr endif Podmíněné volání podprogramu na adrese Addr. Je-li vynulován příznak Zero, současná hodnota programového čítače P C je uložena do zásobníku návratových adres CST ACK a programový čítač P C je aktualizován hodnotou Addr. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
RET N Z 1 1 1 0 1 0 1 0 Addr if not(Z): Input Output P C ⇐ POP() + 1 endif Podmíněný návrat z podprogramu. Je-li vynulován příznak Zero, programový čítač P C je aktualizován hodnotou vyjmutou z vrcholu zásobníku CST ACK a zvýšenou o jedničku. JMP C, Addr 1 1 1 0 0 1 0 0 Addr if (C): Input Output P C ⇐ Addr endif Podmíněný skok na adresu Addr. Programový čítač P C je aktualizován hodnotou Addr, je-li nastaven příznak Carry. CAL C, Addr Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 1 0 0 1 0 1 Addr if (C): Output CST ACK ⇐ PUSH(P C) Input P C ⇐ Addr endif Podmíněné volání podprogramu na adrese Addr. Je-li nastaven příznak Carry, současná hodnota programového čítače P C je uložena do zásobníku návratových adres CST ACK a programový čítač P C je aktualizován hodnotou Addr.
43
Značka, operace Využité příznaky, kód instrukce, nastavené příznaky Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry RET C 1 1 1 0 0 1 1 0 Addr if (C): Input Output P C ⇐ POP() + 1 endif Podmíněný návrat z podprogramu. Je-li nastaven příznak Carry, programový čítač P C je aktualizován hodnotou vyjmutou z vrcholu zásobníku CST ACK a zvýšenou o jedničku. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
JMP N C, Addr 1 1 1 0 1 1 0 0 Addr if not(C): Input Output P C ⇐ Addr endif Podmíněný skok na adresu Addr. Programový čítač P C je aktualizován hodnotou Addr, je-li vynulován příznak Carry. CAL N C, Addr Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 1 0 1 0 1 Addr 1 if not(C): Input Output CST ACK ⇐ PUSH(P C) P C ⇐ Addr endif Podmíněné volání podprogramu na adrese Addr. Je-li vynulován příznak Carry, současná hodnota programového čítače P C je uložena do zásobníku návratových adres CST ACK a programový čítač P C je aktualizován hodnotou Addr. Zero Carry
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
Zero Carry
RET N C 1 1 1 0 1 1 1 0 Addr if not(C): Input Output P C ⇐ POP() + 1 endif Podmíněný návrat z podprogramu. Je-li vynulován příznak Carry, programový čítač P C je aktualizován hodnotou vyjmutou z vrcholu zásobníku CST ACK a zvýšenou o jedničku. SLA RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 0 0 0 0 Cout ⇐ RDS1 [7] Input Output RDS1 ⇐ RDS1 [6 : 0]&C Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vlevo. Do bitu RDS1 [0] je nasunut obsah příznaku Carry. Carry je pak aktualizován vysouvaným bitem RDS1 [7].
44
Značka, operace Využité příznaky, kód instrukce, nastavené příznaky ROL RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 0 0 1 0 ROT ⇐ RDS1 [7] Input Output RDS1 ⇐ RDS1 [6 : 0]&ROT Z ⇐ RDS1 ≡ 0 C ⇐ ROT Obsah registru RDS1 je bitově rotován o jednu pozici vlevo. Do bitu RDS1 [0] je nasunut obsah bitu RDS1 [7]. Příznak Carry je aktualizován „rotačnímÿ bitem RDS1 [7]. SLX RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 0 1 0 0 Cout ⇐ RDS1 [7] Output RDS1 ⇐ RDS1 [6 : 0]&RDS1 [0] Input Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vlevo. Do bitu RDS1 [0] je nasunut obsah předchozí hodnoty bitu RDS1 [0]. Příznak Carry je aktualizován vysouvaným bitem RDS1 [7]. SL0 RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 0 1 1 0 Cout ⇐ RDS1 [7] Input Output RDS1 ⇐ RDS1 [6 : 0]&0 Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vlevo. Do bitu RDS1 [0] je nasunuta hodnota 0. Příznak Carry je aktualizován vysouvaným bitem RDS1 [7]. SL1 RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 0 1 1 1 Cout ⇐ RDS1 [7] Input Output RDS1 ⇐ RDS1 [6 : 0]&1 Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vlevo. Do bitu RDS1 [0] je nasunuta hodnota 1. Příznak Carry je aktualizován vysouvaným bitem RDS1 [7]. SRA RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 1 0 0 0 Cout ⇐ RDS1 [0] Input Output RDS1 ⇐ C&RDS1 [7 : 1] Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vpravo. Do bitu RDS1 [7] je nasunut obsah příznaku Carry. Carry je pak aktualizován vysouvaným bitem RDS1 [0].
45
Značka, operace Využité příznaky, kód instrukce, nastavené příznaky SRX RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 1 0 1 0 Cout ⇐ RDS1 [0] Output RDS1 ⇐ RDS1 [7]&RDS1 [7 : 1] Input Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vpravo. Do bitu RDS1 [7] je nasunut obsah předchozí hodnoty bitu RDS1 [7]. Příznak Carry je aktualizován vysouvaným bitem RDS1 [0]. ROR RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 1 1 0 0 ROT ⇐ RDS1 [0] Input Output RDS1 ⇐ ROT &RDS1 [7 : 1] Z ⇐ RDS1 ≡ 0 C ⇐ ROT Obsah registru RDS1 je bitově rotován o jednu pozici vpravo. Do bitu RDS1 [7] je nasunut obsah bitu RDS1 [0]. Příznak Carry je aktualizován „rotačnímÿ bitem RDS1 [0]. SR0 RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 1 1 1 0 Cout ⇐ RDS1 [0] Input Output RDS1 ⇐ 0&RDS1 [7 : 1] Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vpravo. Do bitu RDS1 [7] je nasunuta hodnota 0. Příznak Carry je aktualizován vysouvaným bitem RDS1 [0]. SR1 RDS1 Zero Carry 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Zero Carry 1 1 0 1 RDS1 1 1 1 1 Cout ⇐ RDS1 [0] Input Output RDS1 ⇐ 1&RDS1 [7 : 1] Z ⇐ RDS1 ≡ 0 C ⇐ Cout Obsah registru RDS1 je bitově posunut o jednu pozici vpravo. Do bitu RDS1 [7] je nasunuta hodnota 1. Příznak Carry je aktualizován vysouvaným bitem RDS1 [0].
46
B B.1
assembler Název
assembler – překladač mnemonického zápisu instrukcí do binárního kódu.
Spuštění programu bez voleb předpokládá na standardním vstupu zdrojový soubor vyhovující gramatice ze sekce B.6. Tento vstup je tranformován do binární podoby a jako holý text je vypsán na standardní výstup. Tím se rozumí vypsání šestnácti binárních číslic 0 a 1 (tvořících kód každé přeložené instrukce) na jednom řádku. Toto je základní funkce programu. Další činností je pak překlad vstupního programu do formy vhodné pro simulaci v nástroji nsim (viz. část 5.2 a příloha C).
B.4
Volby
-h . . . zobrazení základní nápovědy k použití programu a jeho volbám. -i file . . . specifikace vstupního souboru. Není-li uvedeno, předpokládá se standardní vstup. -o file . . . specifikace výstupního souboru. Není-li uvedeno, předpokládá se standardní výstup. -f form . . . nastavení výstupního formátu. K dispozici jsou možnosti plain, verbose a vhdl. Není-li uvedeno, používá se hodnota plain. plain . . . holý výstup obsahující pouze kódy přeložených instrukcí. Příklad: 1001100101100011 verbose . . . předchozí tvar je rozčleněn na čtyřbitové skupiny, obohacen o adresu instrukce a mnemonický zápis instrukce. Příklad: 0x00:"1001 1001 0110 0011" STR R9, 99 vhdl . . . výstupní formát je kompatibilní s inicializací pole bitových vektorů šířky 16 bitů o velikosti 256 položek. Vhodné v kombinaci s volbami -t temp a -T temp. Příklad: "1001100101100011", -- 027 0x9963 STR R9, 99 -t temp . . . specifikace šablony pro výstup do VHDL. Pokud je zadána volba -f vhdl, program čte obsah souboru se šablonou a v nezměněné podobě jej zapisuje na výstup. Když však v šabloně narazí na začátku řádku na direktivu #####ROM#CONTENT#####, nahradí její první výskyt přeloženým kódem programu ve VHDL-kompatibilní formě. Není-li použita volba -f vhdl, nemá -t temp žádný účinek.
47
-T temp . . . specifikace další šablony při výstupu do VHDL. Pokud je zadána volba -f vhdl, program čte obsah souboru se šablonou a v nezměněné podobě jej zapisuje na standardní výstup nebo do souboru specifikovaného volbou -O file. Direktiva #####PRG#CONTENT##### je pak nahrazena řetězci s mnemonickým zápisem programu. Tato volba je vhodná pro inicializaci pole řetězců v testbench souboru pro jednodušší orientaci při simulaci VHDL. Dává k dispozici čitelnější popis instrukcí oproti instrukčním kódům. Vzhledem k oddělení simulační a syntézní části kódu je tento krok prováděn do zvláštního souboru a ne do souboru s inicializací programové paměti, jak by se na první pohled mohlo zdát jednodušší. -n . . . transformace vstupního programu do formy kompilovatelné programem nsim. Vzhledem k jistým koncepčním omezením, není možné, aby nsim zpracoval původní formu zápisu programu. Proto je nutné provést některé změny: • Instrukce, u kterých je možné použít více druhů zápisu operandů, je nutno rozdělit na samostatné zápisy instrukcí, jejichž počet odpovídá počtu možných zápisů. Příklad: MOV MOV • JMP JMP
• Je-li řádek s intrukcí označen návěštím (label), je nutno jej rozdělit na řádky dva. Zvlášť návěští a zvlášť instrukci. nsim nepovoluje kombinaci návěští a instrukce na jednom řádku. Příklad: •
label: MOV R0, 10 ⇒ label: ⇒ MOVR R0, 10
• Převedení binárního zápisu čísel do (hexa)decimální podoby. Např.: 0b00001011 ⇒ 13.
B.5
Příklad použití
• Vstupní program (uložený v souboru, nebo zapsaný na standardní vstup): mov mov mov loop: out sl0 sub jmp finish: jmp
Pozn.: Hlášení programu jsou směrována na standardní chybový výstup, čili se při přesměrování do souboru nebo volbě -o file ve výstupu neobjeví. • Rozšířená forma překladu. Použití VHDL šablony (relevantní část šablony s direktivou je patrná na začátku výpisu): ... -- ----------------------------------------------------------------------------- Program ROM architecture -- ---------------------------------------------------------------------------architecture program_rom of program_rom is type ROM_t is array(0 to 255) of std_logic_vector(15 downto 0); constant ROM : ROM_t := ( #####ROM#CONTENT##### ); begin ...
• Vlastní překlad: $ ./assembler.exe < ../ASM/test5.asm \ -f vhdl \ -t ../VHDL/program_rom_template.vhd Parsing input ... DONE Printing output (updated template content) ... -- ----------------------------------------------------------------------------- Processor for FPGA -- Pavel Faltynek, [email protected] --- program_rom.vhd - program ROM -- ---------------------------------------------------------------------------library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all;
49
-- ----------------------------------------------------------------------------- Program ROM entity -- ---------------------------------------------------------------------------entity program_rom is port( clk : in std_logic; address : in std_logic_vector(7 downto 0); data : out std_logic_vector(15 downto 0) ); end entity program_rom;
-- ----------------------------------------------------------------------------- Program ROM architecture -- ---------------------------------------------------------------------------architecture program_rom of program_rom is type ROM_t is array(0 to 255) of std_logic_vector(15 downto 0); constant ROM : ROM_t := ( --addr code mnemonic "0000000000001000", -- 000 0x0008 MOV R0, 8 "0000000100000001", -- 001 0x0101 MOV R1, 1 "0000111110010100", -- 002 0x0F94 MOV R15, 148 "1100000111111011", -- 003 0xC1FB OUT R1, R15 "1101000100000110", -- 004 0xD106 SL0 R1 "0110000000000001", -- 005 0x6001 SUB R0, 1 "1110100000000011", -- 006 0xE803 JMP nz, 3 "1111000000000111", -- 007 0xF007 JMP 7 "0000000000000000", -- 008 0x0000 MOV R0, 0 "0000000000000000", -- 009 0x0000 MOV R0, 0 "0000000000000000", -- 010 0x0000 MOV R0, 0 "0000000000000000", -- 011 0x0000 MOV R0, 0 ... Zkráceno ... "0000000000000000", -- 253 0x0000 MOV R0, 0 "0000000000000000", -- 254 0x0000 MOV R0, 0 "0000000000000000" -- 255 0x0000 MOV R0, 0 ); begin -- ----------------------------------------------- Synchronous ROM data reading -- ---------------------------------------------ROM_p : process(clk) is begin if clk’event and clk = ’1’ then data <= ROM(conv_integer(address)); end if; end process ROM_p; end architecture program_rom; DONE