Да си направим самостоятелно свой собствен 16-битов CPU през 2023 година (видео)
За създаването на домашен процесор са необходими голям брой логически схеми. Наистина има логика в това, че за реализирането на регистрите, брояча на инструкциите, ALU (Аритметично-Логическо Устройство) и другите компоненти на процесора на базата на TTL или CMOS логика са необходими значителен брой чипове. Но колко точно?
Опитах се да оптимизирам моя самоделен процесор, като сведох до минимум броя на логическите чипове, за да отговоря на въпроса: какъв е минималният брой интегрални схеми, необходими за пълноценен процесор на Тюринг без използването на друг CPU?
Отговорът ми е, че за създаването на 16-битов последователен централен процесор са необходими само 8 интегрални схеми, включително памет и тактов генератор. Той разполага със 128KB SRAM, 768KB FLASH и може да бъде клокван до 10 MHz. Той съдържа само 1-битово ALU, но повечето от неговите 52 инструкции работят с 16-битови стойности (последователно). При максимална скорост той изпълнява приблизително 12 хил. инструкции в секунда (0,012 MIPS) и, наред с другите неща, е в състояние да прехвърля поточно видео на LCD дисплей, базиран на PCD8544 (Nokia 5110), с приблизително 10 FPS.
Ако се избере подходящата класификация за разделяне на крайните автомати и процесорите, моята 16-битова система може да се счита за процесор с най-малък брой интегрални схеми. Други претенденти за тази титла биха могли да бъдат 1-битовият компютър на Джеф Лофтън с 1 инструкция и 1 бит памет и опростеният процесор на Даниел Торнбург с 1 инструкция byte-byte-jump (копира 1 байт от едно място в паметта на друго и след това извършва безусловен преход) и памет, симулирана на Raspberry PI.
Хардуерът
Вдъхновението за създаването на тази архитектура идва от други проекти на процесори, като JAM-1 на Джеймс Шърман, SAP-1 на Бен Етер, 4-битовия Crazy Small CPU на Уорън, неговата 8-битова версия и други. Всички те и много други подобни архитектури използват „управляващи“ EEPROM, EPROM или ROM за генериране на управляващите сигнали на компонентите на процесора, защото това е много по-лесно, отколкото генерирането им само чрез логически схеми, а също и защото осигурява много по-голяма гъвкавост за в бъдеще. Аз също реших да използвам такава „управляваща“ памет, а именно EPROM. Само че, за разлика от гореспоменатите проекти, аз се стремях към възможно най-малък брой чипове, така че се опитах да „натъпча“ възможно най-голяма част от обработката на данните в паметта, за да намаля изискванията към другите компоненти на процесора или, още по-добре, напълно да се отърва от тях. Основните стъпки, които предприех, бяха следните:
- Избавих се напълно от ALU и го реализирах във вид на таблица. Тъй като повечето EPROM имат само 8-битов изход, а системата се нуждае и от други управляващи сигнали, размерът на данните на ALU трябваше да бъде значително ограничен. Но няма нужда да се притесняваме, той може да бъде намален до само един бит: в действителност 1-битовите изчисления са достатъчни.
- За да може да се извърши каквото и да е смислено изчисление, резултатите от 1-битовото ALU трябва да бъдат серийни – тоест последователни. Това е идеално за използването на SRAM памет с последователен достъп, която предоставя и други предимства. Първо, отпада необходимостта от регистри, тъй като всички ALU операции могат да се извършват директно с данните в SRAM. Второ, SRAM паметите с последователен достъп също така имат последователно адресиране, така че не е необходимо да записваме адресите на източника и местоназначението. Трето, произволната разреденост на обработката на данните може да се получи, като просто се избере по подходящ начин периодът на повторение на тактовите импулси на SRAM паметта. Избрах 16 бита (16 импулса на тактовия генератор на SRAM за 1 операция на ALU) като приемлив компромис между удобство и скорост.
Необходими са поне два SRAM чипа, като единият от тях трябва да осигурява последователен вход за нашето 1-битово ALU, като в същото време другият трябва да съхранява резултата. - Необходими са два последователни входа за ALU операции с два операнда (например ADD/AND/XOR…). Разбира се, можете да добавите трета SRAM памет (2 за входовете на ALU, 1 за резултата), но има и по-добро решение. Ако вместо SRAM се използва FLASH памет с последователен достъп, предимствата се запазват (вече имаме последователни данни и последователен адрес), като FLASH паметта може да се използва и за съхраняване на команди/програми, както и за осигуряване на ALU входове.
- Не е необходимо да се добавя хардуер за брояча на инструкциите, тъй като в SRAM има достатъчно място за съхраняване на неговата стойност.
Но дори при тези съществени опростявания все още е необходим допълнителен хардуер. Въпреки това всичко може да се събере само на 8 чипа, както е показано по-долу:
Схемата се базира на 128-килобайтовата EPROM памет M27C1001-15, работеща с напрежение 5 V, която съчетава краен автомат за управление и 1-битов ALU. Нейните изходни линии се фиксират от чипа 74HC574 на всеки период на повторение на тактовите импулси и управляват двете 64KB последователни SRAM 23LCV512 и една 1MB серийна FLASH W25Q80. Няма достатъчно изходи за управление на всяка памет поотделно, затова те споделят една обща шина за данни и също така частично линия за избор на чипа. Само линиите за синхронизиращите импулси остават отделни. Не можах да намеря серийна FLASH памет на 5 V, затова резисторите R3, R4 и R5 ограничават тока и образуват мост за намаляване на напрежението от 5 V на 3,3 V. Не считам стабилизатора на напрежение MCP1703 3,3 V за част от процесора (считах го, но само като част от захранването), но ако го броите, процесорът ще се състои от 9 чипа.
Текущата инструкция се съхранява в буферирания преместващ регистър 74HC595, чиито контролни линии също са частично споделени с чиповете памет. Изпълнението на всяка инструкция отнема няколко такта, така че прогресът на инструкцията се следи от брояча на „микрокода“ със 74HC393. Когато командата бъде изпълнена, линията Counter_reset нулира брояча на „микрокода“ и започва изпълнението на следващата команда, записана в буфера 74HC595.
74HC574 и броячът на „микрокода“ 74HC393 използват противоположните фронтове на синхронизиращите импулси и затова тактовият генератор с 74HC14 подава инвертиран тактов сигнал към 74HC393, за да осъществи синхронизацията.
Входове и изходи
Това, което не можах да реализирам в моя процесор по по-разумен начин, е самопрограмирането на FLASH паметта. Следователно bootloader не е възможен и зареждането на нова програма във FLASH паметта с последователен достъп трябва да се извършва външно. За тази цел използвах микроконтролера Attiny13, който прослушва чрез UART поредицата от команди, така че за зареждането на нов код е подходящ всякакъв USB-UART адаптер. При програмиране той изключва изхода на 74HC574 чрез линията „prog_en“ и започва директно да програмира FLASH паметта. Микроконтролерът се използва само за зареждане на новата програма и по-нататък процесорът работи добре и без него.
Единствените налични изходи са горните два бита на регистъра за смяна на инструкциите 74HC595. Използвах една от тези инвертирани линии за избор на чипа, което дава възможност на процесора да се свързва с устройства, подобни на SPI. Така например към него може да се свърже директно SPI-базиран LCD дисплей с напрежение 3,3 V PCD8544 (например Nokia 5110), като вторият старши команден бит се използва като селектор за данни/команди на LCD дисплея. Възможно е също така вместо LCD да се свърже допълнителен преместващ регистър от типа на 74HC595, за да се получат класически цифрови изходни линии.
Единствените налични входове са двата сигнала данни/вход на паметта, свързани с адресните шини на EPROM (A9, A11). Чиповете памет с последователен достъп поддържат висок импеданс на тези сигнали, когато те не се използват, така че да могат да се дискретизират като общи цифрови входове, когато чиповете памет са неактивни. Важно е да се отбележи, че входният сигнал не трябва да създава смущения на данните в паметта, затова е необходимо високо съпротивление между входния сигнал и входната шина на паметта (R6, R7). Забележка: четенето на входния сигнал по шините за данни на паметта работи само за тактови честоти до около 8 MHz. При по-високи честоти дискретизираните данни започват да стават погрешни и работата на процесора може да спре.
По-горе беше показано видеото на моя процесор, който възпроизвежда музикалния клип „Bad Apple!!!“ на LCD дисплея PCD8544. В долния видеоклип показвам възможността за управление на общите цифрови изходи след добавянето на още един 74HC595. Същата схема би могла да се използва за създаване на 8-битова музика със скорост до 4300 семпли/сек, ако вместо светодиоди се използва матрица от резистори R-2R, като по-горе е дадена схемата, която използвах за създаване на саундтрака към видеоклипа „Bad Apple!!!“.
Таблицата за разпределение на паметта
Процесорът няма отделни регистри, но има две SRAM, откъдето може да извършва четене и запис. Недостатъкът е, че всеки път, когато процесорът иска да получи достъп до данните, той трябва да запише пълния 16-битов адрес на SRAM паметта. А плюсът е, че тъй като все още трябва да се записва пълния 16-битов адрес, процесорът (и командите като цяло) може да получи достъп до всичките 64KB SRAM с постоянно еднакво време на достъп.
Избрах едната SRAM памет (U8/RAM1) за съхранение на програмните данни и всички аритметични и логически операции трябва да се извършват със стойности в тази памет. Втората памет SRAM (U7/RAM2) се използва за организация на стека, така че само някои команди могат да четат и променят нейното съдържание. Първите няколко байта от двата чипа памет са запазени за съхраняване на вътрешното състояние на процесора (брояч на инструкциите, бита на флага, указателя на стека, междинния резултат, източника и крайния адрес, както и други вътрешно използвани значения). Ето как изглежда приблизителната таблица за разпределение на паметта:
Заслужава да се спомене и начинът за използване на FLASH паметта като втори вход на ALU. Тъй като FLASH е доста голяма (1 MB), в нея може да се постави цялата 16-битова индексна таблица, съдържаща еднакви по формат 16-битови стойности. С тази 128 KB таблица за търсене на процесорните инструкции по подадения код може да се запише 16-битова стойност във FLASH паметта като адрес и да се прочетат подобните 16-битови стойности като данни, които да се използват като вход за ALU.
Малко неудобство при използването на памети с последователен достъп е, че тяхното адресиране е по MSB-first, докато 1-битовото ALU извършва изчисления във формат LSB-first. За да работи адресирането на паметта, трябва да преобразуваме битовете от формата LSB-first, с който работи централният процесор, във формата MSB-first, с който работят чиповете памет. Обръщането на битовете с помощта на 1-битов ALU не е толкова лесна задача, затова запазих още 128KB от FLASH паметта за таблицата за намиране на „обърнатите стойности“, за да ускоря операцията. Всичко работи по същия начин, както при предишната таблица – стойността се записва във FLASH паметта като адрес и после се чете като данни.
Именно заради тези таблици моят процесор има само 768 KB FLASH памет, а броячът на инструкциите (PC) започва от адрес 0x040000, а не от нулата.
Край на първа част. Във втората част ще разгледаме набора процесорни команди и ще представим няколко елементарни примера на асемблер за изчертаване на триизмерни фигури и тяхното завъртане в пространството, възпроизвеждане на клипове с ниска резолюция и други.