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 ───




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

Похожие статьи:
Юмор - That is true.
Почтовый ящик - О народном проекте - PROGRESS MEGADEMO.
Юмор - подборка анекдотов.
Приколы - Описание ЕСННС (Единый Стандарт на Совкового СисОпа).
Обзор игр - 4х4 PUZZLE, PARADОX ANGLE, XIXIТ, NEТ WALК.

В этот день...   24 апреля