Ach, ta paměť…

Klopné obvody od A do Z… vlastně „od D do T“… a k tomu taky něco o tom, jak si ve VHDL vytvořit paměť RAM i ROM.

Klopné obvody snad není potřeba představovat nikomu, kdo se v elektronice dostal za kapitolu 3 – Základní hradla. Dokonce i ve stavebnici Logitronik 01 se čtyřmi NAND byl klopný obvod R-S už někde v půlce návodu. I já jsem tu už některé typy představil, včetně obvodu D. Pojďme ale systematičtěji:

Klopné obvody

R-S

Nejjednodušší klopný obvod s dvěma asynchronními vstupy R a S a výstupem Q (u všech klopných obvodů bývá k dispozici i negovaný výstup /Q, ale tady ho, jak se říká, zanedbám). Poskládáte si ho ze dvou hradel NAND či NOR (anebo ze dvou tranzistorů…), kde výstup jednoho je zaveden jako vstup druhého a vice versa. Obecně platí, že log. 1 na vstupu R překlopí obvod do stavu 0, log. 1 na vstupu S překlopí obvod do stavu 1 a pokud jsou oba vstupy v log. 0, obvod zůstává v tom stavu, v jakém byl předtím. 1 na obou vstupech uvede obvod do nedefinovaného stavu.

R-S s povolovacím vstupem

K předchozímu typu přidává další vstup E (Enable). Když je tento vstup v log. 0, jsou vstupy R a S „odpojeny“ a jejich změna se na stavu obvodu nijak neprojeví. Pokud je E v log. 1, obvod funguje jako výše uvedený.

 

Kód je jen mírně upravený předchozí:

D

Hazardní stav, kdy R = S = 1, můžeme eliminovat pomocí sloučení vstupů R a S u předchozího typu klopného obvodu do jednoho vstupu D (Data), který je připojen přímo na S a přes invertor na R. Tím zajistíme, že vstupy R, S budou vždy v jednom stavu (0,1) nebo (1,0). Pomocí E pak určujeme, jestli se zapisuje nový stav (1), nebo jestli si obvod pamatuje předchozí (0). Výsledný klopný obvod nazýváme „D“ (v anglické literatuře DFF z „D Flip Flop“). Vstup E bývá označen jako C (Clock).

D s asynchronními vstupy

Existuje „mutace“ výše uvedeného obvodu, kdy k obvodu D, který je synchronní (tj. řízený hodinovým vstupem) přidáme asynchronní vstupy R, S – tedy takové, které nulují či nastavují obvod bez ohledu na stav hodinového vstupu C.

Dělička

Pokud na vstup D připojíme negovaný výstup Q, získáme na výstupu výstup s frekvencí, která odpovídá poloviční frekvenci, přivedené na vstup C.

T

Klopný obvod T vznikne podobně jako předchozí dělička tím, že zavedeme negovaný výstup zpět na vstup D, tentokrát ale přes „povolovací“ hradlo AND (D <= not Q AND T). Pokud je T=0, pamatuje si klopný obvod poslední stav, pokud je 1, tak se s každým pulsem hodin překlopí.

I k tomuto klopnému obvodu lze připojit synchronní či asynchronní vstupy pro nastavení / nulování.

Paměť

Paměť jako elektronický prvek bývá implementována pomocí klopných obvodů (statická RAM či registry). Ve VHDL není potřeba vytvářet paměť takto složitě, stačí použít prosté:

Nejprve deklarujeme typ ram_t, který představuje 256bytovou paměť RAM („pole osmibitových hodnot“), no a na druhém řádku vytvoříme jeho fyzickou reprezentaci – signál „ram“ bude mít hodně daleko do běžné představy „signálu“ coby datového vodiče, ale nebojte, za chvilku si ho „schováme“ do komponenty s běžnějším rozhraním. Všimněte si konstrukce s „others“. Zmiňoval jsem se, že tato konstrukce nastaví „všechny ostatní hodnoty, nezadané explicitně“ na určitou hodnotu. Tady nastavuje 256 položek na hodnotu „8 nul“.

V elektronice rozlišujeme dva základní typy pamětí: ROM (Read Only Memory) a RAM (Random Access Memory, přesněji: RWM – Read/Write Memory). Paměť ROM má vstupní adresovou sběrnici, výstupní datovou a vybavovací vstup (CE – Chip Enable, OE – Output Enable apod.) Paměť RAM má rovněž vstupní adresovou sběrnici, vstupní a výstupní datovou (někdy spojenou do obousměrné), vybavovací vstup a vstupní signál pro zápis hodnoty.

Obousměrná sběrnice

Ve VHDL můžeme relativně snadno implementovat obousměrnou třístavovou sběrnici. Při výběru směru v části PORT zadáme jako typ „INOUT“ – signál se od té chvíle chová jako vstup (tedy lze jej přiřadit jiným signálům), i jako výstup (tedy lze jemu přiřadit hodnotu).

Představme si jednobitový obousměrný vstup / výstup D. Pokud je řídicí signál E roven 1, vystupuje na tento výstup signál A. Pokud je řídicí signál E ve stavu 0, je signál D nastaven jako vstupní a jeho hodnota se propisuje do signálu Q. Nějak takto:

Block1

Kód pro takový obvod bude jednoduchý – využijeme možnosti přiřadit výstupu hodnotu „Z“ (vysoká impedance)

Paměti RAM (RWM)

RAM si můžeme představit jako blok klopných obvodů typu D. Adresová sběrnice je zapojena na dekodér 1-na-N a každý výstup ovládá jeden klopný obvod. Pokud je dán požadavek zápisu, jsou vstupní data přivedena na vstup D daného klopného obvodu a jsou zapsána pulsem na hodinovém vstupu. Čtení dat probíhá obdobně – z výstupů všech klopných obvodů je multiplexerem vybrán požadovaný údaj (podle adresy).

Paměť RAM může být ve VHDL jednoportová či dvouportová (částečně nebo plně). Jednoportová RAM má jednu adresovou sběrnici a jeden vstup, určující, jestli se bude číst, nebo zapisovat. Může mít oddělenou vstupní a výstupní datovou sběrnici, nebo ji může mít obousměrnou. Částečně dvouportová paměť má adresovou + datovou sběrnici pro zápis a samostatnou adresovou + datovou sběrnici pro čtení. Plně dvouportová paměť má dvě nezávislé sady kompletních vývodů (data, adresa, řídicí signály), a znamená to, že v jeden okamžik mohou přistupovat dva různé obvody k téže paměťové matici s různými požadavky (zápis, čtení, z různých adres, nebo i ze stejných – zde pozor, při konkurenčním zápisu na stejnou adresu není výsledek zaručený, pokud nemá paměť definované priority vstupů).

V obvodech FPGA bývají speciálně vyhrazené bloky pamětí – právě do nich bývají alokována velká bitová pole. Jejich počet, velikost a organizace záleží na výrobci a typu. Pro zajímavost si popíšeme, jak je implementována paměť v obvodech Cyclone II (použitý v doporučeném začátečnickém kitu).

Cyclone II obsahují bloky paměti, nazývané M4K (Memory 4K), což je dvouportová paměť s velikostí 4608 bitů včetně paritních. Každý takový blok je možno organizovat do bloku 4Kx1bit, 2Kx2, 1Kx4, 512×8, 512×9, 256×16, 256×18, 128×32 nebo 128×36 bitů. Máme tedy půl kilobyte paměti.

Různé obvody z řady Cyclone II obsahují různé množství M4K bloků:

Typ Počet bloků Kapacita (bity) Kapacita (kB)
EP2C5 26 119808 13
EP2C8 36 165888 18
EP2C15 52 239616 26
EP2C20 52 239616 26
EP2C35 105 483840 52,5
EP2C50 129 594432 64,5
EP2C70 250 1152000 125

Kapacita v kB se bere při organizaci po osmi bitech.

Cyclone IV obsahují bloky paměti, nazývané M9K (Memory 9K), což je dvouportová paměť s velikostí 9216 bitů včetně paritních. Každý takový blok je možno organizovat do bloku 8Kx1bit, 4Kx2, 2Kx4, 1Kx8, 1Kx9, 512×16, 512×18, 256×32 nebo 256×36 bitů. Máme tedy jeden kilobyte paměti.

Různé obvody z řady Cyclone II obsahují různé množství M9K bloků:

Typ Počet bloků Kapacita (Kbits) Kapacita (kB)
EP4CE6 30 270 30
EP4CE10 46 414 46
EP4CE15 56 504 56
EP4CE22 66 594 66
EP4CE30 66 594 66
EP4CE40 126 1134 126
EP4CE55 260 2340 260
EP4CE75 305 2745 305
EP4CE115 432 3888 432

Kapacita v kB se bere při organizaci po osmi bitech.

Kromě vyhrazených bloků paměti (proto též „bloková paměť“) lze ve FPGA vytvořit tzv. „distribuovanou paměť“. Každá základní stavební buňka FPGA obsahuje několik klopných obvodů, které mohou sloužit k zapamatování dat, a syntetizér VHDL dokáže tyto obvody použít pro vytvoření paměti (ovšem takto použité buňky nelze už použít pro jiný účel). Distribuovaná paměť se proto hodí pro malé paměti. Bloková paměť je vhodnější pro větší paměti, jen je třeba počítat s její granularitou, tj. přiděluje se vždy po blocích. Jinými slovy – jedna stobytová paměť zabere jeden blok, sto jednobytových zabere sto bloků.

To, jestli se má vektor dat implementovat do blokové, nebo do distribuované paměti, rozhoduje syntetizér – většinou podle velikosti bloku dat a podle dalších indicií, např. zda je čtení i zápis synchronní. Ukažme si půlkilobytovou paměť RAM (8 bitů) s oddělenými vstupními a výstupními daty a se dvěma řídicími signály – CE (Chip Enable, operace probíhají při náběžné hraně CE) a WE (1 = zapisuje se, 0 = nezapisuje se).

Na kódu není nic záludného nebo neznámého, s výjimkou atributu ramstyle (pro Xilinx ram_style). Pomocí tohoto atributu můžeme explicitně určit, do jaké paměti se bude daný signál umisťovat. U mého testovacího kódu se vybere automaticky paměť M4K a výsledek je:

Pro zajímavost, pokud vynutím umístění 512B paměti do distribuované (v názvosloví Altery to je „logic“), výsledek se radikálně změní (kromě toho, že samotné zpracování bude mnohonásobně delší):

Vidíte, že tentokrát prosté pole 512 položek po 8 bitech, tedy 4096 bitů, vedlo k obsazení více než sedmi tisíc logických elementů, z toho 4104 paměťových a přes 3000 kombinačních. Proto vždy dbejte, aby se velká pole mohla umisťovat do blokové paměti, která je k podobným věcem určena.

Pozor! I když použijete správný atribut, tak se může stát, že váš vektor bude umístěn do distribuované paměti. Snadno taková situace vznikne, když použijete asynchronní čtení. Kdybychom ve výše uvedeném kódu přenesli řádek

 

mimo synchronní část (tedy tu, která je ovládána vzestupnou hranou signálu CE), detekuje jej syntetizér jako možné „čtení během zápisu“, a takový přístup implementuje v distribuované paměti, protože bloková jej neumožňuje.

Zlatá pravidla tedy zní: Malé vektory klidně v distribuované, velké se snažte umístit do blokové; přečtěte si dokumentaci ke konkrétnímu obvodu, abyste věděli, jak se paměť nazývá a jaké má možnosti; do blokové paměti zásadně synchronně.

Pro zajímavost – varianta s obousměrnou datovou sběrnicí:

Paměť ROM

Pro nejrůznější dekódovací tabulky, složitou kombinatoriku nebo třeba mikrokód využijeme paměť ROM. Ve FPGA nemáme nic jako ROM k dispozici, veškerá paměť je RAM, a tak si funkcionalitu ROM simulujeme tím, že neaktivujeme možnost zápisu. Na druhou stranu to znamená, že data musíme zadat nějak jinak.

U malých bloků to není problém udělat prostým přiřazením ve zdrojovém kódu:

Pokud je paměť větší, byl by takový zápis krajně nepraktický. V takovém případě použijeme možnost zadat data v externím souboru. Altera používá formát MIF (Memory Initialization File), Xilinx používá COE, ale vývojové nástroje obou výrobců dokážou zpracovat Intel HEX file. To je tedy pravděpodobně nejvhodnější varianta… Teda – byla by, pokud by zrovna například Altera nevyžadovala HEX v prapodivném formátu „4 byty na řádek“… Naštěstí existuje řešení!

Hotové komponenty

Vývojové prostředí Quartus obsahuje knihovnu hotových komponent, uzpůsobených na míru konkrétním FPGA od Altery. Obdobnou knihovnu nabízí i Xilinx a další výrobci. V této knihovně najdete spoustu „vysokoúrovňových“ obvodů, jako jsou např. PLL, standardní rozhraní (PCIe, Ethernet, DisplayPort, …), aritmetické obvody (násobičky, děličky,  sčítačky) nebo právě paměti. U Altery můžeme tyto obvody využít tak, že je začleníme do architektury a vhodně nastavíme GENERIC MAP a PORT MAP. Viz popis obvodů ze základní knihovny LPM.

Zde jiná šířka .HEX souboru nevadí, syntetizér si s ní poradí. Všimněte si použití knihovny lpm, vytvoření instance entity „lpm_rom“ a nastavení základních parametrů (šířka adresové sběrnice, datové sběrnice, soubor pro inicializaci atd.) Výsledkem je správně přeložená paměť ROM, umístěná v blokové paměti a po startu FPGA inicializovaná obsahem příslušného HEX souboru (ten se stane součástí konfigurace, nahrávané do konfigurační FLASH).

Knihovna hotových komponent se v IDE Quartus II skrývá pod skromným označením Megafunctions. Nejjednodušší způsob, jak s nimi pracovat, je využít MegaWizard Plug-In Manager (menu Tools). Zde si vyberete požadovanou komponentu, nastavíte její vlastnosti a necháte vygenerovat. Používáte ji pak jako jakoukoli jinou komponentu (průvodce vám vygeneruje i vzorový kód). Obdobné nástroje existují i pro FPGA od Xilinx.

Možná trochu překvapivým závěrem této kapitoly je: Pokud chcete použít standardní typ paměti, netvořte ji ručně, ale použijte tu, kterou nabízí vaše vývojové prostředí!