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

User Tag List

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

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

  1. #1
    Master Аватар для boo_boo
    Регистрация
    10.05.2005
    Адрес
    Москва
    Сообщений
    713
    Благодарностей: 14
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию мысли по написанию модуля эмуляции z80 на С

    думаю написать отдельный модуль эмуляции z80, на чистом C, чтоб с полпинка вставлялся куда угодно -- библиотека и h-файл. за основу возьму, наверное, код из FUSE.. или US...
    вопрос в том, каким делать API, требования -- возможность создания нескольких процессоров, никаких экспортируемых переменных, только функции, ну и поддержка всех фич, ессно
    набросал вот чего-то:
    Код:
    /*процессор -- поперто из 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*/
    лень писать ,)
    Последний раз редактировалось boo_boo; 21.01.2006 в 19:03.

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

  3. #2
    Veteran Аватар для SMT
    Регистрация
    16.01.2005
    Адрес
    Бобруйск
    Сообщений
    1,267
    Благодарностей: 30
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    а зачем свою? если 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 все времени получились жутко запутанными

  4. #3
    Master Аватар для boo_boo
    Регистрация
    10.05.2005
    Адрес
    Москва
    Сообщений
    713
    Благодарностей: 14
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от SMT
    а зачем свою? если TR-DOS был слабоват, то для Z80 легче доисправить то, что есть в глюкалке
    я как на исходники глюкалки смотрю, так руки опускаются -- все одним сплошным клубком... хочется, чтобы как в жизни -- z80 же не знает, что внутри у ВГ, и наоборот, они просто общаются через некий интерфейс. вообщем, ИМХО, такое разделение и мне время сэкономит (в коде легче ориентироваться и отлаживать, когда все по полочкам), и людям пригодится.
    Цитата Сообщение от SMT
    а почему 16k массивов только 3, а не 4?
    ага, 4 их, глючу
    Цитата Сообщение от SMT
    Z80-ядер навалом
    я не нашел ни одного практически, которое не требует вмешательства в код, чтобы его использовать... а если так, то заодно можно все малость перелопатить и свой API сделать. Файзуллинский эмуль хорош, но не позволяет больше одного процессора создать, а это может пригодиться (GS и тп)

    за скоростью гнаться не хочу, с учетом мощности современных компов затраты на вызов функции через указатель не пугают
    сделать API классами можно с одной стороны, но с другой чистый C универсальней, а ситуации, в которой понадобиться наследовать от класса Z80, не могу представить
    Цитата Сообщение от SMT
    на самом деле, Z80 неразрывно связан с циклом эмуляции. потому что INT обрабатывается в зависимости от того, была ли пред. команда EI
    можно флажок завести на этот случай...
    Цитата Сообщение от SMT
    для скорпиона с профПЗУ страницы ПЗУ переключаются при чтении определённых адресов.
    ну так можно в callback'e на чтение памяти отследить это, и поменять страницу...
    Цитата Сообщение от SMT
    пентагоновский кеш 2-8K имеет страницы, меньше чем 16K, причём запись в одну область должна сказываться на зеркальных остальных (реализовано в Z80S - там в ядре размер страницы не 16K, а 2K).
    а где можно прочитать про этот самый кэш? слабо себе представляю, кто он такой
    Цитата Сообщение от SMT
    точная эмуляция бордюрных эффектов требует сделать запись в порт с временной меткой где-то между началом и концом команды, причём сдвиг зависит от типа команды: outi, out (#FE),a или out (c),d
    а можно пару слов о том, как это в US сделано?

  5. #4
    Veteran Аватар для SMT
    Регистрация
    16.01.2005
    Адрес
    Бобруйск
    Сообщений
    1,267
    Благодарностей: 30
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от boo_boo
    , в которой понадобиться наследовать от класса Z80, не могу представить
    ну можно наследовать, переопределив виртуальные ф-ции чтения портов/памяти, но это не освобождает от вызова через указатель. надо не наследовать от класса Z80, а делать типа этого:
    Код:
    CUniversalZ80WithoutMemPorts<CSpectrumMem,CSpectrumPorts> MainZ80;
    CUniversalZ80WithoutMemPorts<CSpectrumMemDbg,CSpectrumPortsDbg> MainZ80Dbg;
    CUniversalZ80WithoutMemPorts<CGSMem,CGSPorts> gsZ80;
    таких ядер легко можно наделать сколько надо, причём всё, что относится к памяти/портам будет заинлайнено. в принципе, в unreal 3 ядра. не знаю, стоит ли ради повышения скорости в отдельных случаях так сильно увеличивать размер конечного exe-шника. хотя, если теперь эмуляторы даже на жабе пишут, скоростью можно пожертвовать
    Цитата Сообщение от boo_boo
    где можно прочитать про этот самый кэш? слабо себе представляю, кто он такой
    журналы чёрн.ворона#3, dejavu#7. это статическое ОЗУ 2-16k, припаиваемое параллельно ПЗУ. имеется небольшая схемка, которая включает его вместо пзу (например, вместо TR-DOS, позволяя программе в этом кеше обращаться сразу и ко всей 48-й памяти, и к портам дисковода). если ОЗУ-шка маленькая (2-8k), то старшие линии адреса никуда не заводятся, поэтому запись в адрес #0111 "запишет" и в #0911,#1111,#1911,#2111,#2911,#3111,#3911...

    Цитата Сообщение от SMT
    сделать запись в порт с временной меткой где-то между началом и концом команды
    Цитата Сообщение от boo_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

  6. #5
    Veteran Аватар для SMT
    Регистрация
    16.01.2005
    Адрес
    Бобруйск
    Сообщений
    1,267
    Благодарностей: 30
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

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

  7. #6
    Master Аватар для boo_boo
    Регистрация
    10.05.2005
    Адрес
    Москва
    Сообщений
    713
    Благодарностей: 14
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    насчет страниц памяти я брежу -- z80 не знает никаких страниц, с ними имеет дело уже контроллер памяти, а его эмуляция -- отдельный вопрос.
    так что, думаю, достаточно задания callback'ов на чтение памяти (возвращает байт по адресу) и на запись (соотв выставляет)
    Цитата Сообщение от SMT
    ядро 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
    угу... хммм... а если так: добавить еще один callback, который будет вызываться на каждом такте. в нем организовывать задержку, запускать кадровый синхроимпульс, INT.... Вообщем, делать там все, что имеет отношение к таймингу. А WAIT от памяти и #FE устраивать тоже снаружи, перехватив эти операции в callback'ах обращения к памяти/порту.

  8. #7
    Veteran Аватар для SMT
    Регистрация
    16.01.2005
    Адрес
    Бобруйск
    Сообщений
    1,267
    Благодарностей: 30
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    с потактовым callback'ом будет в 10 раз медленнее. сейчас, конечно, не времена P-133, поэтому всё равно. но если переносить из unreal AY и ULA с отвязкой их от Z80, друг от друга и от других устройств, придётся покилять все супер-извраты и сделать в лоб, как и для Z80, функцию step, которая эмулирует 1 такт работы устройства и вызывать её из такого callback'а

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

    о!, в следующих версиях unreal так и сделаю - должна появиться прибавка в производительности за счёт минимизации замещения данных к L1-кеше кода и последовательному доступу к массивам. заодно аниальясинговый FIR-фильтр можно будет написать более прозрачно, почти "в лоб"

  9. #8
    Master Аватар для boo_boo
    Регистрация
    10.05.2005
    Адрес
    Москва
    Сообщений
    713
    Благодарностей: 14
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

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

  10. #9
    Veteran Аватар для SMT
    Регистрация
    16.01.2005
    Адрес
    Бобруйск
    Сообщений
    1,267
    Благодарностей: 30
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    а, это мелочь. хранить массив из 16 регистров, они же не меняются со стороны самой AY

  11. #10
    Master Аватар для boo_boo
    Регистрация
    10.05.2005
    Адрес
    Москва
    Сообщений
    713
    Благодарностей: 14
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от SMT
    а, это мелочь. хранить массив из 16 регистров, они же не меняются со стороны самой AY
    а, круто

    очередной вариант api для z80, с учетом недоработок предыдущего:
    Код:
    enum Z80_REG_T {regAF,regBC,regDE,regHL,regAF_,regBC_,regDE_,regHL_,regIX,regIY,regPC,regSP,regIR,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);

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

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

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

Похожие темы

  1. Руководство по написанию пресс-релизов.
    от Vyacheslav Mednonogov (500:812/1.30) в разделе Разное
    Ответов: 0
    Последнее: 13.10.2005, 19:30
  2. Странные мысли
    от acidrain в разделе Unsorted
    Ответов: 162
    Последнее: 18.08.2005, 07:21

Ваши права

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