KrzysioCart Micro SD – rewolucja dla fanów konsoli Pegasus/Famicom

Mejs, nasz wierny kamrat, po przeczytaniu wiadomości o powstaniu polskiego devcarta, powiedział znamienne słowa: „(…)Podoba mi się bardzo ten opis, jak wieczorami rozważał różne sposoby. Samowystarczalny Pegasus w Polsce. Nawet jakby doszło do globalnej klęski, to Pegasus odrodziłby się w Polsce.” Takie słowa najlepiej oddają nasz zachwyt nad KrzysioCartem MicroSD. Oddajmy głos samemu konstruktorowi – Krzysiobalowi!

Przedstawiam Wam swoje najnowsze dzieło – KrzysioCart MicroSD – kardridż do konsoli Pegasus, który będzie ostatnim, jaki kupisz – żaden inny już nie będzie potrzebny. Kardridż obsługuję 82% wszystkich gier, jakie wyszły na konsolę NES / Famicom / Pegasus. Obsługiwane są następujące mappery (układy rozszerzające możliwości konsoli):

Częstość     | ID      | Nazwa             | Ilość   | % całości
występowania | mappera |                   | tytułów |
————-+———+——————-+———+———-
1            | 1       | MMC1              | 804     | 24,62
2            | 4       | MMC3              | 765     | 23,42
3            | 0       | NROM              | 434     | 13,29
4            | 2       | UNROM            | 336     | 10,29
5            | 3       | CNROM            | 236    | 7,23
6            | 7       | ANROM             | 60      | 1,84

17           | 71      | CAMERICA     |14      | 0,43

30           | 232    | CAMERICA Quattro  | 8       | 0,24

43           | 15      | 100-in-1/168-in-1 | 4       | 0,12

SUMA:                                                         81.48

Ponadto, tylko ten kardridż obsługuję słynną składankę 168-in-1, bez której żaden polski fan tej konsoli nie wyobrażałby sobie dziesiątek godzin spędzonych na graniu – to mój ukłon w stronę polskich użytkowników.

Dla niecierpliwych od razu dobra wiadomość – kardridż można zakupić ode mnie (zapraszam na koniec artykułu po szczegóły). W skład zestawu wchodzi:
– kardridż w obudowie
– karta pamięci MicroSD 4 GB
– czytnik kart MicroSD na USB, umożliwiający nagrywanie na kartę
Jest to więc wszystko, czego potrzebujesz.

Kardridż został przetestowany na na najpopularniejszych modelach konsol:
– MT777DX
– IQ502 rev2/3
– SP60 (na scalaku)
– RINCO (Thompsonic)
– dwa gluty no-name
Na każdej działa bezproblemowo. Dodatkowo, wbudowana jest w niego możliwość aktualizacji oprogramowania bezpośrednio z karty SD – wystarczy wskazać odpowiedni plik! Nikt inny nie wymyślił czegoś takiego w Europie, a ja sam jako drugi na świecie rozpocząłem pracę nad tą tematyką w 2012, kiedy to nikt jeszcze nie myślał zbyt wiele o flash-cartach!

Historia
Pierwsze poważne kroki w temacie Pegasusów postawiłem w roku 2012, kiedy to stworzyłem ogromnych rozmiarów kardridż, będący moją pracą magisterską. Było on oparty o moduł z układem Xilinx XC3S400 (Spartan 3). Kardridż w końcowej fazie obsługiwał ponad 95% tytułów, w tym nawet MMC5, VRC6. Jednak skomplikowana budowa sprawiła, że pozostał on jedynym egzemplarzem prototypowym.

Kilka lat później postanowiłem stworzyć trochę prostszy nośnik – oparty o układ Xilinx XC9572XL – 72 makrocele (więcej tutaj: http://www.elektroda.pl/rtvforum/topic3094839.html)

Początkowo miał on obsługiwać jedynie składankę 168-in-1 oraz gry ze Złotej 5 i 4 (mapper Camerica #71 oraz #232) i UNROM. Wszystkie te gry łączył fakt, że posiadają one 8 KB CHR-RAM. Później doszła obsługa gier NROM, polegająca na modyfikacji oprogramowania tak, aby po wybraniu gry, a przed przekazaniem jej sterowania, procesor nagrał do pamięci RAM grafikę z tej gry (NROM posiada 8kB pamięci ROM). Następnie zamieniając pamięć SRAM z 6264 (8 kB) na 62256 (32 kB) i podpinając dwie najwyższe linie adresowe, można było obsługiwać mapper CNROM. Kolejna lekka modyfikacja oprogramowana polegała na dodaniu obsługi MMC1 (ale tylko gry z 8 kB CHR-RAM). Dodając 8 kB PRG-RAM ($6000-$7FFF) można było jeszcze obsługiwać gry MMC1 z dodatkowym RAMem, np. Legends of Zelda, Dyna Blaster. Dodanie obsługi MMC1 z CHR-ROM oraz mappera MMC3 nie było możliwe z uwagi na niewystarczającą ilość zasobów w układzie CPLD oraz brak nóg – wykorzystałem wszystkie z dostępnych portów I/O a i tak musiałem się uciekać do pewnych trików, np. multipleksację: Y <= A when in = ‚0’ else B
zamienić na Y <= A when in = ‚0’ else ‚Z’, podciągająć jednocześnie Y pod B rezystorem.

Niestety moje „źródełko” układów XC9572 wyschło, a w Polsce ceny i dostępność tych układów pozostawiała wiele do życzenia. Chińczycy z kolei (aliexpress) cztery razy zrobili mnie w kulki, nie wysyłając zamówionych układów. W efekcie musiałem poszukiwać jakiejś alternatywy.

Budowa – początki, pierwsze trudności i pierwsze sukcesy
Na próbę zakupiłem bliźniacze układy Altery Max II, posiadające więcej, bo aż ok. 192 makrocel i ponad 2 razy więcej końcówe I/O. Od strony montażu, pomimo gęściejszego rastra (TQFP100), lutuje mi się je łatwiej, niż Xilinxa (PLCC44) – może dlatego, że w Xilinxie trzeba każdą nóżkę przylutować oddzielnie, a w Alterze wystarczy trik z minifalą.

Po pierwszych próbach z tymi układami miałem mieszane uczucia – wsad, który na Xilinxie zajmował ok 60 makrocel, na Alterze zajmował ich przeszło 120 (jedna makrocela to podstawowa jednostka pojemności układu CPLD/FPGA, zwykle wystarczająca do zapamiętania 1 bitu lub obsługująca jedno wyjście). Musiałem zmienić trochę styl programowania – w Xilinxie szanowałem ilość zapamiętywanych bitów (rozmiar rejestrów), kosztem bardziej rozbudowanych funkcji logicznych – poznałem nawet ciekawą funkcją `alias` w VHDL-u, która pozwala ten poszczególne bity z tego samego rejestru nazwać różnymi nazwami. W Alterze okazało się to dawać dość dziwne rezultaty. Czasami prostsza formuła zajmowała więcej komórek, niż bardziej skomplikowana – testy przeprowadzałem na Quartusie 9. Dopiero po przejściu na wersję 13 pewne problemy się rozwiązały, chociaż niektóre nadal zostały (np. zmiana w pewnym miejscu, zupełnie niezwiązanym z kartą pamięci SD powodowała problemy z jej obsługą, a np. zmiana opcji `Fitter seed` powodowała nagle znów powrót do działania).
Gdy udało mi się uzupełnić implementację mappera MMC1 o o obsługę gier z CHR-ROM oraz dodać pełną (!!!) obsługę mappera MMC3 (CHR-ROM + scanline counter), odbijałem się wielokrotnie od przekroczenia zasobów w układzie. Dopiero wykonywanie szeregu optymalizacji sprawiło, że cały wsad wreszcie zmieścił się do układu (95% wykorzystanych makrocel).

Mapper MMC3 – scanline counter
Kolejnym problemem była obsługa scanline countera w MMC3 – dodatku wzbogacającego gry o możliwość powiadomienia za pomocą przerwania w przypadku, gdy jest generowana linia wideo o określonym numerze. Jest to dodatek ułatwiający np. przełączenie się na drugi zestaw kafelków do generowania bardziej rozbudowanej grafiki lub status-barów.

Swoją wiedzę na temat specyfikacji czerpałem z nesdev.com – licznik scanlinii zlicza narastające zbocza na linii A12 od PPU, jednak ignorowane są oscylascje i zmiany zbyt częste.

Dopiero lekka modyfikacja momentu, w którym zgłaszane jest przerwanie sprawiła, że obraz w grach zaczął się wyświetlać poprawnie. Wciąż jednak przerwanie nie przychodziło dokładnie w tym momencie co powinno, co skutkowało trzęsieniem się obrazu. Ponadto, testy na konsolach na scalakach powodowały mniej zauważalne trzęsienie, niż konsole na glutach – prawdopodobnie konsole na scalakach mają dłuższe ścieżki sygnałowe, co skutkuje ich zwiększoną pojemnością do masy, przez co krótkotrwałe szpilki nie są tak łatwo przenoszone Dopiero zastosowanie wymyślonego przeze mnie filtru analogowego (dodatkowe elementy na kardridżu) + filtru cyfrowego (w układzie FPGA) sprawiło, że obraz zaczął przypominać taki, jak na emulatorze. Porównując z implemetacją MMC3 na pirackim scalaku mogę stwierdzić, że moja implementacja jest niemal idealna, obraz jest tak samo stabilny, a przełączanie następuję dokładnie w tych samych momentach, co na emulatorze.

Po lewej – implementacja na układzie scalonym AX5202P, dodawanym do kardridży – powinna działać dobrze, a obraz się trzęsie, a przerwanie zgłaszane jest w złym miejscu. Po prawej – moja implementacja – obraz idealnie stabilny.


Obsługa kart micro sd
Na obsługę kart micro sd składa się:
– blok w FPGA zajmujący się konwersją bajtu na postać szeregową i przesłanie go po SPI do karty (blok także musiał zostać napisany w sposób ekstremalnie optymalny),
– wiele linijek w assemblerze 6502 odpowiedzialnych za realizację komunikacji z kartą (wykrywanie obecności i typu karty, wysyłanie pakietów do karty),
– jeszcze więcej linijek w assemblerze odpowiedzialnych za wykrycie partycji, listowanie katalogów, wczytywanie plików (pełna obsługa FAT16/32, rozmiar klastra od 512 bajtów wzwyż)
Wszystkie powyższe części zostały napisane przeze mnie w całości od zera. Obsługa kart SD (wcześniej MMC) została napisana już w 2012 podczas realizacji pracy magisterskiej (wtedy obsługiwałem karty MMC). Z czasem, gdy w moje ręce trafiły karty SD (do 2 GB) zauważyłem, że protokół komunikacji nie wymaga zmian, a komunikacja z kartą działa tak samo. Dopiero karty >4 GB (SDHC) wymagają zmiany trybu adresowania (nr bloku podaje się jako 0, 1, 2, … zamiast 0, 256, 512, …).
Ponadto przez moje ręce przewinęło się też sporo kart 4 GB (głownie firmy SanDisk) które wciąz sprawiały kłopoty (podczas gdy dużo większa karta 32 GB KingSton działała bezbłędnie).

Dopiero modyfikacja komunikacji z kartą polegająca na wysyłaniu dodatkowych zboczy zegara przed kolejnymi odczytami przyniosła upragniony sukces
Optymalizacje czasu ładowania gry
Początkowo czas ładowania gry był niezadowalający (np. 168-in-1 -> 2 minuty). Na czas ładowania gry skłąda się:
– wyczyszczenie pamięci Flash (kilka sekund),
– odczyt z wybranego ROMu z grą porcji danych do pamięci PRG-ROM (sektor po sektorze) i jej zaprogramowanie (bajt bo bajcie),
– odczyt z wybranego ROMu z grą porcji danych do pamięci CHR-RAM i jej zapis.

Początkowo algorytm zapisu polegał na odczycie jednego bajta, jego zaprogramowaniu, następnie sprawdzeniu:
– czy nie trzeba wczytać nowego sektora,
– czy nie trzeba zmienić bank do pamięci,
– a może to już koniec programowania?.
Seria sprawdzeń po każdym bajcie była szalenie niewydajna. Pamiętam, jak jeden z wieczorów spędziłem nad rozważaniami i nad zmianą tego sposobu. Wszakże, jeżeli udałoby się programowanie całości zamiast `bajt-po-bajcie`, podzielić na programowanie go w blokach po 256 bajtów, po którym to bloku dopiero następowałoby sprawdzenie powyższych warunków, to działałoby to dużo szybciej. Trzeba jednak przed programowaniem każdego bloku określić, ile bajtów należy zaprogramować w danej iteracji (1-256) i zapamiętać to w rejestrze Y. Wtedy to główna pętla programująca mogłaby wyglądać tak:

;w Y – ilość bajtów do zaprogramowania
program_chunk_loop:
lda (ptr_src), Y
sta (ptr_dst), Y
iny
bne program_chunk_loop

Nawet, jeżeli wstępne obliczanie długości bloku trochę zajmie, to i tak mamy przewagę, bo każda iteracja powyższej pętli jest niesłychanie szybka. Ale czy można to jeszcze przyspieszyć? Tak! – ponieważ cała podprocedura programująca i tak wykonuje się w pamięci RAM (nie można jednocześnie programować pamięci ROM i wykonywać z niej kodu), to jeżeli długi, pięciocyklowy rozkaz odczytu/zapisu pośredniego (przez wskażnik zero-page i rejestr Y) – `lda (ptr_src), y` zamienić na rozkaz odczytu absolutnego: `lda $ffff, y` (gdzie adres $ffff zostanie przed każdym wejściem do pętli bloku sprytnie podmieniony w pamięci RAM), zyskujemy wtedy 2 cykle na każdy obrót (a obrotów jest 256).

Programowanie gry Super Mario Bros 3 (256 KB PRG-ROM + 128 KB CHR-ROM): 17 sekund:

Zbliżenie:

Kolejne zbliżenie:

Porównanie z programowaniem pamięci CHR-RAM:

Dodatkowe mappery
Dodanie obsługi kolejnych mapperów nie jest możliwe z uwagi na praktycznie całkowite wykorzystanie układu FPGA. Aczkolwiek płytka została zaprojektowana w tak przemyślany sposób (doprowadzenie do FPGA wszystkich lini adresowych z CPU), że może też posłużyć mi jako platforma do testów nad innymi rzadszymi mapperami i ewentualnie zaimplementowanie takich mapperów zamiast obecnych (np. VRC6).

Podsumowanie
Po wykonaniu zgodnego z oczekiwaniem i w pełni działającego egzemplarza prototypowego pomyślałem, że na taki wynalazek przecież czekają wszyscy posiadacze konsol. Ciężko dostępny i drogi konkurencyjny produkt (everdrive) nie jest w zasięgu ręku większości osób, stąd mój projekt może być ewenementem – wykonałem kilkanaście sztuk dla innych `zapaleńców grania` i postanowiłem  go także.

 Czytaj również: KrzysioCart Micro SD – FAQ

Krzysiobal