Форум Эму-Россия http://forum.emu-russia.net/ |
|
[АРХИВ] Таймеры и Прерывания http://forum.emu-russia.net/viewtopic.php?f=13&t=63 |
Страница 1 из 1 |
Автор: | Keyman [ 15 авг 2007, 15:36 ] |
Заголовок сообщения: | [АРХИВ] Таймеры и Прерывания |
Автор: 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; |
Автор: | Keyman [ 15 авг 2007, 15:48 ] |
Заголовок сообщения: | Re: Таймеры и Прерывания [АРХИВ] |
[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, поясни как ты намерен считать програмное время в отдельном потоке, откуда данные брать будешь? |
Автор: | Keyman [ 15 авг 2007, 15:56 ] |
Заголовок сообщения: | Re: Таймеры и Прерывания [АРХИВ] |
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, весь рекомпилированый код биоса умещается. |
Автор: | R4kk00n [ 18 авг 2007, 18:10 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
Кто бы ещё код отформатировал как Код: for (int i=0; i<10; i++) { foo(bar); } |
Автор: | Keyman [ 18 авг 2007, 22:32 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
А что не так? Поясни. |
Автор: | R4kk00n [ 22 авг 2007, 00:28 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
Сейчас код неформатирован. Поэтому его читать местами трудно. Другое дело, что это лучше бы сделать оригинальным авторам или лицам заинтересованым (нет, я не настолько заинтересован...) [хотя заинтересованные точно также могут что-то неправильно прочитать] |
Автор: | madmonkey [ 22 авг 2007, 00:51 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
Artistic Style отформатирует другое дело что такие маленькие примеры кода нет особого смысла форматировать |
Автор: | Keyman [ 24 авг 2007, 22:58 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
Я в программировании плохо понимаю. Так что стараюсь в точности копировать присланные мне темы, уж извините. |
Автор: | Антоха 007 [ 24 авг 2007, 23:51 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
Кода здесь немного, так что я могу в свободное время его перековырнуть, чтобы он стал более читаемым. Другое дело, что форум, насколько я знаю, поддерживает в теге code только php, но это ничего, подсветка все равно почти одинаковая получится. Другое дело, как организовать все это. Keyman, если надумаете, я в вашем распоряжении, ЛС вперед. |
Автор: | Антоха 007 [ 26 авг 2007, 11:39 ] |
Заголовок сообщения: | Re: [АРХИВ] Таймеры и Прерывания |
Смотрим... Код отформатирован? Да. Читаем легче? Да, вполне возможно. И сообщения станут компактнее. Код: 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; |
Страница 1 из 1 | Часовой пояс: UTC + 3 часа [ Летнее время ] |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |