.. _w03-ram: ================================== WykĹad 3: RAM w ukĹadach cyfrowych ================================== Data: 03.11.2020 .. toctree:: .. contents:: Czym jest RAM ============= RAM jest ogĂłlnÄ nazwÄ na pamiÄÄ o dowolnym dostÄpie â typ pamiÄci, w ktĂłrej moĹźemy we w miarÄ podobnym czasie dostaÄ siÄ do dowolnej komĂłrki (czyli jÄ odczytaÄ lub zapisaÄ). Jest to tak naprawdÄ niezbyt dobra definicja (moĹźna by siÄ kĹĂłciÄ, Ĺźe DRAM tak naprawdÄ nie pasuje do tej definicji). RAMem bÄdziemy okreĹlaÄ tablicÄ bitĂłw, ktĂłra ma: - szerokoĹÄ: ile bitĂłw ma jednostka pamiÄci, ktĂłrÄ czytamy/piszemy - gĹÄbokoĹÄ: ile róşnych adresĂłw moĹźemy wybraÄ do odczytu/zapisu - rozmiar: szerokoĹÄ Ă gĹÄbokoĹÄ - porty odczytu: zbiory sygnaĹĂłw, ktĂłrych uĹźywamy do odczytu zawartoĹci RAMu. PamiÄÄ moĹźe mieÄ jeden lub wiÄcej niezaleĹźnych portĂłw odczytu. - porty zapisu: zbiory sygnaĹĂłw, ktĂłrych uĹźywamy do zapisu zawartoĹci RAMu. - porty odczytu i zapisu (robiÄ ce jedno i/lub drugie pod tym samym adresem). RAM jest bardzo czÄsto przydatnym elementem w logice cyfrowej. W zaleĹźnoĹci od potrzeb stosuje siÄ róşne rozmiary i technologie RAMu. Te najwaĹźniejsze to: 1. Najprostszym rodzajem RAMu jest po prostu zespóŠprzerzutnikĂłw poĹÄ czonych przez multipleksery (do odczytu) i dekodery adresu zapisu. Szybko staje siÄ to jednak nieefektywne (szczegĂłlnie w przypadku logiki programowalnej). 2. SRAM (pamiÄÄ statyczna): tablica bitĂłw zrealizowanych jako tzw. zatrzaski (po 6 tranzystorĂłw kaĹźdy w najczÄstszej realizacji), zorganizowana fizycznie w wiersze i kolumny. PamiÄÄ ta sama podtrzymuje swĂłj stan dopĂłki ma zasilanie i moĹźe w dowolnym momencie przeczytaÄ lub zapisaÄ dowolny bit. PamiÄÄ SRAM moĹźe byÄ osadzona w ukĹadzie logicznym (jeĹli jest maĹa), bÄ dĹş umieszczona w osobnym ukĹadzie scalonym (jeĹli jest duĹźa). W przypadku ukĹadĂłw ASIC, moĹźemy zaprojektowaÄ pamiÄÄ o doĹÄ dowolnych parametrach. W przypadku ukĹadĂłw FPGA, mamy dostÄpne gotowe bloki pamiÄci o z gĂłry ustalonych rozmiarach, zazwyczaj podzelonych na kilka rodzajĂłw: - LUT RAM albo distributed RAM: maĹe (rzÄdu dziesiÄ tek bÄ dĹş setek bitĂłw), sÄ nieco przerobionymi LUTami do ktĂłrych dorobiona zostaĹa funkcjonalnoĹÄ zapisu. Zazwyczaj majÄ jeden port synchroniczny port zapisu i jeden lub wiÄcej asynchroniczny port odczytu. JeĹli nie sÄ akurat uĹźywane jako pamiÄÄ, mogÄ sĹuĹźyÄ za zwykĹe LUTy. - block RAM: Ĺredniego rozmiaru (rzÄdu dziesiÄ tek kilobitĂłw). Dedykowany blok. Zazwyczaj majÄ dwa niezaleĹźne synchroniczne porty odczytu+zapisu. - (czasem w niektĂłrych FPGA) ultra RAM: jak block RAM, ale wiÄksze (rzÄdu setek kilobitĂłw). UkĹady FPGA (w przeciwieĹstwie do ASIC) zazwyczaj dajÄ nam moĹźliwoĹÄ wyboru stanu poczÄ tkowego pamiÄci (w szczegĂłlnoĹci moĹźemy nie uĹźywaÄ Ĺźadnych portĂłw zapisu i traktowaÄ pamiÄÄ jako ROM). JeĹźeli bloki pamiÄci dostÄpne w FPGA sÄ dla nas za maĹe, moĹźna poĹÄ czyÄ wiele z nich w jednÄ pamiÄÄ. 3. DRAM (pamiÄÄ dynamiczna): tablica bitĂłw zrealizowanych w najtaĹszy moĹźliwy sposĂłb: kaĹźdy bit to jeden tranzystor i jeden kondensator. RĂłwnieĹź fizycznie zrealizowana w wiersze i kolumny. Ze wzglÄdu na sposĂłb realizacji, pamiÄÄ ta caĹy czas powoli zapomina swĂłj stan, nawet gdy jest zasilana â gwarantowany czas przechowywania informacji to tylko 64ms. Co wiÄcej, dostÄp jest bardzo skomplikowany â trzeba najpierw przeczytaÄ caĹy wiersz pamiÄci do specjalnego bufora (co zajmuje trochÄ czasu) przed przeczytaniem/odczytaniem danej komĂłrki, a nastÄpnie odĹoĹźyÄ go z powrotem (co rĂłwnieĹź zajmuje czas). UĹźycie takiej pamiÄci wymaga skomplikowanego kontrolera pamiÄci, ktĂłry bÄdzie pilnowaĹ wykonania wszystkich czynnoĹci w odpowiedniej sekwencji i z odpowiednimi odstÄpami czasowymi, a takĹźe zapewniaĹ odĹwieĹźanie pamiÄci â regularne czytanie i zapisywanie z powrotem kaĹźdego wiersza pamiÄci, by utrzymaÄ jej stan. PoniewaĹź efektywna konstrukcja pamiÄci DRAM wymaga trochÄ innego procesu niĹź efektywna konstrukcja logiki (i ze wzglÄdu na jej rozmiar), pamiÄÄ DRAM jest zawsze osobnym ukĹadem. Kontroler pamiÄci znajduje siÄ jednak razem z logikÄ . W przypadku FPGA, kontroler pamiÄci realizuje siÄ zazwyczaj uĹźywajÄ c programowalnej logiki (wyjÄ tkiem sÄ FPGA z wbudowanym kontrolerem pamiÄci, w tym te z wbudowanym procesorem jak Zynq). RAM osadzony w ukĹadach cyfrowych ================================= Aby uĹźyÄ osadzonego RAMu w FPGA, moĹźemy: 1. PrzeczytaÄ dokumentacjÄ FPGA, wybraÄ odpowiedni rodzaj RAMu i zinstancjonowaÄ rÄcznie dany prymityw, bÄ dĹş 2. OpisaÄ wymiary i funkcjonalnoĹÄ poĹźÄ danego RAMu w jÄzyku opisu sprzÄtu i zdaÄ siÄ na automatyczny wybĂłr przez narzÄdzia syntezy. Automatyczny wybĂłr jest oczywiĹcie prostszy, lecz czÄsto ma ograniczone moĹźliwoĹci â prymitywy dostÄpne w FPGA miewajÄ wiele funkcji ciÄĹźkich do opisania w jÄzyku sprzÄtu i ich uĹźycie w ten sposĂłb bÄdzie niemoĹźliwe (np. funkcje ECC czy hardware FIFO). Opis RAMu w nMigen ------------------ Aby opisaÄ swĂłj RAM w nMigen, najpierw tworzymy obiekt typu ``Memory`` z odpowiednimi wymiarami i danymi poczÄ tkowymi, a nastÄpnie wywoĹujemy jego metody ``read_port`` i ``write_port``, by stworzyÄ jego porty, i dodajemy je jako podmoduĹy naszego moduĹu:: m = Module() ... # PamiÄÄ o 16 komĂłrkach, kaĹźda komĂłrka ma 8 bitĂłw. Dane poczÄ tkowe # to 0x12, 0x33, 0x44, 0, 0, ..., 0. mem = Memory(width=8, depth=16, init=[0x12, 0x33, 0x44]) # Tworzymy synchroniczny port odczytu. rdport = m.submodules.rdport = mem.read_port() raddr = Signal(4) rdata = Signal(8) m.d.comb += [ # podĹÄ czamy adres z ktĂłrego ma czytaÄ (gdzieĹ powinno byÄ przypisanie do raddr) rdport.addr.eq(raddr), # podĹÄ czamy dane rdata.eq(rdport.data), # [opcjonalnie] podĹÄ czamy sygnaĹ enable â gdy ten sygnaĹ bÄdzie rĂłwny 0, # port nie bÄdzie pracowaĹ (wartoĹÄ rdata siÄ nie zmieni). JeĹli go nie # podĹÄ czymy, domyĹlnie bÄdzie to zawsze 1. rdport.en.eq(coĹtam == coĹ), ] # Tworzymy synchroniczny port zapisu. wrport = m.submodules.wrport = mem.write_port() waddr = Signal(4) wdata = Signal(8) wen = Signal() m.d.comb += [ wrport.addr.eq(waddr), wrport.data.eq(wdata), # Gdy ten sygnaĹ bÄdzie rĂłwny 1 na zboczu zegara, nastÄ pi zapis danych # wdata pod adres waddr. Gdy bÄdzie rĂłwny 0, nic siÄ nie stanie. wrport.en.eq(wen), ] JeĹli zamiast tego chcielibyĹmy mieÄ asynchroniczny port odczytu:: # Tworzymy asynchroniczny port odczytu. rdport = m.submodules.rdport = mem.read_port(domain='comb') raddr = Signal(4) rdata = Signal(8) m.d.comb += [ # podĹÄ czamy adres z ktĂłrego ma czytaÄ (gdzieĹ powinno byÄ przypisanie do raddr) rdport.addr.eq(raddr), # podĹÄ czamy dane rdata.eq(rdport.data), # w asynchronicznych portach odczytu nie wolno mieÄ sygnaĹu en â nie miaĹoby to sensu ] Czasem przydaje siÄ port zapisu z osobnymi sygnaĹami enable dla róşnych grup bitĂłw danych (w szczegĂłlnoĹci, osobno dla kaĹźdego bajtu w sĹowie):: mem = Memory(width=32, depth=0x1000) # Tworzymy synchroniczny port zapisu z osobnÄ kontrolÄ zapisu kaĹźdego bajtu. wrport = m.submodules.wrport = mem.write_port(granularity=8) waddr = Signal(12) wdata = Signal(32) wen = Signal(4) m.d.comb += [ wrport.addr.eq(waddr), wrport.data.eq(wdata), # bit 0 wen mĂłwi, czy zapisaÄ bity 0-7 wdata # bit 1 wen mĂłwi, czy zapisaÄ bity 8-15 wdata # bit 2 wen mĂłwi, czy zapisaÄ bity 16-23 wdata # bit 3 wen mĂłwi, czy zapisaÄ bity 24-31 wdata wrport.en.eq(wen), ] W nMigen nie moĹźemy bezpoĹrednio opisaÄ portu odczytu+zapisu â zamiast tego, tworzymy osobne porty odczytu i zapisu wspĂłĹdzielÄ ce sygnaĹ adresu. NarzÄdzia syntezy scalÄ je w jeden port, jeĹli bÄdzie to potrzebne do realizacji RAMu. Realizacja RAMĂłw w FPGA ----------------------- NaleĹźy pamiÄtaÄ, Ĺźe nasz opis RAMu musi byÄ realizowalny w docelowym ukĹadzie FPGA. Synteza sprĂłbuje wielu transformacji by mĂłc zrealizowaÄ nasz RAM, lecz nie zawsze bÄdzie to moĹźliwe: 1. SzerokoĹÄ RAMu zostanie dopasowana do szerokoĹci sprzÄtowej. JeĹli nasz sprzÄt ma tylko szerokoĹÄ 8, a prosimy o RAM o szerokoĹci 5, zostanie on odpowiednio rozszerzony (marnujÄ c â dostÄpnych bitĂłw). JeĹli prosimy o RAM o szerokoĹci 24, syntezator wykorzysta 3 rĂłwnolegĹe bloki sprzÄtu by poskĹadaÄ nasz RAM. 2. GĹÄbokoĹÄ RAMu rĂłwnieĹź zostanie dopasowana, marnujÄ c RAM jeĹli nasz RAM jest mniejszy od bloku, bÄ dĹş wykorzystujÄ c wiele blokĂłw (i odrobinÄ dodatkowej logiki sterujÄ cej) gdy nasz RAM jest wiÄkszy. 3. JeĹli nasze porty nie dajÄ siÄ zapakowaÄ do portĂłw bloku sprzÄtowego przez nadmiar portĂłw odczytu, synteza zduplikuje caĹy RAM dla kaĹźdego portu odczytu (bÄ dĹş grupy portĂłw odczytu). JeĹli nie da siÄ zapakowaÄ z innych powodĂłw (nadmiar portĂłw zapisu, brak moĹźliwoĹci poscalania portĂłw odczytu z portami zapisu, bÄ dĹş brak wymaganej funkcjonalnoĹci portĂłw), RAMu w ogĂłle nie da siÄ zrealizowaÄ. Zaleca siÄ przestrzegaÄ nastÄpujÄ cych reguĹ: 1. UĹźywamy maksymalnie jednego portu zapisu. 2. JeĹźeli pamiÄÄ jest maĹa: staramy siÄ minimalizowaÄ liczbÄ portĂłw odczytu. 3. JeĹźeli pamiÄÄ jest duĹźa (kilka kilobitĂłw i wiÄcej): minimalizujemy liczbÄ portĂłw odczytu, uĹźywamy tylko synchronicznych portĂłw odczytu. Do czego uĹźywany jest RAM ========================= PrzykĹadowe uĹźycia RAMu: 1. Plik rejestrĂłw procesora 2. Kolejka przychodzÄ cych danych / zleceĹ / pakietĂłw ... 3. Kod / pamiÄÄ operacyjna mikrokontrolera 4. PamiÄÄ cache 5. Tabele staĹych (np. stablicowane wartoĹci sinusa) 6. Tablica aktualnie przetwarzanych danych (czÄĹÄ mnoĹźonej macierzy, itp)