thoughts on writing a z80 emulation module in C

ZXNet echo conference «zxnet.pc»

From SMT To All 22 January 2006

Hello SMT if you make a completely universal kernel, you need to provide for an increase in the current time and in functions of working with memory/ports - in some models, and at least in original 48/128, generated by WAIT when accessing port #FE and half memory pages. the processor and devices like Scorpio PC keyboard controller

From SMT To All 22 January 2006

Hello, boo_boo boo> , in which I need to inherit from the Z80 class, I can’t boo> submit Well, you can inherit by redefining the virtual functions for reading ports/memory, but this does not exempt the call via a pointer. you should not inherit from the class Z80, but do something like this: ┌─- CODE ─── CUniversalZ80WithoutMemPorts MainZ80; CUniversalZ80WithoutMemPorts MainZ80Dbg; CUniversalZ80WithoutMemPorts gsZ80; └── CODE ─── such nuclei can easily be made as many as necessary, and everything that relates to memory/ports will be inlined. In principle, unreal has 3 cores. I don't know if it's worth it to increase speed, in some cases increase the size so much the final exe. although, if now emulators even write in toad, the speed you can donate boo> where can I read about this cache? I have little idea who boo> he is like that black crow magazines#3, dejavu#7. this is static RAM 2-16k, solderable parallel to the ROM. there is a small circuit that turns it on instead of ROM (for example, instead of TR-DOS, allowing the program in this cache to immediately access the entire 48th memory, and to the drive ports). if the RAM is small (2-8k), then the senior address lines do not go anywhere, so the entry to address #0111"will write" in #0911,#1111,#1911,#2111,#2911,#3111,#3911... SMT> write to the port with a timestamp somewhere between start and SMT> end of command boo> a few words about how this is done in the US? The Z80 core increases the clock counter cpu.t by the required amount, calls the function writes to the port (which reads this counter), and again increases cpu.t, in total these increments will give the command execution time. if, as you want, absolutely to abandon global variables, you will have to drag these timestamps like parameters: for example, in void step(Z80 *cpu, int64 &tick) pass the start time, then, for example, in the OTIR command call void port_out(cpu->bc, read_mem(cpu->hl), tick+16), and then increase tick by 21

From SMT To All 22 January 2006

Hello, boo_boo why yours? if TR-DOS was rather weak, then for the Z80 it is easier to correct what is in the glitch why are there only 3 16k arrays and not 4? Z80 cores in bulk. I can suggest looking for the Z80 kernel just under gcc/gpl from (C) Marat Fayzullin (author of the MSX-2 emulator), it also has a dizasm (if you can’t find it, there is a RAR archive - 19k) If you are not looking for speed, any implementation will do. and if you chase, then you need to build memory and port read/write functions directly into the emulation code Z80 instructions (without calling through the pointer), and other necessities like all sorts breakpoints for different events You can design it modularly by writing these functions marked inline and including Z80 kernel, or more beautifully - as a template class, then it is passed to the constructor class that reads memory/ports, eats inline functions in fact, the Z80 is inextricably linked to the emulation cycle. because INT processed depending on whether there was a previous one. EI team for Scorpio with professional ROM, ROM pages switch when reading certain addresses. Pentagon cache 2-8K has pages less than 16K, and writes in one area should affect the mirror others (implemented in Z80S - there in the kernel the page size is not 16K, but 2K). accurate emulation of border effects requires writing to a port with a timestamp somewhere between the beginning and the endcommands, and the shift depends on the type of command: outi, out (#FE),a or out (c),d these 3 examples are not taken into account by the proposed API In general, untying the general from time to time within the frame is a good idea. it's a pity that I didn’t notice it earlier (probably I noticed __int64 too late). everything in real time turned out terribly confusing

From Stanislav Lomakin To All 22 January 2006

Hello, All I’m thinking of writing a separate z80 emulation module, in pure C, so that with half a kick inserted anywhere - library and h-file. I'll probably use the code as a basis from FUSE.. or US... the question is how to make an API, the requirements are the ability to create several processors, no exported variables, only functions, and support all the features, of course :) I jotted down something like this: ┌─- CODE ─── /*processor - flooded from 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; /*creation and initialization of the processor. page1/2/3 -- pointers to arrays (16K long), which will be used as memory pages*/ Z80 *z80_create(void *page1, void *page2, void *page3); /*destroying the processor - I indulge my passion for destruction ;) */ void z80_destroy(Z80 *cpu); /*replacement of one of 3 memory pages - for things like switching 128k pages, etc.*/ void z80_set_mempage(Z80 *cpu, int page_num, void *page); /*execute the next command, returns the amount spent number of bars*/ int z80_step(Z80 *cpu); /*setting the callback function to read from the port*/ void z80_set_pread_cb(Z80 *cpu, z80_pread_cb cb_fn); /*setting the callback to write to the port*/void z80_set_pwrite_cb(Z80 *cpu, z80_pwrite_cb cb_fn); /*setting the callback to read from memory*/ void z80_set_mread_cb(Z80 *cpu, z80_mread_cb cb_fn); /*setting the callback to write to memory*/ void z80_set_mwrite_cb(Z80 *cpu, z80_mwrite_cb cb_fn); /*interrupt*/ void z80_int(Z80 *cpu); /*non-maskable interrupt*/ void z80_nmi(Z80 *cpu); /*reset*/ void z80_reset(Z80 *cpu) /*functions for getting register values - so don’t get tied up in the fields of the Z80 struct*/ too lazy to write) └── CODE ───

From Stanislav Lomakin To All 22 January 2006

Hello SMT SMT> why have your own? if TR-DOS was weak, then for the Z80 it’s easier to fix it SMT> what is in the glitch SMT> It’s like I’m looking at the source code of a glitch, and I give up - it’s all in one piece in a ball... I want it to be like in life - the z80 doesn’t know what’s inside the VG, and on the contrary, they simply communicate through some interface. in general, IMHO, this is separation will also save me time (the code is easier to navigate and debug, when everything is in order), and people will find it useful. SMT> why are there only 3 16k arrays and not 4? SMT> yeah, 4 of them, buggy :) SMT> Z80 cores in bulk SMT> I have not found any practically that does not require intervention in the code in order use it... and if so, then at the same time you can shovel everything a little and your Make an API. Fayzulla emulsion is good, but does not allow more than one create a processor, and this may be useful (GS, etc.) I don’t want to chase speed; taking into account the power of modern computers, the costs for calling a function through a pointer is not scary :) On the one hand, you can make API classes, but on the other hand, pure C is more universal, but I can’t find a situation in which I need to inherit from the Z80 class imagine :oSMT> in fact, the Z80 is inextricably linked to the emulation cycle. because SMT> INT is processed depending on whether there was a previous one. EI team SMT> You can check the box for this case... SMT> for Scorpio with professional ROM, ROM pages switch when read SMT> specific addresses. SMT> Well, you can track this in the memory reading callback and change it page... SMT> Pentagon cache 2-8K has pages less than 16K, and writes SMT> in one area should affect the mirror others SMT> (implemented in Z80S - there in the kernel the page size is not 16K, but 2K). SMT> where can I read about this very cache? I have no idea who he is :( SMT> accurate emulation of border effects requires writing to port c SMT> timestamp somewhere between the beginning and end of the command, with a shift SMT> depends on the command type: outi, out (#FE),a or out (c),d SMT> Can you say a few words about how this is done in the US?

From Stanislav Lomakin To All 23 January 2006

Hello SMT I'm delusional about memory pages - the z80 doesn't know any pages, but has them It's already a memory controller, and its emulation is a separate issue. so I think it’s enough to set callbacks to read memory (returns byte to address) and to write (set accordingly) SMT> Z80 core increases the cpu.t clock counter by the required amount SMT> calls the port write function (which reads this counter), and again SMT> increases cpu.t, in total these increments will give the execution time SMT> commands. if, as you want, you completely abandon global SMT> variables, you will have to drag these timestamps as parameters: SMT> for example, in void step(Z80 *cpu, int64 &tick) pass the start time SMT> then, for example, in the OTIR command call void port_out(cpu->bc, SMT> read_mem(cpu->hl), tick+16), and then increase tick by 21 yeah... hmmm... and if so: add another callback that will be called at every beat. organize a delay in it, launch personnel sync pulse, INT.... In general, do everything there that has to do with timing. And WAIT from memory and #FE are also arranged externally, intercepting these operations in callbacks for accessing memory/port.

From SMT To All 23 January 2006

Hello, boo_boo with a clock-by-cycle callback it will be 10 times slower. now, of course, is not the time P-133, so it doesn't matter. but if you transfer from unreal AY and ULA with their decoupling from the Z80, from each other and from other devices, you will have to switch everything super-perverted and make, as for the Z80, a step function that emulates 1 clock of the device and call it from such a callback I have now come up with an independent interface for AY: the input is an array of records to the port (register number, value, AY clock), and the last emulation clock (in case There was no recording, but we need to get the sound). the output is the number of complete samples, issued by AY before the required clock cycle and an array of the actual samples. defiant The function must provide a buffer of sufficient size. thus, losses from frequent switching Z80/ULA/AY are minimized, you can also write such a buffer directly into a PSG file, or by writing only the last register values to frame and passed through LHA to a VTX file. I like it better than beat by beat emulation? oh!, in the next versions of unreal I’ll do this - there should be an increase in performance by minimizing data replacement to the L1 code cache and sequential access to arrays. At the same time, an analiasing FIR filter can be will write more transparently, almost “on the forehead”

From Stanislav Lomakin To All 23 January 2006

Hello SMT SMT> has now come up with an independent interface for AY: the input is an array of records SMT> to port (register number, value, AY clock), and the last emulation clock SMT> (in case there was no recording, but you need to get sound). at the output - SMT> number of complete samples issued by AY before the required clock and array SMT> samples themselves. the calling function must provide a buffer SMT> is of sufficient size. thus, losses from frequent SMT> switching Z80/ULA/AY, you can also write such a buffer directly to SMT> PSG file, or by writing only the last register values in the frame and SMT> driven through LHA into a VTX file. I like it better than beat by beat SMT> emulation? SMT> that is, accumulate calls to AY ports, and then feed such a function, getting a piece of sound from her... very nice :) but then there will be some lag behind the AY from the Z80 - what to do if the encoder wants to read the register rather than write it?

From Stanislav Lomakin To All 24 January 2006

Hello SMT SMT> ah, this is a small thing. store an array of 16 registers, they do not change with SMT> side of AY itself ah, cool :) another API option for z80, taking into account the shortcomings of the previous one: ┌─- CODE ─── enum Z80_REG_T {regAF,regBC,regDE,regHL,regAF_,regBC_,regDE_,regHL_,regIX,regIY,regPC,regSP,re gIR,regIM/*0,1 or 2*/,regIFF1,regIFF2}; typedef void (*z80_tstate_cb)(); /*the next 4 callbacks take the tick number in the step as the first argument, which input/output is made*/ 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; /*creation and initialization of the processor.*/ Z80 *z80_create(); /*destroying the processor - I indulge my passion for destruction ;) */ void z80_destroy(Z80 *cpu); /*execute the next command, returns the number of cycles spent*/ int z80_step(Z80 *cpu); /*setting a callback that will be called at every emulation clock*/ void z80_set_tstate_callback(Z80 *cpu, z80_tstate_cb cb_fn); /*setting the callback function to read from the port*/void z80_set_pread_cb(Z80 *cpu, z80_pread_cb cb_fn); /*setting the callback to write to the port*/ void z80_set_pwrite_cb(Z80 *cpu, z80_pwrite_cb cb_fn); /*setting the callback to read from memory*/ void z80_set_mread_cb(Z80 *cpu, z80_mread_cb cb_fn); /*setting the callback to write to memory*/ void z80_set_mwrite_cb(Z80 *cpu, z80_mwrite_cb cb_fn); /*interrupt, second argument - opcode for IM0%)*/ void z80_int(Z80 *cpu, unsigned char op); /*non-maskable interrupt*/ void z80_nmi(Z80 *cpu); /*generation of w_states WAIT cycles. (w_states will be called once clock callback) for use in memory access callbacks and ports*/ void z80_w_states(Z80 *cpu, unsigned w_states); /*reset*/ void z80_reset(Z80 *cpu) /*function to get the register value*/ unsigned z80_get_reg(Z80 *cpu, enum Z80_REG_T reg); /*function for setting the register value*/ unsigned z80_set_reg(Z80 *cpu, enum Z80_REG_T reg, unsigned value); /*returns 1 if z80 is waiting at halt*/ int z80_is_halted(Z80 *cpu); └── CODE ───