ZXNet эхоконференция «zxnet.pc»


тема: мысли по написанию модуля эмуляции z80 на С



от: SMT
кому: All
дата: 22 Jan 2006
Hello, SMT если делать совсем универсальное ядро, нужно предусмотреть увеличение текущего времени и в функциях работы с памятью/портами - в некоторых моделях, да хоть в оригинальных 48/128, вырабатывается WAIT при обращении к порту #FE и половине страниц памяти. так же могут тормозить процессор и устройства типа скорпионовского контроллера пц-клавиатуры

от: SMT
кому: All
дата: 22 Jan 2006
Hello, boo_boo boo> , в которой понадобиться наследовать от класса Z80, не могу boo> представить ну можно наследовать, переопределив виртуальные ф-ции чтения портов/памяти, но это не освобождает от вызова через указатель. надо не наследовать от класса Z80, а делать типа этого: ┌─- CODE ─── CUniversalZ80WithoutMemPorts MainZ80; CUniversalZ80WithoutMemPorts MainZ80Dbg; CUniversalZ80WithoutMemPorts gsZ80; └── CODE ─── таких ядер легко можно наделать сколько надо, причём всё, что относится к памяти/портам будет заинлайнено. в принципе, в unreal 3 ядра. не знаю, стоит ли ради повышения скорости в отдельных случаях так сильно увеличивать размер конечного exe-шника. хотя, если теперь эмуляторы даже на жабе пишут, скоростью можно пожертвовать boo> где можно прочитать про этот самый кэш? слабо себе представляю, кто boo> он такой журналы чёрн.ворона#3, dejavu#7. это статическое ОЗУ 2-16k, припаиваемое параллельно ПЗУ. имеется небольшая схемка, которая включает его вместо пзу (например, вместо TR-DOS, позволяя программе в этом кеше обращаться сразу и ко всей 48-й памяти, и к портам дисковода). если ОЗУ-шка маленькая (2-8k), то старшие линии адреса никуда не заводятся, поэтому запись в адрес #0111 "запишет" и в #0911,#1111,#1911,#2111,#2911,#3111,#3911... SMT> сделать запись в порт с временной меткой где-то между началом и SMT> концом команды boo> пару слов о том, как это в US сделано? ядро Z80 увеличивает счётчик тактов cpu.t на нужную величину, вызывает функцию записи в порт (которая читает этот счётчик), и снова увеличивает cpu.t, в сумме эти приращения дадут время выполнения команды. если, как ты хочешь, совсем отказываться от глобальных переменных, придётся эти временные метки таскать как параметры: например, в void step(Z80 *cpu, int64 &tick) передавать время начала, потом, например, в команде OTIR вызывать void port_out(cpu->bc, read_mem(cpu->hl), tick+16), а потом увеличивать tick на 21

от: SMT
кому: All
дата: 22 Jan 2006
Hello, boo_boo а зачем свою? если TR-DOS был слабоват, то для Z80 легче доисправить то, что есть в глюкалке а почему 16k массивов только 3, а не 4? Z80-ядер навалом. могу предложить поискать Z80-ядро как раз под gcc/gpl от (C) Marat Fayzullin (автор эмуля MSX-2), оно ещё и с дизасмом (если не найдёшь, есть RAR архив - 19k) если не гнаться за скоростью, подойдёт любая реализация. а если гнаться, то нужно встроить функции чтения/записи памяти и портов прямо в код эмуляции инструкций Z80 (без вызова через указатель), и прочие нужности вроде всяких breakpoints на разные события модульно оформить можно, написав эти функции с пометкой inline и проинклюдив ядро Z80, либо более красиво - как класс-шаблон, тогда в конструктор передаётся класс, читающий память/порты, ест-но inline-функциями на самом деле, Z80 неразрывно связан с циклом эмуляции. потому что INT обрабатывается в зависимости от того, была ли пред. команда EI для скорпиона с профПЗУ страницы ПЗУ переключаются при чтении определённых адресов. пентагоновский кеш 2-8K имеет страницы, меньше чем 16K, причём запись в одну область должна сказываться на зеркальных остальных (реализовано в Z80S - там в ядре размер страницы не 16K, а 2K). точная эмуляция бордюрных эффектов требует сделать запись в порт с временной меткой где-то между началом и концом команды, причём сдвиг зависит от типа команды: outi, out (#FE),a или out (c),d эти 3 примера не учтены предлагаемым API вообще, отвязать общее время от времени внутри кадра - хорошая идея. жалко, что я её не заметил раньше (наверное, поздновато разглядел __int64). в unreal все времени получились жутко запутанными

от: Станислав Ломакин
кому: All
дата: 22 Jan 2006
Hello, All думаю написать отдельный модуль эмуляции z80, на чистом C, чтоб с полпинка вставлялся куда угодно -- библиотека и h-файл. за основу возьму, наверное, код из FUSE.. или US... вопрос в том, каким делать API, требования -- возможность создания нескольких процессоров, никаких экспортируемых переменных, только функции, ну и поддержка всех фич, ессно :) набросал вот чего-то: ┌─- CODE ─── /*процессор -- поперто из FUSE*/ typedef struct { regpair af,bc,de,hl; regpair af_,bc_,de_,hl_; regpair ix,iy; byte i; word r; byte r7; /* The high bit of the R register */ regpair sp,pc; byte iff1, iff2, im; int halted; } Z80; /*создание и инициализация процессора. page1/2/3 -- указатели на массивы (длиной 16K), которые будут использоваться как страницы памяти*/ Z80 *z80_create(void *page1, void *page2, void *page3); /*уничтожение процессора -- тешу свою страсть к разрушению ;) */ void z80_destroy(Z80 *cpu); /*замена одной из 3х страниц памяти -- для вещей вроде переключения 128k страниц и тп*/ void z80_set_mempage(Z80 *cpu, int page_num, void *page); /*выполнение очередной команды, возвращает затраченное кол-во тактов*/ int z80_step(Z80 *cpu); /*установка функции-callback'а на чтение из порта*/ void z80_set_pread_cb(Z80 *cpu, z80_pread_cb cb_fn); /*установка callback'а на запись в порт*/ void z80_set_pwrite_cb(Z80 *cpu, z80_pwrite_cb cb_fn); /*установка callback'а на чтение из памяти*/ void z80_set_mread_cb(Z80 *cpu, z80_mread_cb cb_fn); /*установка callback'а на запись в память*/ void z80_set_mwrite_cb(Z80 *cpu, z80_mwrite_cb cb_fn); /*прерывание*/ void z80_int(Z80 *cpu); /*немаскируемое прерывание*/ void z80_nmi(Z80 *cpu); /*сброс*/ void z80_reset(Z80 *cpu) /*функции для получения значений регистров -- чтоб не завязываться на поля struct'уры Z80*/ лень писать ,) └── CODE ───

от: Станислав Ломакин
кому: All
дата: 22 Jan 2006
Hello, SMT SMT> а зачем свою? если TR-DOS был слабоват, то для Z80 легче доисправить SMT> то, что есть в глюкалке SMT> я как на исходники глюкалки смотрю, так руки опускаются -- все одним сплошным клубком... хочется, чтобы как в жизни -- z80 же не знает, что внутри у ВГ, и наоборот, они просто общаются через некий интерфейс. вообщем, ИМХО, такое разделение и мне время сэкономит (в коде легче ориентироваться и отлаживать, когда все по полочкам), и людям пригодится. SMT> а почему 16k массивов только 3, а не 4? SMT> ага, 4 их, глючу :) SMT> Z80-ядер навалом SMT> я не нашел ни одного практически, которое не требует вмешательства в код, чтобы его использовать... а если так, то заодно можно все малость перелопатить и свой API сделать. Файзуллинский эмуль хорош, но не позволяет больше одного процессора создать, а это может пригодиться (GS и тп) за скоростью гнаться не хочу, с учетом мощности современных компов затраты на вызов функции через указатель не пугают :) сделать API классами можно с одной стороны, но с другой чистый C универсальней, а ситуации, в которой понадобиться наследовать от класса Z80, не могу представить :o SMT> на самом деле, Z80 неразрывно связан с циклом эмуляции. потому что SMT> INT обрабатывается в зависимости от того, была ли пред. команда EI SMT> можно флажок завести на этот случай... SMT> для скорпиона с профПЗУ страницы ПЗУ переключаются при чтении SMT> определённых адресов. SMT> ну так можно в callback'e на чтение памяти отследить это, и поменять страницу... SMT> пентагоновский кеш 2-8K имеет страницы, меньше чем 16K, причём запись SMT> в одну область должна сказываться на зеркальных остальных SMT> (реализовано в Z80S - там в ядре размер страницы не 16K, а 2K). SMT> а где можно прочитать про этот самый кэш? слабо себе представляю, кто он такой :( SMT> точная эмуляция бордюрных эффектов требует сделать запись в порт с SMT> временной меткой где-то между началом и концом команды, причём сдвиг SMT> зависит от типа команды: outi, out (#FE),a или out (c),d SMT> а можно пару слов о том, как это в US сделано?

от: Станислав Ломакин
кому: All
дата: 23 Jan 2006
Hello, SMT насчет страниц памяти я брежу -- z80 не знает никаких страниц, с ними имеет дело уже контроллер памяти, а его эмуляция -- отдельный вопрос. так что, думаю, достаточно задания callback'ов на чтение памяти (возвращает байт по адресу) и на запись (соотв выставляет) SMT> ядро Z80 увеличивает счётчик тактов cpu.t на нужную величину, SMT> вызывает функцию записи в порт (которая читает этот счётчик), и снова SMT> увеличивает cpu.t, в сумме эти приращения дадут время выполнения SMT> команды. если, как ты хочешь, совсем отказываться от глобальных SMT> переменных, придётся эти временные метки таскать как параметры: SMT> например, в void step(Z80 *cpu, int64 &tick) передавать время начала, SMT> потом, например, в команде OTIR вызывать void port_out(cpu->bc, SMT> read_mem(cpu->hl), tick+16), а потом увеличивать tick на 21 угу... хммм... а если так: добавить еще один callback, который будет вызываться на каждом такте. в нем организовывать задержку, запускать кадровый синхроимпульс, INT.... Вообщем, делать там все, что имеет отношение к таймингу. А WAIT от памяти и #FE устраивать тоже снаружи, перехватив эти операции в callback'ах обращения к памяти/порту.

от: SMT
кому: All
дата: 23 Jan 2006
Hello, boo_boo с потактовым callback'ом будет в 10 раз медленнее. сейчас, конечно, не времена P-133, поэтому всё равно. но если переносить из unreal AY и ULA с отвязкой их от Z80, друг от друга и от других устройств, придётся покилять все супер-извраты и сделать в лоб, как и для Z80, функцию step, которая эмулирует 1 такт работы устройства и вызывать её из такого callback'а сейчас придумал независимый интерфейс для AY: на входе массив записей в порт (ном.регистра, значение, такт AY), и последний такт эмуляции (на случай, если записи не было, но звук получить надо). на выходе - количество полных семплов, выданных AY-ком до нужного такта и массив собственно семплов. вызывающая функция обязана предоставить буфер достаточного размера. таким образом, минимизируются потери от частых переключений Z80/ULA/AY, также можно писать такой буфер сразу в PSG-файл, или, записав лишь последние значения регистров в кадре и прогнав через LHA, в VTX-файл. так больше нравится, чем потактовая эмуляция? о!, в следующих версиях unreal так и сделаю - должна появиться прибавка в производительности за счёт минимизации замещения данных к L1-кеше кода и последовательному доступу к массивам. заодно аниальясинговый FIR-фильтр можно будет написать более прозрачно, почти "в лоб"

от: Станислав Ломакин
кому: All
дата: 23 Jan 2006
Hello, SMT SMT> сейчас придумал независимый интерфейс для AY: на входе массив записей SMT> в порт (ном.регистра, значение, такт AY), и последний такт эмуляции SMT> (на случай, если записи не было, но звук получить надо). на выходе - SMT> количество полных семплов, выданных AY-ком до нужного такта и массив SMT> собственно семплов. вызывающая функция обязана предоставить буфер SMT> достаточного размера. таким образом, минимизируются потери от частых SMT> переключений Z80/ULA/AY, также можно писать такой буфер сразу в SMT> PSG-файл, или, записав лишь последние значения регистров в кадре и SMT> прогнав через LHA, в VTX-файл. так больше нравится, чем потактовая SMT> эмуляция? SMT> то есть аккумулировать обращения к портам AY, а потом скармливать такой ф-ии, получая от нее кусок звука... очень симпатично :) но тогда ведь будет некоторое отставание AY от Z80 -- что делать, если кодер захочет не записать регистр, а прочитать?

от: Станислав Ломакин
кому: All
дата: 24 Jan 2006
Hello, SMT SMT> а, это мелочь. хранить массив из 16 регистров, они же не меняются со SMT> стороны самой AY а, круто :) очередной вариант api для z80, с учетом недоработок предыдущего: ┌─- CODE ─── enum Z80_REG_T {regAF,regBC,regDE,regHL,regAF_,regBC_,regDE_,regHL_,regIX,regIY,regPC,regSP,re gIR,regIM/*0,1 или 2*/,regIFF1,regIFF2}; typedef void (*z80_tstate_cb)(); /*последующие 4 callback'a первым аргументом принимают номер такта в шаге, на котором производится ввод/вывод*/ typedef unsigned char (*z80_pread_cb)(unsigned char t_state, unsigned port); typedef void (*z80_pwrite_cb)(unsigned char t_state, unsigned port, unsigned char value); typedef unsigned char (*z80_mread_cb)(unsigned char t_state, unsigned addr); typedef void (*z80_mwrite_cb)(unsigned char t_state, unsigned addr, unsigned char value); struct _z80_cpu_context; typedef struct _z80_cpu_context Z80; /*создание и инициализация процессора.*/ Z80 *z80_create(); /*уничтожение процессора -- тешу свою страсть к разрушению ;) */ void z80_destroy(Z80 *cpu); /*выполнение очередной команды, возвращает затраченное кол-во тактов*/ int z80_step(Z80 *cpu); /*установка callback'a, который будет вызываться на каждом такте эмуляции*/ void z80_set_tstate_callback(Z80 *cpu, z80_tstate_cb cb_fn); /*установка функции-callback'а на чтение из порта*/ void z80_set_pread_cb(Z80 *cpu, z80_pread_cb cb_fn); /*установка callback'а на запись в порт*/ void z80_set_pwrite_cb(Z80 *cpu, z80_pwrite_cb cb_fn); /*установка callback'а на чтение из памяти*/ void z80_set_mread_cb(Z80 *cpu, z80_mread_cb cb_fn); /*установка callback'а на запись в память*/ void z80_set_mwrite_cb(Z80 *cpu, z80_mwrite_cb cb_fn); /*прерывание, второй аргумент - код операции для IM0 %)*/ void z80_int(Z80 *cpu, unsigned char op); /*немаскируемое прерывание*/ void z80_nmi(Z80 *cpu); /*генерация w_states WAIT-циклов. (при этом будет w_states раз вызван тактовый callback) для использования в callback'ах обращения к памяти и портам*/ void z80_w_states(Z80 *cpu, unsigned w_states); /*сброс*/ void z80_reset(Z80 *cpu) /*функция для получения значения регистра*/ unsigned z80_get_reg(Z80 *cpu, enum Z80_REG_T reg); /*функция для установки значения регистра*/ unsigned z80_set_reg(Z80 *cpu, enum Z80_REG_T reg, unsigned value); /*возвращает 1 если z80 ждет на halt'e*/ int z80_is_halted(Z80 *cpu); └── CODE ───




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
IS-DOS - настройка и работа IS-DOS 2000.
Новости - Новости от FLASH на 01.06.97 года.
Советы бывалого - Этикет и дурные привычки.
WANTED - Розыск программ...
Shoutburst - program structure

В этот день...   8 мая