С любовью к вам, Yandex.Direct
Размещение рекламы на форуме способствует его дальнейшему развитию
Alex_K(05.12.2020)
Итак, гость нашей студии - монстр таёжного рока - команда деления.
Самая массивная из всех команд процессора ВМ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-режиме не сработает.
Последний раз редактировалось Titus; 05.12.2020 в 17:41.
Alex_K(05.12.2020)
В пультовом отладчике. Таймер желательно выключить, запускать с запретом прерываний, т.е. RS=340. Программка проверки занимает несколько слов, так быстрее проверить так.
- - - Добавлено - - -
Ох! Очень интересно! Получается, что при арифметическом переполнении результат PSW обязательно копируется в CPSW, даже несмотря на HALT с запрещёнными прерываниями. Надо обязательно проверить.
Эту тему просматривают: 2 (пользователей: 0 , гостей: 2)