Comments 7
В целом подумал, подумал, и, кажется, примерно определился.
Википедия, здоровья сему справочнику, указывает в качестве рекорда полного сумматора на КМОП 24 транзистора. Это сумматор, реализованный как один логический элемент «полный сумматор». С, так сказать, полным сокращением всех «лишних членов уравнений».
А вот если из отдельных элементов, то лучше классического «nine-NAND» никто не придумал (ну серьёзно, где ещё можно проверить это вот «не придумали ли за сто лет ещё чего-то», как не на Википедии?) 9 NAND — это 36 транзисторов на КМОП, разница невелика. Но!
С такого сумматора можно снимать с промежуточных участков операции «MOV» (очевидно; просто взяли вход и дело с концом), «XOR» (даже не нужен вход «запрет внутреннего переноса» — просто берём в нужном месте, где этот самый перенос ещё не встрял в результат), «ADD/ADC» (одна и та же команда, просто можем подать на первый перенос флаг переноса, а можем — ноль) и таки «NAND». Да, «Мух» потребуется приличный, ну да как без них, без мухов-то. Что ещё хуже по транзисторам, лишний муховый вход или лишняя логика. Муховый вход обычно дешевле. А вот с 24-транзисторного практически ничего дополнительного снять нельзя…
Итого: поскольку NAND A, A имеет ещё какой великий смысл, подменять нужно только операнд для MOV. Поэтому подаём на вход АЛУ строго выбранный операнд, а вот у второго муха (который выбирает, какие именно результаты взять с АЛУ — окончательную сумму или что-то промежуточное) входы, отвечающие за MOV, пропускаем через дополнительный мух, который подсовывает на них P в тех случаях, если операнд у нас A. Он вносит дополнительную задержку только на входах, реализующих MOV — а туда хоть часы с кукушкой можно поставить, там запас по времени относительно других операций конский.
Избавившись от лагов и обеспечив забесплатно важнейшую команду MOV A, P (у нас же только P можно грузить константой прямо из микрокода, командой SCN! Перекладывать её в аккумулятор — важнейшая операция), позаботимся о бедном NAND A, A. Тут я хочу ввернуть изящный хак: добавить ещё один флаг «NEG» и при NAND A, A всегда ставить его в единицу, а вот флаг переноса — не обнулять, а сохранять текущий (любые другие команды, модифицирующие флаги, обнуляют NEG). И команду ADD слегка модифицировать: на первый вход переноса подаётся не 0, а флаг NEG.
Поясняю: берём младший байт вычитаемого из срамы, инвертируем, NEG взводится. Берём младший байт уменьшаемого из срамы, делаем ADD. Получается сложение уменьшаемого с инверсией вычитаемого плюс единичка — то есть нормальное человеческое вычитание в дополнительном коде. Теперь у нас есть нормальный флаг переноса из младшего байта и младший байт разности. Кладём его в сраму, берём второй байт вычитаемого. Инвертируем, флаг переноса не меняется. Берём второй байт уменьшаемого, делаем ADC. Он учитывает реальный перенос из младшего байта, как и подобает ADC — а вот NEG игнорирует. Перебирая вот так вот байты, производим вычитание «на лету», не храня нигде в сраме допкод вычитаемого. Ну, и как выше говорил, чтобы не мешать нормальной работе ADD, любые другие операции, кроме NAND A, A, NEG обнуляют.
Флаг переноса для всех NAND, кроме NAND A, A, можно использовать для быстрой проверки флагов: он может означать не то, что результат у нас 1 ??? ???, а то, что он у нас 111 111. То есть в несколько извращённом, но крайне полезном смысле «вот ещё бы одна единичка, и был бы перенос». Всё равно реальных переносов в NAND, ясен пень, не бывает — а вот кинуть в P маску с единичкой в нужном бите и через NAND A, P быстро проверить, единичка ли в этом бите аккумулятора, штука архиполезная. Но перенос, зараза, из сумматора вылезает последним, так что надо бы подумать, что это от нас потребует по быстродействию и по лишним транзисторам. Есть ощущение, что копейки, но надо смотреть по месту. В случае простого AND это был бы, конечно, флаг нулевого результата, но переход от AND к NAND нам эту халяву прикрыл…
Чем-то напонило Programmable I/O (PIO) контроллер, встроенный в Raspberry PI PICO: такой же крохотный размер памяти для его программы и минималисткий набор инструкций.
«И питать прямо от 12 вольт, а не от 3.3»
Ох, помню... Сначала 155 серия, ей строго 5 вольт подавай. Впрочем, на практике и от батарейки на 4,5 тоже работало. Потом 561, сколько же различной несложной автоматики на ней делали. От 3 до 15 вольт. А как пошла мода пихать что-нибудь 8051-совсестимое куда ни попадя - потребовало снова строго 5, стабильных. Казалось шагом назад.
В целом подумал, подумал, и, кажется, примерно определился.
Википедия, здоровья сему справочнику, указывает в качестве рекорда полного сумматора на КМОП 24 транзистора. Это сумматор, реализованный как один логический элемент «полный сумматор». С, так сказать, полным сокращением всех «лишних членов уравнений».
А вот если из отдельных элементов, то лучше классического «nine-NAND» никто не придумал (ну серьёзно, где ещё можно проверить это вот «не придумали ли за сто лет ещё чего-то», как не на Википедии?) 9 NAND — это 36 транзисторов на КМОП, разница невелика. Но!
С такого сумматора можно снимать с промежуточных участков операции «MOV» (очевидно; просто взяли вход и дело с концом), «XOR» (даже не нужен вход «запрет внутреннего переноса» — просто берём в нужном месте, где этот самый перенос ещё не встрял в результат), «ADD/ADC» (одна и та же команда, просто можем подать на первый перенос флаг переноса, а можем — ноль) и таки «NAND». Да, «Мух» потребуется приличный, ну да как без них, без мухов-то. Что ещё хуже по транзисторам, лишний муховый вход или лишняя логика. Муховый вход обычно дешевле. А вот с 24-транзисторного практически ничего дополнительного снять нельзя…
Итого: поскольку NAND A, A имеет ещё какой великий смысл, подменять нужно только операнд для MOV. Поэтому подаём на вход АЛУ строго выбранный операнд, а вот у второго муха (который выбирает, какие именно результаты взять с АЛУ — окончательную сумму или что-то промежуточное) входы, отвечающие за MOV, пропускаем через дополнительный мух, который подсовывает на них P в тех случаях, если операнд у нас A. Он вносит дополнительную задержку только на входах, реализующих MOV — а туда хоть часы с кукушкой можно поставить, там запас по времени относительно других операций конский.
Избавившись от лагов и обеспечив забесплатно важнейшую команду MOV A, P (у нас же только P можно грузить константой прямо из микрокода, командой SCN! Перекладывать её в аккумулятор — важнейшая операция), позаботимся о бедном NAND A, A. Тут я хочу ввернуть изящный хак: добавить ещё один флаг «NEG» и при NAND A, A всегда ставить его в единицу, а вот флаг переноса — не обнулять, а сохранять текущий (любые другие команды, модифицирующие флаги, обнуляют NEG). И команду ADD слегка модифицировать: на первый вход переноса подаётся не 0, а флаг NEG.
Поясняю: берём младший байт вычитаемого из срамы, инвертируем, NEG взводится. Берём младший байт уменьшаемого из срамы, делаем ADD. Получается сложение уменьшаемого с инверсией вычитаемого плюс единичка — то есть нормальное человеческое вычитание в дополнительном коде. Теперь у нас есть нормальный флаг переноса из младшего байта и младший байт разности. Кладём его в сраму, берём второй байт вычитаемого. Инвертируем, флаг переноса не меняется. Берём второй байт уменьшаемого, делаем ADC. Он учитывает реальный перенос из младшего байта, как и подобает ADC — а вот NEG игнорирует. Перебирая вот так вот байты, производим вычитание «на лету», не храня нигде в сраме допкод вычитаемого. Ну, и как выше говорил, чтобы не мешать нормальной работе ADD, любые другие операции, кроме NAND A, A, NEG обнуляют.
Флаг переноса для всех NAND, кроме NAND A, A, можно использовать для быстрой проверки флагов: он может означать не то, что результат у нас 1 ??? ???, а то, что он у нас 111 111. То есть в несколько извращённом, но крайне полезном смысле «вот ещё бы одна единичка, и был бы перенос». Всё равно реальных переносов в NAND, ясен пень, не бывает — а вот кинуть в P маску с единичкой в нужном бите и через NAND A, P быстро проверить, единичка ли в этом бите аккумулятора, штука архиполезная. Но перенос, зараза, из сумматора вылезает последним, так что надо бы подумать, что это от нас потребует по быстродействию и по лишним транзисторам. Есть ощущение, что копейки, но надо смотреть по месту. В случае простого AND это был бы, конечно, флаг нулевого результата, но переход от AND к NAND нам эту халяву прикрыл…
А, и вот ещё ситуация, которую я рассматривать не планирую, но для полноты картины нельзя не упомянуть.
Допустим, у нас паразитная ёмкость линий пользовательской промки даёт лаг примерно такой же, как наш простейший (последовательный) перенос. Тогда мы можем почти удвоить тактовую, просто сделав так, чтобы операции с переносом и извлечение данных из промки никогда не встречались в одной команде.
Тогда команды ADD/ADC получат другой набор аргументов, где могут фигурировать P и даже константа «1» (сиречь инкремент). Ну, а чтобы прибавить константу из промки, её придётся сначала переложить при помощи MOV (всё равно по времени это будет почти одно и то же), правда, дефицит свободных регистров будет ощущаться ещё больнее (одна из причин, по которым я не хочу это всерьёз рассматривать — с точки зрения назначения процессора это оптимизация вторичных характеристик в ущерб первичным).
Кроме этого, возможна ситуация, когда у ADD/ADC из-за этого подхода осталось настолько мало аргументов (6, а то и 4), что можно ADC и вовсе упразднить, а сложение с переносом сделать как разновидность аргумента (A=A+?+NEG в случае ADD, A=A+?+CF в случае ADC, а пишем как ADD A, ? или ADD A, ?+C оба случая). Это освобождает целый опкод для тех же инкрементов и декрементов, которые весьма актуальны для быстрого вошканья по той же промке или сраме, например. Хотя бы то же самое сложение многобайтных чисел.
И вишенка на торте — инкремент, скажем, HL может делаться вообще без АЛУ, если там тоже регистр-счётчик, как PC. Причём обоих байтов сразу. В общем, реальные случаи применения могут ласково шептать на ушко «сделай свою версию, разница в схемотехнике крошечная, а эффект…»
…и ещё моментик всплыл, пока схемы корябал.
После третьего вызова SCN никакие другие команды не имеют смысла, кроме четвёртого вызова SCN. Заранее записать и использовать полубайт сегмента нельзя — сразу к четвёртой команде перейти невозможно. Получается, там код операции пропадает зря.
А между тем, сегмент мог бы использоваться не только для безусловного перехода, но и для любой другой команды, для которой типична предварительная загрузка регистра P какой-то хардкодной константой. Даже хотя бы для доступа к сраме — ну, нельзя будет использовать вычисленный P для операций, которые обращаются к области выше 64 байта. А сейчас этих областей и вовсе нет, кек. А если сделать адресацию как Seg:P — то хотя бы к фиксированным адресам, прямо указанным в микрокоде, обращаться будет можно (загрузили, как обычно, P двумя командами SCN, потом третьей загрузили Seg, а потом — любая команда, использующая SRAM[P], после чего Seg обнуляется и снова всё как обычно).
А ещё можно Seg не обнулять, если он был не нулевой, а P — инкрементировать. Быстрый, но строго последовательный доступ к памяти выше 64-го байта. А ещё можно именно туда замапить порты (порты-то как раз обычно по фиксированному адресу), а операнды портов использовать именно как добавочные биты адреса (то есть сливаем пространства срамы и портов в монолит срамы 64x3 байта, а порты делаем «над ними», правда, ХЗ зачем столько адресов каким-то портам).
Надо всё это дополнительно обдумать и оценить, каким количеством транзисторов за это платим и сколько — сэкономим (уменьшив размер микрокода и, соответственно, количество его ключей).
Шестибитный процессор без единой картинки