Čítač je fundamentálním obvodovým blokem nezbytným pro návrh většiny číslicových systémů. Blok čítače je v číslicových obvodech používán v řadě aplikací; nejtypičtějším použitím je čítač užitý jako časovač – blok, který počítá události (například náběžné hrany hodin) na svém vstupu. S čítačem se můžeme setkat ovšem i v jiných podobách, například jako s řadičem pro generování určité sekvence řídicích signálů, s generátorem adresy pro paměť, či čítačem instrukcí programu v procesoru (Program Counter). Blok čítače můžeme chápat i jako zjednodušený stavový automat, je-li doplněn o převodník stavu čítače na požadované výstupní signály automatu. Vlastní převodník přitom může degenerovat i na jednoduché „dráty“ mezi výstupem čítače a řídicími signály, je-li stav čítače vhodně kódován. Příklad užití čítače jako generátoru sekvence signálů lze nalézt na obrázku 1.
Obr. 1 Čítač užitý jako stavový automat pro generování sekvence signálů. V levé části obrázku vidíme příklady časových průběhů hodnot logických signálů na vstupu čítače a na jeho výstupu (hodinový signál clk, resetovací vstup res a výstupy čítače cnt(0) a cnt(1)) spolu s požadovanými průběhy generovaných signálů – ctrl1, 2 a 3. V pravé části je pak rozkresleno schéma čítače spolu s dekodérem pro generování příslušných signálů.
Základní podoba binárního čítače je velmi dobře známá a probíraná ve všech kurzech číslicového návrhu a zmiňovaná i každou knihou zaměřenou na návrh číslicových obvodů, viz například [1], kapitola 2. Už méně známé a zmiňované jsou alternativní možnosti implementace čítače spolu s jeho vlastnostmi; to je překvapivé vzhledem k fundamentálnímu významu bloku čítače pro číslicové systémy.
Potřebujeme-li navrhnout čítač pro konkrétní aplikaci, základní vlastnost, kterou je třeba vzít v úvahu, je kódování stavu čítače. Kódování stavu ovlivňuje téměř všechny parametry výsledného návrhu (velikost, maximální pracovní hodinovou frekvenci, spotřebu elektrické energie a počet současně se měnících bitů na sběrnici na výstupu čítače – maximální Hammingovu vzdálenost [2] dvou sousedních stavů čítače MHVS). Vhodná volba kódování je klíčovým rozhodnutím a právě jí je věnován náš příspěvek. Dalším důležitým kritériem pro návrh čítače je volba mezi synchronním čítačem (synchronous counter, všechny registry klopí ve stejný okamžik), nebo asynchronním čítačem (ripple counter, registry klopí postupně). Základní rozdíly mezi oběma čítači a argumenty pro volbu jednoho či druhého postupu budou také diskutovány v textu.
Jako příklad je v textu užit jednoduchý blok čítače procházejícího osmi stavy; pro jednotlivé alternativy je prezentován i VHDL kód návrhu a implementační parametry. Ty jsou pak v závěru článku shrnuty a vzájemně srovnány. Pro simulaci i implementaci bylo použito volně dostupné návrhové prostředí ISE Webpack [3]; jednotlivé bloky byly implementovány do obvodu xc5vlx30-3ff324. Začínající návrháři mohou nalézt detailní návod jak pracovat s návrhovým prostředím v knize [1], kapitola 2.
V celém textu označujeme počet registrů udržujících stav čítače jako N, počet stavů jako Ns. Jako fclk_maxoznačujeme maximální dosažitelnou pracovní frekvenci čítače, Tclk_min= 1/fclk_maxje pak minimální perioda hodinového cyklu.
Řešení popsaná v textu jsou použitelná jak při návrhu číslicových obvodů na programovatelných hradlových polích, tak při návrhu zákaznických integrovaných obvodů. Pro jednotlivé možnosti implementace číslicové logiky uvádíme v závěru seriálu kromě odhadů velikosti vlastní logiky a délky kritické cesty v čítači i méně často diskutovanou vlastnost – stručnou analýzu možností „ruční editace“ návrhu čítače v případě nutnosti provedení tzv. ECO úpravy (Engineering Change Order, viz [4]) už vyráběného integrovaného obvodu.
Příspěvek je z prostorových důvodů rozdělen do několika dílů; v tomto příspěvku bude prezentována plná implementace binárního čítače spolu se synchronním binárním a Johnsonovým čítačem. V dalším příspěvku potom ukážeme implementaci Grayova čítače, čítače v kódu 1 z N a binárního asynchronního čítače spolu se závěrečným shrnutím jejich vlastností.
Před rozborem jednotlivých alternativ implementace čítačů nejprve shrňme základní funkce, které může čítač mít; spolu s výkladem nechť čtenář sleduje výpis v příkladu 1:
LIBRARY IEEE; USE IEEE.std_logic_1164.ALL; USE IEEE.numeric_std.ALL; ENTITY binary_counter IS GENERIC ( N : natural := 10 ); PORT ( async_res : IN std_logic; async_ld : IN std_logic; clk : IN std_logic; sync_res : IN std_logic; sync_ld : IN std_logic; ld_val : IN std_logic_vector (N-1 DOWNTO 0); cnt_en : IN std_logic; cnt_up : IN std_logic; cnt_top : IN std_logic_vector (N-1 DOWNTO 0); cnt_max : OUT std_logic; cnt_zero : OUT std_logic; cnt_out : OUT std_logic_vector (N-1 DOWNTO 0) ); END ENTITY binary_counter; ARCHITECTURE rtl OF binary_counter IS SIGNAL cnt_nx : unsigned (N-1 DOWNTO 0); SIGNAL cnt_d : unsigned (N-1 DOWNTO 0); SIGNAL cnt_q : unsigned (N-1 DOWNTO 0); SIGNAL cnt_max_i : std_logic; SIGNAL cnt_zero_i : std_logic; SIGNAL increment : std_logic; SIGNAL decrement : std_logic; BEGIN increment <= '1' WHEN cnt_en = '1' AND cnt_up = '1' ELSE '0'; decrement <= '1' WHEN cnt_en = '1' AND cnt_up = '0' ELSE '0'; cnt_nx <= cnt_q+1 WHEN increment = '1' ELSE cnt_q-1 WHEN decrement = '1' ELSE cnt_q; cnt_max_i <= '1' WHEN cnt_q >= unsigned(cnt_top) ELSE '0'; cnt_zero_i <= '1' WHEN to_integer(cnt_q) = 0 ELSE '0'; cnt_d <= (OTHERS => '0') WHEN sync_res = '1' ELSE --synchronni reset ma prioritu pred citanim unsigned(ld_val) WHEN sync_ld = '1' ELSE --synchronni preload ma prioritu pred citanim (OTHERS => '0') WHEN cnt_max_i = '1' AND increment = '1' ELSE --preteceni pri citani nahoru unsigned(cnt_top) WHEN cnt_zero_i = '1' AND decrement = '1' ELSE --podteceni pro citani dolu cnt_nx; reg_cnt : PROCESS (clk, async_res, async_ld, ld_val) BEGIN IF async_res='1' THEN cnt_q <= (OTHERS => '0'); ELSIF async_ld='1' THEN cnt_q <= unsigned(ld_val); ELSIF clk'EVENT AND clk='1' THEN cnt_q <= cnt_d; END IF; END PROCESS reg_cnt; cnt_max <= cnt_max_i; cnt_zero <= cnt_zero_i; cnt_out <= std_logic_vector(cnt_q); END ARCHITECTURE rtl;
Příklad 1: Maximalistická verze binárního čítače se všemi běžnými funkcemi.
Binární kód je všem návrhářům dobře známý. Jeho výhodou je minimální počet registrů nutných pro implementaci čítače procházejícího Ns stavy, N=ceil (log2(Ns)), kde ceil je funkce zaokrouhlující svůj argument nahoru na celé jednotky. Dále je snadné provádět nad výstupem binárního čítače nejrůznější aritmetické operace (například porovnávání – >,< atd.), jejich implementace v binárním kódu je velmi intuitivní a běžně používaná. Výhodou je i to, že čítač čítající v binárním kódu může mít libovolný počet stavů. Nevýhodou synchronního binárního čítače je potřeba většího množství kombinační logiky pro implementaci sčítačky pro generování dalšího stavu a větší zpoždění v této logice, které omezuje fclk_max. Poslední významnější nevýhodou je skutečnost, že sousední stavy binárního čítače se mohou lišit až v N bitech (MHVS je N); kombinační logická funkce, která by dekódovala stavy čítače a generovala podle nich příslušné výstupy jako např. blok decoder na obrázku 1, pak bude téměř jistě produkovat statické či dynamické hazardy (glitche) na svém výstupu.
Pokud bychom chtěli upravovat čítač pomocí ECO změny, můžeme chtít buď jen zvýšit počet stavů (přidat další stavy na konec sekvence), nebo sekvenci „rozšířit“ vložením nových stavů „doprostřed“. Kombinační logická funkce binárního čítače patří mezi složitější z pohledu ECO změn; většinou lze prodloužit sekvenci přidáním dalších stavů, ale není možné vložit stavy dovnitř do sekvence čítače (čítač by pak už nečítal v binárním kódu).
Příklad 2 uvedený níže demonstruje už jen zjednodušenou verzi čítače z příkladu 1, sekvenci stavů demonstruje obrázek 2.
Obr. 2 Příklad běhu čítače, sekvence stavů 000, 001, 010, 011, 100, 101, 110, 111.
Příklad 2: Binární čítač – RTL kód a schéma LIBRARY IEEE; USE IEEE.std_logic_1164.ALL; USE IEEE.numeric_std.ALL; ENTITY cnt IS GENERIC ( N : natural := 4 ); PORT ( res : IN std_logic; clk : IN std_logic; cnt_out : OUT std_logic_vector (N-1 DOWNTO 0) ); END ENTITY cnt; ARCHITECTURE rtl_bin OF cnt IS SIGNAL cnt_d : unsigned (N-1 DOWNTO 0); SIGNAL cnt_q : unsigned (N-1 DOWNTO 0); BEGIN reg_cnt : PROCESS (clk, res) BEGIN IF res=′1′ THEN cnt_q <= (OTHERS => ′0′); ELSIF clk‘EVENT AND clk=′1′ THEN cnt_q <= cnt_d; END IF; END PROCESS reg_cnt; cnt_d <= cnt_q + 1; cnt_out <= std_logic_vector(cnt_q); END ARCHITECTURE rtl_bin;
Stav a výstup Johnsonova čítače je kódován v Johnsonově nebo tzv. plazivém kódu. Kód se konstruuje jednoduše – první stav je zakódován samými nulami; pro další stavy se stavový registr posouvá vlevo a zprava nasouvá jednička až do okamžiku, kdy jsou všechny registry nastavené do log. 1. Potom se celý proces opakuje s tím rozdílem, že zprava nasouváme log. 0 až do okamžiku, kdy jsou všechny registry stavu čítače nastavené do log. 0. Pak celý postup opakujeme. Příklad sekvence v Johnsonově kódu je uveden níže v obrázku 3.
Obr. 3 Příklad běhu čítače, sekvence stavů je 0000, 0001, 0011, 0111, 1111, 1110, 1100, 1000.
Příklad 3: Johnsonův čítač – RTL kód a schéma ARCHITECTURE rtl_johnson OF cnt IS SIGNAL cnt_d : std_logic_vector (N-1 DOWNTO 0); SIGNAL cnt_q : std_logic_vector (N-1 DOWNTO 0); BEGIN reg_cnt : PROCESS (clk, res) BEGIN IF res=′1′ THEN cnt_q <= (OTHERS => ′0′); ELSIF clk‘EVENT AND clk=′1′ THEN cnt_q <= cnt_d; END IF; END PROCESS reg_cnt; cnt_d <= cnt_q(N-2 DOWNTO 0)&NOT(cnt_q(N-1)); cnt_out <= cnt_q; END ARCHITECTURE rtl_johnson;
Výhodou Johnsonova kódu je to, že MHVS je jen 1, proto je Johnsonův čítač často používán například v generátorech hodinových signálů (navazující kombinační logika bloku decoder generující vlastní hodinové signály má za dodržení dalších dodatečných podmínek výstup bez statických i dynamických hazardů). Nevýhodou je, že počet registrů udržujících stav čítače je zde N=Ns/2; dále je možná jen implementace čítače o sudém počtu stavů. Všimněme si zde, že mezi registry čítače v podstatě není žádná kombinační logika, jen jeden invertorm který invertuje výstup z posledního registru a vytváří „plazivou“ sekvenci nul a jedniček. To umožňuje dosáhnout vysoké hodnoty fclk_max. Do čítače implementovaného jako Johnsonův lze proto snadno vložit pomocí ECO změny další stavy a prodloužit tak sekvenci čítání.
[1] Jakub Šťastný. FPGA Prakticky, BEN Praha 2011.
[2] Hamming Distance. http://en.eikipedia.org/wiki/Hamming_distance
[3] ISE WebPack. http://www.xilinx.com/tools/webpack.htm
[4] Engineering Change Order. http://en.wikipedia.org/wiki/Engineering_Change_Order
[5] Steve Golson. One-hot state machine design for FPGAs, 3rd PLD Design conference, Santa Clara, 1993.
[6] Steve Golson. State machine design techniques for Verilog and VHDL, 1994.