Как должен выглядеть регистр указателя стека на Verilog?
У меня вопрос по программированию на Verilog и счетчику стека в котором будет хранится контекст процессора, т.е. набор его регистров.
Не так давно, посетила мысль написать на Verilog несложный процессор. И вроде бы все относительно просто. Просто до того момента как начинаешь оптимизировать.
Итак. Предположим в процессор закладывается набор регистров, и предполагается некоторое ABI, похожнее на ARM-овское. Первые четыре регистра это аргументы и результат функции, а оставшиеся,- контекст функции. Все равно при вызове функции как правило эти регистры укладываются в стек, т.е. выполняется команда stmia sp!, {r4-r1x,lr}, или push {r4-r1x,lr}. Вообще говоря эта инструкция приводит к приращению указателя стека. О нем и поговорим.
Предположим, преследуя цели защиты контекста функции я решил сохранять эти регистры аппаратно, в отдельный стек. А преследуя цель увеличения производительности, решил еще и сделать отдельные стеки для каждого регистра, но с общим указателем.
Это удобно. Поменял указатель (из специального режима) сделал call и вот ты в новой функции или даже задаче, или прерывании. В этом случае, у каждого регистра своя шина данных и шина адреса, связанная с регистром указателя стека. Они параллельные, а значит быстрые.
Доступ к обычному стеку (как к сегменту памяти) сохраняется для типовых задач. А этот фактически хранит лишь контекст функции/задачи/прерывания.
Насколько я понимаю среди блоков процессора на фабриках, можно найти SRAM, причем даже с отдельными шинами данных и адреса для чтения и записи.
Если с памятью все понятно, то с адресом все немного сложнее. Когда процессор выполняет команду push, следует иметь адрес следующей ячейки в текущем такте работы процессора.
Когда он выполняет команду pop, следует иметь предыдущий адрес, т.к. данные необходимо забрать из памяти. Если конечно планируется сделать это за один такт.
Да, конечно, можно реализовать реверсивную цепочку регистров и это будет быстрее, но у этого есть свои минусы. Во-первых вырастет динамическое энергопотребление, т.к. все триггеры одновременно переключаться. Во-вторых в этом случае не будет указателя стека и процессор станет "однозадачным" + "прерывания" в контексте текущей задачи.
Кроме того, столько триггеров в FPGA для моделирования, наверное нет. Конечно, для маленького МК, можно сделать короткий стек на несколько вызовов. Было что-то подобное в AT90S1200 для регистра PC (call/ret). Там 8 кажется, если не изменяет память. Только сохраняем все регистры общего назначения, не задействованные в передаче аргумента и возвращении результата, включая PC.
Что-то подсказывает что память как готовый блок использовать выгоднее.
Идеи описаны. Вернемся к вопросам реализации. Итак, имеем регистр SP. Фактически, для операций push и pop, требуется иметь сразу два адреса оиличающихся на единицу.
Но при операции push, в начале такта требуется значение на единицу больше, чем при операции pop. Т.е. сигнал WR, в текущем такте должен быть сформирован с соответствующим адресом. А сигнал RD, с другим.
Приращение счетчика, это сумматор. Сумматор использует схему переноса, а значит будет выполнятся в течение некоторого времени.
Если не задумываться о таймингах, то можно сделать реверсивный счетчик и сумматор. Это и будут адреса для чтения и записи. Или нет?
Реверсивный счетчик, это сумматор с числом 1 или 0xFFFF(F), в зависимости от того инкрементируем адрес или нет. Т.е. получается что все биты кроме нулевого меняются. Сумматор задействует схемы переноса разряда, а значит это приводит к некоторой задержке. После сумматора счетчика, нужен еще один сумматор.
Если не конвейеризовать операции сложения и мультиплексирования результата, то уменьшается тактовая частота.
Теперь вопрос.
Получается, чтобы счетчик указателя стека работал быстро, в действительности требуется держать два счетчика, и просчитывать их значения заранее? Т.е. по факту нужны три регистра: значение, значение + 1, значение -1? Или даже еще значение +2?
Неужели, в процессорах, такой простой элемент как счетчик настолько серьезно конвейеризируется? Как это реализуется в серьезных изделиях?
Сам я в вопросе ноль, но мне очень интересно, на сколько современный ИИ способен отвечать на вопросы, в т.ч. и такие (очевидно что в интернете на такой вопрос уже отвечали, а значит как минимум gpt что то про это видел). Можете дать оценку этим ответам? google:gemmini 2.5 pro preview anthropic:claude sonnet 3.7 openai:o4-mini
Сам я вижу что anthropic явно выдал ответ общими словами, но гугл и openai что то понимают. Но важно что они оба могут красиво и правдоподобно сгаллюцинировать
Если отвечать на вопрос, а не на очень длинное введение (спорное), то ответ такой:
Счётчик по определению содержит в себе сумматор, как минимум +1 (конечно, если мы говорим о двоичном счётчике в дополнительном коде). В сумматоре используются схемы быстрого параллельного переноса, по этому он обычно работает без дополнительных ухищрений на целевой частоте.
Дополнительные счётчики, как вы предлагаете, ничем не помогут, потому что на следующем такте снова понадобится -1, +1 и +2, только теперь на единицу больше. Как минимум один счётчик всё равно должен считать.
Ну и вы по-моему не с того края начали оптимизировать. Почитайте книжку Харрисов, там разбирается типичный подход к разработке микроархитектуры.
Добрый день!
Книжку я читал. Знаю и о ковейере и о перестановке инструкуий и о bypass-ах. Проблема в другом.
В нашем мире, к сожалению очень много лжи, которой многие верят наслово.
Когда начинаешь что-то делать сам. Сталкиваешься с узкими местамии понимаешь что предложенное решение не работает.
Какое количество бит одновременно охватывает схема ускоренного (параллельного) переноса? А
Что касается счетчиков... Как оказалось и тут не все просто. Если не использовать тактирование по двум фронтам (negedge и posedge), то к началу такта, следует держать два адреса на шине оперативной памяти. Один для чтения (команда pop). Другой для записи (команда push). Это read и write порт двухпортовой памяти. Собственно в этом случае команда выполнится к концу такта.
Дополнительный счетчик, напротив, поможет. Предположим, имеется референсный регистр указателя стека.
Если его инкрементировать, то новое значение для второго адреса будет получено только через два такта. Т.е. ппризойдет суммирование + защелкивание в регистр. И лишь после этого второй счетчик обновится, используя новое значение первого. У нас же логика синхронная....
Когда счетчика два, они инкрементируются / декрементируются параллельно. Главное, инициализировать их значениями (текущим и +1).
А вообще в этом мире многое вызывает вопросы. Особенно вещи далекие от электроники. Например медицина и заболевания. Там много можно наковырять.