Важная информация

User Tag List

Показано с 1 по 4 из 4

Тема: Эмуляция встроенного таймера процессора 1801ВМ1

  1. #1
    Guru
    Регистрация
    11.09.2009
    Адрес
    Москва
    Сообщений
    4,777
    Спасибо Благодарностей отдано 
    2
    Спасибо Благодарностей получено 
    122
    Поблагодарили
    61 сообщений
    Mentioned
    16 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию Эмуляция встроенного таймера процессора 1801ВМ1

    После завершения серии тестов встроенного таймера процессора 1801ВМ1 ( но же ВЕ-таймер ) мною написан алгоритм максимально точной эмуляции его работы.

    Предлагаю всем заинтересованным участникам проверить корректность алгоритма:

    Код:
    typedef unsigned long long QWORD;
    typedef unsigned long      DWORD;
    
    #define WORD(a) (*(unsigned short *)&aMemory[a])
    
    PowerON()
    {
      WORD(0177706) = wCPU_ID;
      wVE_Timer_Counter = 0177777;
      WORD(0177710) = wVE_Timer_Counter;
      WORD(0177712) = 0177400;
    }
    
    Reset()
    {
      WORD(0177712) = 0;
      Last_IO_Mode = IO_WRITE;
      wLast_IO_Addr =  0177712;
      Make_IO();
    }
    
    Make_IO()
    {
      switch( wLast_IO_Addr )
      {
        // VE-Timer Counter
        case 0177710:
        {
          if( Last_IO_Mode == IO_WRITE )
          {
            WORD(0177710) = wVE_Timer_Counter;
          }
          else
          {
            if( bVE_Go )
            {
              QWORD qwCurrentCPU_RunTimeNS = qwCPU_TotalRunTimeNS;
              qwCurrentCPU_RunTimeNS += dwCurrentStepsLatencyNS;
              QWORD qwCurrentCPU_Clocks = qwCurrentCPU_RunTimeNS/uCPU_CycleNS;
              QWORD qwUsedCPU_Clocks = qwCurrentCPU_Clocks - qwVE_TimerStart_CPU_Clocks;
              qwUsedCPU_Clocks += qwVE_TimerStart_CPU_Clocks%128;
              QWORD qwCurrentVE_Timer_Clocks = qwUsedCPU_Clocks/nVE_CPU_Clock_Divider;
    
              if( !dwInitialVE_CounterVal ) { dwInitialVE_CounterVal = 0x10000; }
    
              DWORD dwTimerClocksHi  = qwCurrentVE_Timer_Clocks / dwInitialVE_CounterVal;
              DWORD dwTimerClocksLow = qwCurrentVE_Timer_Clocks % dwInitialVE_CounterVal;
    
              if( dwTimerClocksHi )
              {
                bVE_Expiry = true;
                
                if( bVE_NoReload )
                {
                  dwTimerClocksLow = ( qwCurrentVE_Timer_Clocks - dwInitialVE_CounterVal ) % 0xFFFF;
                  dwInitialVE_CounterVal = 0xFFFF;
                  wVE_Timer_Counter = dwInitialVE_CounterVal - dwTimerClocksLow;
                }
                else if( bVE_NoRestart )
                {
                  wVE_Timer_Counter = 0;
                }
                else
                {
                  wVE_Timer_Counter = dwInitialVE_CounterVal - dwTimerClocksLow;
    
                  if( !dwTimerClocksLow || dwInitialVE_CounterVal == 1 )
                  {
                    if( qwCurrentCPU_Clocks%128 < 5 )
                    {
                      wVE_Timer_Counter = 0;
                    }
                  }
                }
              }
              else
              { // dwTimerClocksHi == 0
                wVE_Timer_Counter = dwInitialVE_CounterVal - dwTimerClocksLow;
              }
              
              WORD(0177710) = wVE_Timer_Counter;
            }
          }
        }
        break;
        
        // VE-Timer CSR
        case 0177712:
        {
          word wVE_Timer_CSR = WORD(0177712);
    
          if( Last_IO_Mode == IO_READ )
          {
            wLast_IO_Addr =  0177710;
            Make_IO();
            wLast_IO_Addr =  0177712;
            
            if( bVE_Expiry )
            {
              if( bVE_Monitor )
              {
                wVE_Timer_CSR |= BIT_7;
              }
              
              if( bVE_NoRestart && !bVE_NoReload )
              {
                wVE_Timer_CSR &= ~BIT_4;
              }
            }
            WORD(0177712) = wVE_Timer_CSR|0xFF00;
          }
          else
          { // Last_IO_Mode == IO_WRITE
            bVE_Expiry = false;
            
            bVE_Go = wVE_Timer_CSR & BIT_4;
    
            if( wVE_Timer_CSR & BIT_0 )
            {
              bVE_Go = false;
            }
    
            bVE_NoReload  = wVE_Timer_CSR & BIT_1;
            bVE_Monitor   = wVE_Timer_CSR & BIT_2;
            bVE_NoRestart = wVE_Timer_CSR & BIT_3;
    
            nVE_CPU_Clock_Divider = 128;
            
            if( wVE_Timer_CSR & BIT_5 )
            {
              nVE_CPU_Clock_Divider *= 16;
            }
    
            if( wVE_Timer_CSR & BIT_6 )
            {
              nVE_CPU_Clock_Divider *= 4;
            }
    
            
            QWORD qwCurrentCPU_RunTimeNS = qwCPU_TotalRunTimeNS;
            qwCurrentCPU_RunTimeNS += dwCurrentStepsLatencyNS;
            
            QWORD qwCurrentCPU_Clocks = qwCurrentCPU_RunTimeNS/uCPU_CycleNS;
            
            if( bVE_Go )
            {
              qwVE_TimerStart_CPU_Clocks = qwCurrentCPU_Clocks;
            }
    
            dwInitialVE_CounterVal = WORD(0177706);
            wVE_Timer_Counter = WORD(0177706);
    
            WORD(0177710) = wVE_Timer_Counter;
            WORD(0177712) = wVE_Timer_CSR|0xFF00;
          }
        }
        break;
      }
    }
    ...

    Добавлен файл VM1T8.zip, содержащий "1801VM1 Interrupts Test #8".
    Добавлен файл VM1VE8.zip, содержащий "1801VM1 VE-Timer Test #8".
    Добавлен файл VM1VE9.zip, содержащий "1801VM1 VE-Timer Test #9".
    Добавлен файл VM1E10.zip, содержащий "1801VM1 VE-Timer Test #10".
    Добавлен файл VM1E11.zip, содержащий "1801VM1 VE-Timer Test #11".
    Вложения Вложения
    • Тип файла: zip VM1T8.zip (7.6 Кб, Просмотров: 553)
    • Тип файла: zip VM1VE8.zip (9.2 Кб, Просмотров: 574)
    • Тип файла: zip VM1VE9.zip (8.4 Кб, Просмотров: 525)
    • Тип файла: zip VM1E10.zip (9.0 Кб, Просмотров: 547)
    • Тип файла: zip VM1E11.zip (8.3 Кб, Просмотров: 554)
    Последний раз редактировалось Patron; 25.02.2011 в 15:22.

  2. #1
    С любовью к вам, Yandex.Direct
    Размещение рекламы на форуме способствует его дальнейшему развитию

  3. #2
    Guru
    Регистрация
    11.09.2009
    Адрес
    Москва
    Сообщений
    4,777
    Спасибо Благодарностей отдано 
    2
    Спасибо Благодарностей получено 
    122
    Поблагодарили
    61 сообщений
    Mentioned
    16 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Дополнительные тесты позволили выявить несколько ошибок и неточностей в первой версии алгоритма.

    Вот новая версия алгоритма, ещё более точно (как я полагаю) эмулирующая работу встроенного таймера процессора 1801ВМ1:

    Код:
    typedef unsigned long long QWORD;
    typedef unsigned long      DWORD;
    typedef unsigned short     word;
    
    #define WORD(a) (*(unsigned short *)&aMemory[a])
    
    PowerON()
    {
      WORD(0177706) = wCPU_ID;
      wVE_Timer_Counter = 0177777;
      WORD(0177710) = wVE_Timer_Counter;
      WORD(0177712) = 0177400;
      nVE_MonitoredExpiryCount = 0;
      bVE_Expiry = false;
      bVE_Go = false;
    }
    
    Reset()
    {
      WORD(0177712) = 0;
      Last_IO_Mode = IO_WRITE;
      wLast_IO_Addr =  0177712;
      Make_IO();
    }
    
    Make_IO()
    {
      switch( wLast_IO_Addr )
      {
        // VE-Timer Counter Initial Value
        case 0177706:
        {
          if( Last_IO_Mode == IO_WRITE )
          {
            Last_IO_Mode = IO_READ;
            wLast_IO_Addr =  0177710;
            Make_IO();
            Last_IO_Mode = IO_WRITE;
            wLast_IO_Addr =  0177706;
    
            if( bVE_Go && !bVE_NoReload && !bVE_NoRestart )
            {
              QWORD qwCurrentCPU_RunTimeNS = qwCPU_TotalRunTimeNS;
              qwCurrentCPU_RunTimeNS += dwCurrentStepsLatencyNS;
              QWORD qwCurrentCPU_Clocks = qwCurrentCPU_RunTimeNS/uCPU_CycleNS;
              
              if( qwCurrentCPU_Clocks >= qwNextVE_TimerStart_CPU_Clocks )
              {
                qwPrevVE_TimerStart_CPU_Clocks = qwNextVE_TimerStart_CPU_Clocks;
                dwPrevInitialVE_CounterVal     = dwNextInitialVE_CounterVal;
                
                QWORD qwUsedCPU_Clocks = qwCurrentCPU_Clocks - qwPrevVE_TimerStart_CPU_Clocks;
                QWORD qwCurrentVE_Timer_Clocks = qwUsedCPU_Clocks/nVE_CPU_Clock_Divider;
              
                DWORD dwTimerClocksLow = qwCurrentVE_Timer_Clocks % dwPrevInitialVE_CounterVal;
                
                qwNextVE_TimerStart_CPU_Clocks = qwPrevVE_TimerStart_CPU_Clocks;
                qwNextVE_TimerStart_CPU_Clocks += ( dwPrevInitialVE_CounterVal - dwTimerClocksLow ) * nVE_CPU_Clock_Divider;
              }
              
              dwNextInitialVE_CounterVal = WORD(0177706);
              if( !dwNextInitialVE_CounterVal ) { dwNextInitialVE_CounterVal = 0x10000; }
            }
          }
        }
        break;
        
        // VE-Timer Counter
        case 0177710:
        {
          if( Last_IO_Mode == IO_WRITE )
          {
            WORD(0177710) = wVE_Timer_Counter;
          }
          else
          {
            if( bVE_Go )
            {
              QWORD qwCurrentCPU_RunTimeNS = qwCPU_TotalRunTimeNS;
              qwCurrentCPU_RunTimeNS += dwCurrentStepsLatencyNS;
              QWORD qwCurrentCPU_Clocks = qwCurrentCPU_RunTimeNS/uCPU_CycleNS;
              
              QWORD qwVE_TimerStart_CPU_Clocks = qwPrevVE_TimerStart_CPU_Clocks;
              DWORD dwInitialVE_CounterVal     = dwPrevInitialVE_CounterVal;
              
              if( qwCurrentCPU_Clocks >= qwNextVE_TimerStart_CPU_Clocks )
              {
                bVE_Expiry = true;
                
                if( !bVE_NoRestart )
                {
                  qwVE_TimerStart_CPU_Clocks = qwNextVE_TimerStart_CPU_Clocks;
                  dwInitialVE_CounterVal     = dwNextInitialVE_CounterVal;
                }
              }
              
              QWORD qwUsedCPU_Clocks = qwCurrentCPU_Clocks - qwVE_TimerStart_CPU_Clocks;
              QWORD qwCurrentVE_Timer_Clocks = qwUsedCPU_Clocks/nVE_CPU_Clock_Divider;
              
              DWORD dwTimerClocksHi  = qwCurrentVE_Timer_Clocks / dwInitialVE_CounterVal;
              DWORD dwTimerClocksLow = qwCurrentVE_Timer_Clocks % dwInitialVE_CounterVal;
    
              if( bVE_Expiry )
              {
                if( bVE_Monitor && (dwTimerClocksHi > nVE_MonitoredExpiryCount) )
                {
                  nVE_MonitoredExpiryCount = dwTimerClocksHi;
                }
                
                if( bVE_NoReload )
                {
                  wVE_Timer_Counter = dwInitialVE_CounterVal - dwTimerClocksLow;
                }
                else if( bVE_NoRestart )
                {
                  bVE_Go = false;
                  
                  wVE_Timer_Counter = WORD(0177706);
    
                  if( dwTimerClocksHi == 1 && !dwTimerClocksLow )
                  {
                    if( qwCurrentCPU_Clocks%128 < 5 )
                    {
                      wVE_Timer_Counter = 0;
                    }
                  }            
                }
                else
                {
                  wVE_Timer_Counter = dwInitialVE_CounterVal - dwTimerClocksLow;
                  
                  if( !dwTimerClocksLow )
                  {
                    if( qwCurrentCPU_Clocks%128 < 5 )
                    {
                      wVE_Timer_Counter = 0;
                    }
                  }
                }
              }
              else
              { // !bVE_Expiry
                wVE_Timer_Counter = dwInitialVE_CounterVal - dwTimerClocksLow;
              }
              
              WORD(0177710) = wVE_Timer_Counter;
            }
          }
        }
        break;
        
        // VE-Timer CSR
        case 0177712:
        {
          IO_ModeType IO_Mode = Last_IO_Mode;
          
          Last_IO_Mode = IO_READ;
          wLast_IO_Addr =  0177710;
          Make_IO();
          wLast_IO_Addr =  0177712;
          Last_IO_Mode = IO_Mode;
    
          word wVE_Timer_CSR = WORD(0177712);
    
          if( Last_IO_Mode == IO_READ )
          {
            if( bVE_Expiry )
            {
              if( bVE_Monitor && nVE_MonitoredExpiryCount > 1 )
              {
                wVE_Timer_CSR |= BIT_7;
              }
              
              if( bVE_NoRestart && !bVE_NoReload )
              {
                wVE_Timer_CSR &= ~BIT_4;
              }
            }
            WORD(0177712) = wVE_Timer_CSR|0xFF00;
          }
          else
          { // Last_IO_Mode == IO_WRITE
          
            if( nVE_MonitoredExpiryCount )
            {
              nVE_MonitoredExpiryCount = 2;
            }
          
            bVE_Expiry = false;
            
            bVE_Go = wVE_Timer_CSR & BIT_4;
    
            if( wVE_Timer_CSR & BIT_0 )
            {
              bVE_Go = false;
            }
    
            bVE_NoReload  = wVE_Timer_CSR & BIT_1;
            bVE_Monitor   = wVE_Timer_CSR & BIT_2;
            bVE_NoRestart = wVE_Timer_CSR & BIT_3;
    
            nVE_CPU_Clock_Divider = 128;
            
            if( wVE_Timer_CSR & BIT_5 )
            {
              nVE_CPU_Clock_Divider *= 16;
            }
    
            if( wVE_Timer_CSR & BIT_6 )
            {
              nVE_CPU_Clock_Divider *= 4;
            }
    
            
            if( bVE_Go )
            {
              QWORD qwCurrentCPU_RunTimeNS = qwCPU_TotalRunTimeNS;
              qwCurrentCPU_RunTimeNS += dwCurrentStepsLatencyNS;
              QWORD qwCurrentCPU_Clocks = qwCurrentCPU_RunTimeNS/uCPU_CycleNS;
              
              QWORD qwVE_TimerStart_CPU_Clocks = qwCurrentCPU_Clocks;
              qwVE_TimerStart_CPU_Clocks -= qwVE_TimerStart_CPU_Clocks%128;
    
              QWORD dwInitialVE_CounterVal = WORD(0177706);
              if( !dwInitialVE_CounterVal ) { dwInitialVE_CounterVal = 0x10000; }
              
              dwPrevInitialVE_CounterVal = dwInitialVE_CounterVal;
              dwNextInitialVE_CounterVal = dwInitialVE_CounterVal;
              
              if( bVE_NoReload )
              {
                dwNextInitialVE_CounterVal = 0x10000;
              }
    
              qwPrevVE_TimerStart_CPU_Clocks = qwVE_TimerStart_CPU_Clocks;
              qwNextVE_TimerStart_CPU_Clocks = qwVE_TimerStart_CPU_Clocks;
              qwNextVE_TimerStart_CPU_Clocks += dwInitialVE_CounterVal * nVE_CPU_Clock_Divider;
            }
    
            wVE_Timer_Counter = WORD(0177706);
            
            WORD(0177710) = wVE_Timer_Counter;
            WORD(0177712) = wVE_Timer_CSR|0xFF00;
          }
        }
        break;
      }
    }

  4. #3
    Veteran Аватар для nzeemin
    Регистрация
    20.12.2005
    Адрес
    Москва
    Сообщений
    1,996
    Спасибо Благодарностей отдано 
    1,059
    Спасибо Благодарностей получено 
    1,223
    Поблагодарили
    478 сообщений
    Mentioned
    15 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Patron, не могли бы вы сделать ещё текстовое описание этого алгоритма. Код конечно кодом, тесты тестами, но чтобы до конца понять этот алгоритм нужно погрузиться в этот код и тесты до вашего уровня. А после этого нужно переписать алгоритм под своё окружение. В общем, текст с выводами полученными из тестов -- сильно бы помог.

  5. #4
    Guru
    Регистрация
    11.09.2009
    Адрес
    Москва
    Сообщений
    4,777
    Спасибо Благодарностей отдано 
    2
    Спасибо Благодарностей получено 
    122
    Поблагодарили
    61 сообщений
    Mentioned
    16 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от nzeemin Посмотреть сообщение
    Patron, не могли бы вы сделать ещё текстовое описание этого алгоритма. Код конечно кодом, тесты тестами, но чтобы до конца понять этот алгоритм нужно погрузиться в этот код и тесты до вашего уровня. А после этого нужно переписать алгоритм под своё окружение. В общем, текст с выводами полученными из тестов -- сильно бы помог.
    Мои планы следующие - я разрабатываю универсальный API эмуляции и в процессе этой разработки выяснилось, что необходимо разработать базовый универсальный API, который позволял бы экземпляру объекта-клиента подключаться к экземпляру объекта-сервера, задействуя (только в момент начального подключения) прокси-интерфейс, необходимый для согласования различных версий универсального API, возможно использовавшихся при компиляции модулей эмуляции, экспортирующих связываемые объект-клиент и объект-сервер.

    Если сказать то же самое более простым языком - добавление (при развитии API ) новых методов в таблицу виртуальных функций делает невозможной прямую динамическую компоновку порождённых в эмуляторе экземпляров объектов, созданных разработчиками с использованием библиотек API разных версий. Использование прокси-интерфейса, осуществляющего динамическую компоновку экземпляров объектов модулей эмуляции не по номеру позиции метода в таблице виртуальных функций API, а по его сигнатуре - cделает универсальный API подлинно универсальным.

    Сейчас все работы по этой тематике у меня отложены ( предположительно до осени ). После завершения разработки первой версии универсального API - я выложу различные исходники, включая почти референсную модель процессора 1801ВМ1 (исследование растактовок которого, начатое Вами - ещё не закончено ).

    Пока же - вот предварительный комментарий к выложенному исходнику ( если дополнительно нужен комментарий кода - то насколько подробный ? ).

    Эмуляция в данном случае осуществляется покомандно. Движок эмулятора вызывает у модуля эмуляции процессора метод Steps(nSteps), передавая в качестве аргумента количество команд, которые нужно сэмулировать, и получая в глобальной переменной dwCurrentStepsLatencyNS - общее время в "эмуляторных" наносекундах, которое бы затратил реальный процессор на выполнение сэмулированных команд, которое движок затем прибавляет к общему "накопителю" qwCPU_TotalRunTimeNS, а dwCurrentStepsLatencyNS обнуляет.

    Когда процессор обращается к адресу на странце ввода-вывода - происходит вызов метода Make_IO(), с предварительным занесением аргументов в переменные wLast_IO_Addr и Last_IO_Mode (выглядит довольно глупо - переделаю на обычную передачу аргументов через стек).

    Т.е. основная особенность данного алгоритма эмуляции таймера состоит в том, что интересующие значения рассчитываются не на каждом "тике" тактовой частоты эмулируемого процессора (т.к. эмулируются не отдельные тики, а команды целиком), а только в те моменты, когда при эмуляции выполняемых эмулируемым процессором команд эти значения запрашиваются чтением регистров таймера.

    Эмулировать довольно сложное поведение таймера только в моменты обращения к регистрам оказалось довольно непросто, но зато накладные расходы на эмуляцию равны нулю.

    Если запустить таймер в любом режиме записью в регистр, а затем хоть через час прочитать значение счётчика - вся эмуляция всей истории изменений состояния таймера будет за один шаг выполнена в момент чтения регистра счётчика.

    Если же не читая счётчик - остановить таймер повторной записью в регистр, то хоть за целый час, хоть за целый год максимально корректной эмуляции работы таймера, предшествовашей его остановке - программа эмуляции вообще не использует ни такта времени центрального процессора хост-машины.
    Последний раз редактировалось Patron; 10.07.2011 в 20:54.

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Похожие темы

  1. Форт для процессора 6502
    от rw6hrm в разделе Зарубежные компьютеры
    Ответов: 7
    Последнее: 15.10.2010, 15:01
  2. VHDL-модель процессора Motorola 68000
    от ILoveSpeccy в разделе Несортированное железо
    Ответов: 60
    Последнее: 30.01.2008, 17:09
  3. О шине данных процессора...
    от ILoveSpeccy в разделе Несортированное железо
    Ответов: 25
    Последнее: 01.08.2007, 16:42
  4. Нужна помощь с выбором процессора Z80...
    от ILoveSpeccy в разделе Несортированное железо
    Ответов: 6
    Последнее: 04.05.2007, 02:38

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •