Сообщения без ответов | Активные темы Текущее время: 28 апр 2024, 15:05



Ответить на тему  [ Сообщений: 10 ] 
 [АРХИВ] Таймеры и Прерывания 
Автор Сообщение
Сообщение 15 авг 2007, 15:36
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Автор: Wind 19 Янв 2005.

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

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

Например:
В случае Sega Dreamcast, частота процессора составляет 200000000 тактов в секунду. Каждая инструкция выполняется за n-ое число тактов (какое именно указано в документации), по окончании исполнения очередной инструкции просто прибавляется это число к «некой переменой», в итоге, когда значение переменой достигнет значения в 200000000 будет означать изменение часов на одну секунду.
В Sh4 есть два блока, отвечающие за таймеры RTS, TMU первый отвечает за текущую дату и время с точностью до секунды (т.е. изменяется каждую секунду) одна секунда это большой промежуток и поэтому для синхронизации задач он не используется этот блок можно синхронизировать с часами системы, для которой пишется эмулятор.
TMU имеет, изменяемую скорость хода часов.
Для управления этим блоком используются регистры расположены по адресам:

typedef struct
{
u8 tocr; // 0xffd80000
u8 tstr; // 0xffd80004
u32 tcor0; // 0xffd80008
u32 tcnt0; // 0xffd8000c
u16 tcr0; // 0xffd80010
u32 tcor1; // 0xffd80014
u32 tcnt1; // 0xffd80018
u16 tcr1; // 0xffd8001c
u32 tcor2; // 0xffd80020
u32 tcnt2; // 0xffd80024
u16 tcr2; // 0xffd80028
u32 tcpr2; // 0xffd8002c
} TMU;

Как видно из описания этой структуры, имеется три таймера (каждый можно настроить на свою частоту). Регистр tstr, нижние три бита которого используется, для включения соответствующего «канала». В регистрах tcr0, tcr1, tcr2 выставляется частота. Регистры tcnt0, tcnt1, tcnt2 содержат текущее «время». Регистры tcor0, tcor1, tcor2 содержат значение, которое записывается в соответствующий tcnt во время автоматической перезагрузки канала.
Пример реализации эмуляции TMU:
В конце каждой инструкции перехода вставляем вызов ф-ии CpuTest().

void CpuTest()
{
if (sh4.cycle >= 2000) Tmu();
}
2000 означает обновление значений каждую миллисекунду.

u8 speed[8] = {2, 4, 6, 8, 10, 0, 0, 0};

void Tmu()
{
if(sh4.tmu.tstr & 0x1) sh4.tmu.tcnt0 -= sh4.cycle >> (2 + speed[sh4.tmu.tcr0 & 7]);
if(sh4.tmu.tstr & 0x2) sh4.tmu.tcnt1 -= sh4.cycle >> (2 + speed[sh4.tmu.tcr1 & 7]);
if(sh4.tmu.tstr & 0x4) sh4.tmu.tcnt2 -= sh4.cycle >> (2 + speed[sh4.tmu.tcr2 & 7]);
}

if(sh4.tmu.tstr & 0x1) если канал включен значить необходимо обновить значение.
sh4.tmu.tcntN -= sh4.cycle >> (2 + speed[sh4.tmu.tcrN & 7]);
Обновить канал в зависимости от скорости выставленной в регистре sh4.tmu.tcrN.

Примечание: реализация упрощена для простоты понимания.

Небольшое дополнение (правка):

Привожу более понятную и более правильную реализацию обновления TMU:

u8 speed[8] = {2, 4, 6, 8, 10, 0, 0, 0};
u8 speedmask[8] = {1, 15, 63, 255, 1023, 0, 0, 0};

u32 tcnt0_low;
u32 tcnt1_low;
u32 tcnt2_low;

void Tmu()
{
if(sh4.tmu.tstr & 0x1)
{
sh4.tmu.tcnt0 -= (sh4.cycle + tcnt0_low) >> speed[sh4.tmu.tcr0 & 7];
tcnt0_low = (sh4.cycle + tcnt0_low) & speedmask [sh4.tmu.tcr0 & 7];
}
if(sh4.tmu.tstr & 0x2)
{
sh4.tmu.tcnt0 -= (sh4.cycle + tcnt1_low) >> speed[sh4.tmu.tcr0 & 7];
tcnt0_low = (sh4.cycle + tcnt1_low) & speedmask [sh4.tmu.tcr0 & 7];

}
if(sh4.tmu.tstr & 0x4)
{
sh4.tmu.tcnt0 -= (sh4.cycle + tcnt2_low) >> speed[sh4.tmu.tcr0 & 7];
tcnt0_low = (sh4.cycle + tcnt2_low) & speedmask [sh4.tmu.tcr0 & 7];
}
}

void CpuTest()
{
Tmu();
}

sh4.tmu.tcnt0 -= (sh4.cycle + tcnt0_low) >> speed[sh4.tmu.tcr0 & 7];
В этой строке получаем значение на которое изменяем sh4.tmu.tcnt0.

tcnt0_low = (sh4.cycle + tcnt0_low) & speedmask [sh4.tmu.tcr0 & 7];

В этой строке сохраняем остаток от деления и сохраняем для следующего обновления.


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

Рисование очередного кадра сцены происходит обычно во время обратного хода луча. Это прерывание происходит 59.94 раз в секунду для NTSC (для 50 PAL). Для Sega Dreamcast это должно происходить 200000000/59.94 т.е
if (sh4.cycle >= 3333333) Vsync();

Прерывание может произойти только если оно разрешено, sr.imask используется для маскирования прерываний. Если необходимое прерывание разрешено (в данном случае девятое) необходимо произвести следующее (для Sh4).

sh4.spc = sh4.pc;
sh4.ccn.intevt = 320; // сюда пишется вектор, зависит от номера прерывания
sh4.sgr = sh4.cpu.r[15].UL;
sh4.ssr = sh4.sr;
sh4.sr |= 0x700000f0;
sh4.pc = sh4.vbr + 0x00000600;

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Последний раз редактировалось Keyman 15 авг 2007, 23:01, всего редактировалось 1 раз.



Сообщение 15 авг 2007, 15:48
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
[bETA]mEN
Цитата:
Wind писал:
Это прерывание происходит 50.94 раз в секунду для NTSC (для 50 PAL). Для Sega Dreamcast это должно происходить 200000000/50.94 т.е
if (sh4.cycle >= 3333333) Vsync();


50.94??? А может все-таки 59.94

Wind
Может быть, очень может быть, одним словм не то написал

Organic

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

На практике понятие "время" удобней делить на два понятия : реальное время и программное время. Реальное время - это то время, которое показвает компьютер, то есть реальное местное время. Программное время - это время, которое показывает некий программный счетчик. К примеру, как писал Wind - счетчик инструкций.
Так вот - в эмуляторе реальное время НЕ СОВПАДАЕТ с програмным. Т.е. эмулятор никокгда не выполнит ровно столько-же инструкций, за одну *реальную* секунду, сколько их выполнит реальный процессор. Это сделать невозможно.
Плохо это, или хорошо? На самом деле эмулятору на это наплевать. Ведь если посудить, то за *равные* промежутки реального и программного времени, в эмуляторе выполнится столько-же инструкций, и возникнет столько-же прерываний, сколько и на реальном процессоре. Соблюдение этого правила и является основной задачей *правильной* эмуляции тайминга.

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

Несколько советов :
- При написании эмулятора важно четко понимать - с каким "временем" ты имеешь дело!
- При эмуляции DMA чаще всего можно проигнорировать факт параллельной записи в память процессора и DMA-контроллера. Т.е. для DMA можно пренебречь точным таймингом.

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

- Удобно завести константу - ONE_SECOND, которая будет показывать значение программного счетчика, соответствующего одной программной секунде.

- Не пытайтесь эмулировать таймеры реальным временем. Хлопот не оберетесь.
0 error(s), 0 warning(s)

RomikB
Не знаю насколько я нарушил советы Органика...

У меня используеться смесь реального времени с програмным. Вначале задаеться длина програмной секунды, по умолчанию = секунда реальная. И 60 раз за эту секунду(реально-програмную) вызываеться програмный обрабочик VSync. Т.е. все програмные таймеры привязываються (через коэффициент) именно к реальному времени.
Насколько это правильно сказать не могу, так как тестировал только на демках, на которых всё прекрасно работает. Но ничего проще и удобней для использования вместе с динамическим рекомпилятором в котором нету никакого подсчета инструкций я не нашел.

Wind
Organic красиво расписал надо было мне всю статейку в таком духе расписать. Такой абстрактный подход весьама удобен для понимания. Мне прсто хотелось привести конретный пример. Кстати может ответищь на вопрос заданый в топике по GQ насчет "динарека"
RomikB я не понял ты нашел какие-то сложности подсчета инструкций при использовании динамической рекомпиляции? Для меня наооборот проще подсчитать такты эмулируемой системы не жели использовать реалные часы.

RomikB
Wind, приведи пример такого подсчета, чтобы он не сказывался на скорости эмуляции и на размере кода?
А считать реальные часы очень просто...

Wind
Ну конечно такого примера я привести не смогу.
Но неужели плюс одна иструкция на каждую команду так уж существенно. А обновление таймеров производи, как я уже написал выше в конце инструкций перехода. В сочетании с динамической рекомпиляцией этот подход очень удобен: исполнили очередной блок обновли таймеры.

RomikB
Цитата:
Wind писал:
Ну конечно такого примера я привести не смогу.
Но неужели плюс одна иструкция на каждую команду так уж существенно. А обновление таймеров производи, как я уже написал выше в конце инструкций перехода. В сочетании с динамической рекомпиляцией этот подход очень удобен: исполнили очередной блок обновли таймеры.

Ну одна иструкция это 5 байт и 3 такта.

inc [mem]

Если учесть что одна рекомпилированная иструкция, кроме переходов, занимает 16 байт и выполняеться за 4-10 тактов, то вполне немало...

Wind

Я тебе не предлагаю вставлять счетчик тактов в "рекомпилированый код", проще увеличивать счетчик еще на стадии дисассемблирования-рекомпилирования.

Цитата:
RomikB писал:
Если учесть что одна рекомпилированная иструкция, кроме переходов, занимает 16 байт и выполняеться за 4-10 тактов, то вполне немало...


Это ты я так пониямаю говоришь о MIPS3000, вопрос в том, а какое среднее число тактов получается. Если оно стремится к 10 то, что-то слишком много. У этого процессора нет "флагового регистра", а без него эмуляция для меня лично значительно упрощается.

Кстати как у тебя с опытом эмуляции Видео?

RomikB

нет я говорю уже про рекомпилированный код...

"занимает 16 байт и выполняеться за 4-10 тактов, то вполне немало..."

Эмуляция видео - никакая - никогда не занимался графикой.

Organic

Можно считать программное время отдельным потоком (thread). Тогда не нужно генерировать дополнительный код для инкремента счетчика.
0 error(s), 0 warning(s)

RomikB

Цитата:
Organic писал:
Можно считать программное время отдельным потоком (thread). Тогда не нужно генерировать дополнительный код для инкремента счетчика.


В этом случае придеться потратить кучу времени и ресурсоф на синхронизацию главного потока эмуляции с потоком этого счетчика.

Organic

Цитата:
Цитата:
В этом случае придеться потратить кучу времени и ресурсоф на синхронизацию главного потока эмуляции с потоком этого счетчика

А вот я тоже думаю.. Надо-ли синхронизировать поток CPU с потоком программного времени? Так-ли это необходимо? Ведь всё, что делает поток времени - увеличивает счетчик. Тем же самым занимается поток CPU.
0 error(s), 0 warning(s)

RomikB

Org, поясни как ты намерен считать програмное время в отдельном потоке, откуда данные брать будешь?

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Сообщение 15 авг 2007, 15:56
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Organic
ну.. типа так:

int Counter;

В ОДНОМ ПОТОКЕ
Код:

while(1)
{
Выполняем 1 инструкцию CPU

Counter++
}



В ДВУХ ПОТОКАХ
Код:

while(1)
{
Выполняем 1 инструкцию CPU
}



Код:

while(1)
{
Counter++
}

0 error(s), 0 warning(s)

Shiru
Какой ужас... И зачем такое делать?

RomikB
Бррр, Орг, обьясни в чем смысл твоего примера двухпоточности? По моему он бессмысленен?

Wind
Я же писал считаем такты во время рекомпиляции и в конце блока просто добавляем полученое значение к счетчику(увелечение счетчика находится внутри блока одной косанодой add mem, imm) это самый простой и самый правильный вариантс и ничего более придумывать не нужно.

RomikB
Возьмем стандартную ссылку назад

Блок 1:
много
много
инструкций
ну
штук
30
(тут я понимаю ты добавлеешь кучу тактов)
условный переход далеко вперед:
Блок 2:
одна иструкция
переход назад на конец блока 1

И вот этот самый цикл выполняеться ооочень долго. (пример из практики).

Посчитай примерную погрешность своего способа.

Wind
Что-то я не очень понял вопрос, так что отвечу как понял.
Вообщем блоки имеют такой вид:
сначала рекомпилированый код далее установка нового значения PC и в конце блока увелечение счетчика на число тактов в этом блоке т.е. сама инструкция которая увеличивает счетчик находится внутри самого блока и если блок выполняется повторно то счетчик в очередной раз увеличится на требуемое значение и никакой погрешности нет.

RomikB
Смысл в том что счетчик то увеличиться на 30 например, хотя было выполнено всего 2 инструкции. (последнии инструкции блока) А цикл от нуля до 0x003a0000, вот и посчитай погрешность.

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

RomikB
Цитата:
Wind писал:
Нет блок повторно выполняется только в случае если он начинается с той же позиции, даже если блок до этого места уже скомпилен и переход происходит внутрь то компилится новый блок и соотвественно счетчик после выполнения изменится на нужное значениес и не будет никакой погрешности.

Ладно рассмотрим цикл:

label1:
i = 0;
label2:
j = 0;
label3:
code
j++
if( j < jmax ) goto label 3
i++
if( i < imax ) goto label 2

это интерпретация обычного двойного цикла
for( i = 0 ; i < imax ; i++ )
for( j = 0 ; j < jmax ; j++ )
somecode;

теперь возьми твою модель и скажи сколько раз будет рекомпилирован тобой блок начинающийся от "label1" и заканчивающийся "if( j < jmax ) goto label 3" при выполнении этого цикла скажем при i = 1000, j =100?
Двойной цикл штука часто употребляемая, так что слишком уж ресурсоемким получиться процесс рекомпиляции относительного самого процесса эмуляции.

Wind
Уже рекомпилированый блок не куда не удаляется он существует до тех пор, пока не истратится вся память используемая под рекомпилированый код(я пока остановился на 16мб).

RomikB
Цитата:
Wind писал:
Уже рекомпилированый блок не куда не удаляется он существует до тех пор, пока не истратится вся память используемая под рекомпилированый код(я пока остановился на 16мб).

Ясно. Давай пиши описание в соседней теме, затем буду тебя раскритиковывать =)

16 мегов это под какую систему? NES SNES PSX SEGA PS2?

Wind

Цитата:
RomikB писал:
Wind писал:
Уже рекомпилированый блок не куда не удаляется он существует до тех пор, пока не истратится вся память используемая под рекомпилированый код(я пока остановился на 16мб).
Ясно. Давай пиши описание в соседней теме, затем буду тебя раскритиковывать =)



Критика, да приятная перспектива :), а вообще сейчас мало свободного времени, но я все же постараюсь как можно скорей написать.
Цитата:
RomikB писал:
16 мегов это под какую систему? NES SNES PSX SEGA PS2?


16 метров это я еще до конца не определился, просто пока вроде норма. Система Sega Dreamcast, весь рекомпилированый код биоса умещается.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Сообщение 18 авг 2007, 18:10
Профиль
Аватара пользователя

Зарегистрирован:
22 июл 2007, 21:16
Сообщения: 88
Кто бы ещё код отформатировал как
Код:
for (int i=0; i<10; i++) {
    foo(bar);
...

_________________
We're talkin' about givin' the drummer some...


Сообщение 18 авг 2007, 22:32
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
А что не так? Поясни.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Сообщение 22 авг 2007, 00:28
Профиль
Аватара пользователя

Зарегистрирован:
22 июл 2007, 21:16
Сообщения: 88
Сейчас код неформатирован. Поэтому его читать местами трудно. Другое дело, что это лучше бы сделать оригинальным авторам или лицам заинтересованым (нет, я не настолько заинтересован...) [хотя заинтересованные точно также могут что-то неправильно прочитать]

_________________
We're talkin' about givin' the drummer some...


Сообщение 22 авг 2007, 00:51
Профиль

Зарегистрирован:
25 июл 2007, 18:11
Сообщения: 4
Artistic Style отформатирует
другое дело что такие маленькие примеры кода нет особого смысла форматировать


Сообщение 24 авг 2007, 22:58
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Я в программировании плохо понимаю. Так что стараюсь в точности копировать присланные мне темы, уж извините.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Сообщение 24 авг 2007, 23:51
Профиль

Зарегистрирован:
22 июл 2007, 01:40
Сообщения: 7
Кода здесь немного, так что я могу в свободное время его перековырнуть, чтобы он стал более читаемым. Другое дело, что форум, насколько я знаю, поддерживает в теге code только php, но это ничего, подсветка все равно почти одинаковая получится. Другое дело, как организовать все это.
Keyman, если надумаете, я в вашем распоряжении, ЛС вперед.

_________________
CPU not found. Starting software emulation...


Сообщение 26 авг 2007, 11:39
Профиль

Зарегистрирован:
22 июл 2007, 01:40
Сообщения: 7
Смотрим...
Код отформатирован? Да.
Читаем легче? Да, вполне возможно.
И сообщения станут компактнее.
Код:
u8 speed[8] = {2, 4, 6, 8, 10, 0, 0, 0}; 
u8 speedmask
[8] = {1, 15, 63, 255, 1023, 0, 0, 0}; 

u32 tcnt0_low
; 
u32 tcnt1_low
; 
u32 tcnt2_low
; 

void Tmu
() 
{ 
    if 
(sh4.tmu.tstr & 0x1) 
    
{ 
        sh4
.tmu.tcnt0 -= (sh4.cycle + tcnt0_low) >> speed[sh4.tmu.tcr0 & 7]; 
        tcnt0_low 
= (sh4.cycle + tcnt0_low) & speedmask [sh4.tmu.tcr0 & 7]; 
    
} 
    if 
(sh4.tmu.tstr & 0x2) 
    
{ 
        sh4
.tmu.tcnt0 -= (sh4.cycle + tcnt1_low) >> speed[sh4.tmu.tcr0 & 7]; 
        tcnt0_low 
= (sh4.cycle + tcnt1_low) & speedmask [sh4.tmu.tcr0 & 7]; 
    
} 
    if 
(sh4.tmu.tstr & 0x4) 
    
{ 
        sh4
.tmu.tcnt0 -= (sh4.cycle + tcnt2_low) >> speed[sh4.tmu.tcr0 & 7]; 
        tcnt0_low 
= (sh4.cycle + tcnt2_low) & speedmask [sh4.tmu.tcr0 & 7]; 
    
} 
} 

void CpuTest
() 
{ 
    Tmu
(); 
} 

/* ... */

sh4.tmu.tcnt0 -= (sh4.cycle + tcnt0_low) >> speed[sh4.tmu.tcr0 & 7]; 
/* В этой строке получаем значение на которое изменяем sh4.tmu.tcnt0. */

tcnt0_low = (sh4.cycle + tcnt0_low) & speedmask [sh4.tmu.tcr0 & 7]; 
/* В этой строке сохраняем остаток от деления и сохраняем для следующего обновления. */

/* ... */
 

Код:
label1: 
    i 
= 0; 
    
label2
: 
    j 
= 0; 
    
label3
: 
    
    
/* code */
    
    j
++; 
    if 
( j < jmax ) goto label 3;
    
    i
++; 
    if 
( i < imax ) goto label 2;

/* это интерпретация обычного двойного цикла */
for ( i = 0 ; i < imax ; i++ ) 
    for 
( j = 0 ; j < jmax ; j++ ) 
        somecode
; 
Если надо, я могу весь код отформатировать и выложить ниже.

_________________
CPU not found. Starting software emulation...


Показать сообщения за:  Поле сортировки  
Ответить на тему   [ Сообщений: 10 ] 

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 59


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

Найти:
Перейти:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by STSoftware for PTF (mod by Zeru-j).
Русская поддержка phpBB