Все целиком)
Вид для печати
Итак, гость нашей студии - монстр таёжного рока - команда деления.
Самая массивная из всех команд процессора ВМ2. Она задействует и ресурсы микрокоманд, приостанавливая и запуская несколько раз конвейер заново. И стандартное ALU, и, конечно же, расширенное ALU. Те, ко ее кодировали, конечно, монстры. Я сам разбирался с ней 3 дня и 3 ночи, а еще строил модель на Си для проверки итогового алгоритма, а также просил @Ynicky сравнить с моделью.
DIV Rs,Rd:
Заметки к процедуре деления:Код://======================================================================
//
// Команда DIV Rs,Rd
//
//======================================================================
0x3B: ACC=Rs // ACC - делитель
// (в данной команде поля Rs и Rd кодируются не стандартно)
//----------------------------------------------------------------------
0x1A: EA1=Rd // EA1 - делимое, старшая часть
//----------------------------------------------------------------------
0x2A: EA2=Rd|1 // EA2 - делимое, младшая часть (EA1.EA2 - делимое)
//----------------------------------------------------------------------
0x27: CTR=0x0014 // Счетчик на 20 итераций
//---------------------------------------------------------------------- CTR=20
0x35: ACC=ACC // Проверка делителя на ноль
if (ACC=0) ZERO_DIV=1 // Если ACC=0, то ZERO_DIV=1 (деление на ноль)
else ZERO_DIV=0 //
TLZ=EA1[15] // TLZ=EA1[15] (знак делимого)
CTR=CTR-1
//---------------------------------------------------------------------- CTR=19
0x09: // Первый цикл деления
if (TLZ^ACC[15]=1) // Если у делимого и делителя разные знаки
EA1=EA1+ACC // EA1=EA1+ACC
else EA1=EA1-ACC // иначе EA1=EA1-ACC
EA1.EA2=(EA1.EA2<<1)+EA1[15] // Циклический сдвиг влево
EA2[0]=~EA2[0]^ACC[15] // Инвертируем бит займа и накладываем по XOR знак делителя
FCARRY=EA2[0] // Запомнить первый займ
CTR=CTR-1
//---------------------------------------------------------------------- CTR=18..4 (15 итераций)
0x09: loop:
// Основной цикл деления
if (EA2[0]=0) // Если не было займа, то
EA1=EA1+ACC // EA1=EA1+ACC
else EA1=EA1-ACC // иначе EA1=EA1-ACC
EA1.EA2=(EA1.EA2<<1)+EA1[15] // Циклический сдвиг влево
EA2[0]=~EA2[0]^ACC[15] // Инвертируем бит займа и накладываем по XOR знак делителя
CTR=CTR-1
if (CTR>3) GOTO loop
//---------------------------------------------------------------------- CTR=3
0x31: // Завершающий цикл деления
if (EA2[0]=0) // Если не было займа, то
EA1=EA1+ACC // EA1=EA1+ACC
else EA1=EA1-ACC // иначе EA1=EA1-ACC
ЕА2=(EA2<<1)+EA1[15] // Сдвиг результата влево
EA2[0]=~EA1[0]^ACC[15] // Инвертируем бит займа и накладываем по XOR знак делителя
CTR=CTR-1
//---------------------------------------------------------------------- CTR=2
0x31: // Корректировка остатка
if (EA1=0) // Если EA1=0 (остаток равен 0),
EA1=EA1 // то ничего не делаем
else if (EA2[0]=0) // Иначе если не было займа, то
EA1=EA1+ACC // EA1=EA1+ACC
else EA1=EA1-ACC // иначе EA1=EA1-ACC
CTR=CTR-1
//---------------------------------------------------------------------- CTR=1
0x31: // Корректировка остатка
if (EA1=0) // Если EA1=0 (остаток равен 0),
EA1=EA1 // то ничего не делаем
DIV_Z=1
else
DIV_Z=0 // Иначе
if (TLZ^EA1[15]=0) // Если остаток и делимое одинакового знака, то
EA1=EA1 // ничего не делаем,
else if (ACC[15]^EA1[15]=0) // иначе если остаток и делитель одинакового знака, то
EA1=EA1-ACC // EA1=EA1-ACC
else EA1=EA1+ACC // иначе EA1=EA1+ACC
endif
CTR=CTR-1
//---------------------------------------------------------------------- CTR=0
0x19: // Корректировка результата в зависимости от знаков
if (((TLZ=0) & (ACC[15]=1)) or // Если знак делимого плюс, и знак делителя минус, или
((TLZ=1) & (ACC[15]=0) & (DIV_Z=0)) or // если знак делимого минус, знак делителя плюс, и DIV_Z=0, или
((TLZ=1) & (ACC[15]=1) & (DIV_Z=1))) // если знак делимого минус, знак делителя минус, и DIV_Z=1, то
EA2=EA2+1 NZVC // EA2=EA2+1 с установкой флагов
else EA2=EA2^0 NZVC // Иначе EA2=EA2 с установкой флагов
// Алгоритм установки флагов
C=ZERO_DIV // Флаг C отображает деление на ноль
if ((ZERO_DIV=1) or // Флаг V отображает деление на ноль и арифметическое переполнение
(FCARRY^ACC[15]^TLZ=1) or
(FCARRY^EA2[15]^CARRY))
V=1
else V=0
if (EA2=0) Z=1 // Флаг Z отображает равенство результата нулю
else Z=0
N=EA2[15] // Флаг N отображает знак результата
//---------------------------------------------------------------------- Проверка арифметического переполнения и деления на ноль
0x1E: ACC=PSW&0x02 GET_STATE Z=0 // Если PSW[V]=1 (Z=0), то BRA=1, иначе BRA=0
//----------------------------------------------------------------------
0x2D: NO ALU PI_STB VEC=0x03 RI=100 // Управление: константа вектора 0x0C, используется как константа для АЛУ
if (BRA=1) GOTO 0x2C // Если BRA=1 (V=1) (арифметическое переполнение)
else GOTO 0x0E // Иначе BRA=0 (V=0) (нет переполнения)
//---------------------------------------------------------------------- Нет переполнения и деления на ноль
0x0E: Rd|1=EA1 // EA1 - остаток
//----------------------------------------------------------------------
0x0C: Rd=EA2 // EA2 - частное
if (Rs=R7) GOTO 0x21 // Перейти на команду выборки следующей некэшированной инструкции
else GOTO 0x01 // Перейти на команду выборки следующей инструкции
//---------------------------------------------------------------------- Арифметическое переполнение или деление на ноль
0x2C: PSW=PSW&~0x0C // Очистить флаги N и Z
//----------------------------------------------------------------------
0x28: CPSW=PSW // Копировать PSW в CPSW, потому что при работе с констатной, CPSW в данном режиме не обновляется
//----------------------------------------------------------------------
0x08: ACC=ACC PLI_REQ // Запросить проверку запросов на прерывание
if X(Rs) or @X(Rs) or (Rs=R7) // Если использовали адресацию по R7, то
GOTO 0x21 // Перейти на команду выборки следующей некэшированной инструкции
else // Иначе
GOTO 0x01 // Перейти на команду выборки следующей инструкции
1. На проверку флага V в PLM проверки условий (PLM_BRA), видимо, уже не хватало места, и проверку флага V сделали через проверку флага Z (PSW & 0x02). Что на 4 такта замедляет деление, но ввиду и так достаточно длинного его выполнения, особой роли не играет.
2. В конце деления запись результата в R7 проверяется стандартным для обычных двухоперандных инструкций методом. Однако, ввиду того, что Rd и Rs у команд EALU поменяны местами, эта проверка является некорректной.
3. Не смотря на то, что я всячески стараюсь не менять названия сигналов, данные @Vslav'ом, тут все же пришлось заменить /WAIT_DIV на DIV_Z, ибо никак не соответствует.
4. Каждый из столь емких шагов EALU делается за 4 такта, за исключением проверки флагов, которая всегда делается за 8 тактов.
5. В блоке констант не нашлось констатны 0xF3, поэтому для этого пришлось воспользоваться хитрым образом констатной из блока векторов прерываний 0x0C (BPT), инвертируя ее.
6. Из-за того, что в случае деления на ноль или переполнения, используется модификация PSW (сброс флагов N и Z), а так же нестандартное копирование PSW в CPSW, блокировка CPSW при запрещенных прерываниях в HALT-режиме не сработает.
В пультовом отладчике. Таймер желательно выключить, запускать с запретом прерываний, т.е. RS=340. Программка проверки занимает несколько слов, так быстрее проверить так.
- - - Добавлено - - -
Ох! Очень интересно! Получается, что при арифметическом переполнении результат PSW обязательно копируется в CPSW, даже несмотря на HALT с запрещёнными прерываниями. Надо обязательно проверить.