ПРЕ-обработка-РЫВАНИЯ.
Предлагаю вам перевод цикла The EightThirtyTwo ISA Part 9: INTER-handling-RUPTS автора Alastair M. Robinson.
Сумев заставить процессор работать достаточно хорошо, чтобы запустить программу Hello World, я с тех пор уговорил его запустить программу декомпрессии LZ4, которую я опубликовал неделю или две назад, и был приятно удивлен, обнаружив, что он успешно работает на частоте 133 МГц на моей плате DE2.
Я размышлял о том, как лучше всего обрабатывать прерывания на этом процессоре, и понял, что я загнал себя в угол, используя регистр tmp в качестве регистра ссылки при ветвлении; это означает, что без добавления какого-либо временного регистра хранения или логики стека, особенно для этой задачи, я не могу изменить поток программы извне – например, в ответ на прерывание – без потери содержимого регистра tmp.
Даже если я добавлю такой регистр хранения или операцию стека, у меня также нет возможности отменить этот процесс в конце обработчика прерываний без добавления дополнительной инструкции. У меня все еще осталось немного места для кодирования инструкций с нулевым операндом, но я все равно хотел бы избежать этого, если смогу, так как это несколько увеличит размер логики. Я также предпочел бы не добавлять какие – либо операции стека, потому что (а) это приведет к принудительному использованию определенного регистра для стека, который в настоящее время полностью необязателен, и (б) я все равно потеряю содержимое tmp, когда мы восстановим счетчик программы, если я не добавлю инструкцию на подобии “rts” .
Решение, которое я выбрал на данный момент, сделка между потенциальным временем отклика для простоты и отсутствием дополнительной логики для обработки прерываний.
Проблема, которую необходимо решить, заключается в том, что регистр tmp уничтожается при обслуживании прерывания, поэтому я решил полностью обойти проблему, разрешив прерывания только тогда, когда следующая инструкция собирается записать в tmp. На практике это означает, что конвейер перехватит первый запуск инструкций li или инструкций mt. В принципе ld, ldinc или lddinc также могут быть перехвачены, но я еще не пробовал этого. При возвращении из процедуры прерывания перехваченная инструкция будет выполняться, записывая новое значение в tmp, и, таким образом, ее старое содержимое будет потеряно, но это не будет иметь значения.
Перехваченные инструкции заменяются операциями, которые помещают счетчик программ (с битами состояния процессора, закодированными в верхние четыре бита) на оба входа ALU, операция ALU установлена в xor, первый выход установлен для записи результирующего нуля в r7, а второй выход-для записи переданного значенияв tmp.
Таким образом, при прерывании мы переходим к нулевому местоположению с установленным нулевым флагом. Это сводит к минимуму дополнительную логику, необходимую для поддержки прерываний, не требует дополнительных регистров сохранения значений, логики стека или даже вектора прерывания; начальная точка входа и вектор прерывания совместно используются как нулевое местоположение, с нулевым флагом, установленным при прерывании и очищенным при включении питания.
Однако при сохранении и восстановлении обратного адреса требуется довольно деликатный регистровый танец; поскольку при возвращении нам нужно выполнить инструкцию, которую мы перехватили, чтобы вызвать прерывание, мы должны уменьшить обратный адрес на единицу, прежде чем перейти к нему. Код запуска и обработки прерываний выглядит следующим образом:
vector: // Инициализация кода и вектора прерываний. В прерывании флаг zero должен быть установлен. cond NEQ li IMW1(PCREL(entry-1)) li IMW0(PCREL(entry)) add r7 interrupt: // Мы попадаем сюда, если это прерывание, а не событие включения питания. exg r6 // Обмен указателя стека с адресом возврата stmpdec r0 // Записать r0 в стек stmpdec r6 // Записать адрес возврата в стек. stmpdec r1 // Записать другие повреждаемые регистры в стек mr r6 // Вернуть указатель стека в r6. // Обработка прерывания здесь... ldinc r6 // Восстановить r1 и другие повреждаемые регистры - но записанные ранее... mr r1 ldinc r6 // Переместить адрес возврата в temp mr r0 // и оттуда в r0 li IMW0(-1) add r0 // Уменьшить адрес возврата ldinc r6 // Переместить записанное значение из r0 в tmp. exg r0 // Обменять tmp и r0, восстанавливая r0 и помещяем скорректированный адрес возврата в tmp mr r7 // Переход на скорректированный адрес возврата. entry: // Главный код программы...
Если это выглядит запутанным или многословным… хорошо… возможно, это так – но учтите, что это всего лишь 18 байт!
У меня есть эквивалент моей более ранней демо-версии прерывания ZPU, которая просто чередуется печатью слов “тик” или “так” в ответ на прерывание таймера. Единственное небольшое разочарование заключается в том, что моя логика перехвата является частью критического пути и, таким образом, снижает fmax демонстрационной программы примерно до 129 МГц на DE2.
Адрес для контактов : imax9@narod.ru
Если вам понравились мои работы и вы желаете поддержать сайт - сделайте дотацию.
При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.
© Максим Ильин 2022г.