Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1814 | yogev_ezra | 1 | /*===================================================================== |
2 | z80.c -> Main File related to the Z80 emulation code. |
||
3 | |||
4 | Please read documentation files to know how this works :) |
||
5 | |||
6 | Thanks go to Marat Fayzullin (read z80.h for more info), Raúl Gomez |
||
7 | (check his great R80 Spectrum emulator!), Philip Kendall (some code |
||
8 | of this emulator, such as the flags lookup tabled are from his fuse |
||
9 | Spectrum emulator) and more people I forget to name here ... |
||
10 | |||
11 | This program is free software; you can redistribute it and/or modify |
||
12 | it under the terms of the GNU General Public License as published by |
||
13 | the Free Software Foundation; either version 2 of the License, or |
||
14 | (at your option) any later version. |
||
15 | |||
16 | This program is distributed in the hope that it will be useful, |
||
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
19 | GNU General Public License for more details. |
||
20 | |||
21 | You should have received a copy of the GNU General Public License |
||
22 | along with this program; if not, write to the Free Software |
||
23 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
||
24 | |||
25 | Copyright (c) 2000 Santiago Romero Iglesias. |
||
26 | Email: sromero@escomposlinux.org |
||
27 | ======================================================================*/ |
||
28 | |||
29 | #include "z80.h" |
||
30 | #include "tables.h" |
||
31 | |||
32 | |||
33 | /* RAM variable, debug toggle variable, pressed key and |
||
34 | row variables for keyboard emulation */ |
||
35 | extern byte *RAM; |
||
36 | extern int debug, main_tecla, scanl; |
||
37 | |||
38 | extern int fila[5][5]; |
||
39 | |||
40 | //extern char *tapfile; |
||
41 | //extern FILE *tapfile; |
||
42 | extern char *tfont; |
||
43 | |||
44 | #include "macros.c" |
||
45 | |||
46 | |||
47 | /*==================================================================== |
||
48 | void Z80Reset( Z80Regs *regs, int cycles ) |
||
49 | |||
50 | This function simulates a z80 reset by setting the registers |
||
51 | to the values they are supposed to take on a real z80 reset. |
||
52 | You must pass it the Z80 register structure and the number |
||
53 | of cycles required to check for interrupts and do special |
||
54 | hardware checking/updating. |
||
55 | ===================================================================*/ |
||
56 | void Z80Reset( Z80Regs *regs, int int_cycles ) |
||
57 | { |
||
58 | /* reset PC and the rest of main registers: */ |
||
59 | regs->PC.W = regs->R.W = 0x0000; |
||
60 | |||
61 | regs->AF.W = regs->BC.W = regs->DE.W = regs->HL.W = |
||
62 | regs->AFs.W = regs->BCs.W = regs->DEs.W = regs->HLs.W = |
||
63 | regs->IX.W = regs->IY.W = 0x0000; |
||
64 | |||
65 | /* Make the stack point to $F000 */ |
||
66 | regs->SP.W = 0xF000; |
||
67 | |||
68 | /* reset variables to their default values */ |
||
69 | regs->I = 0x00; |
||
70 | regs->IFF1 = regs->IFF2 = regs->IM = regs->halted = 0x00; |
||
71 | regs->ICount = regs->IPeriod = int_cycles; |
||
72 | |||
73 | regs->IRequest = INT_NOINT; |
||
74 | regs->we_are_on_ddfd = regs->dobreak = regs->BorderColor = 0; |
||
75 | |||
76 | //#ifdef _DEBUG_ |
||
77 | regs->DecodingErrors = 1; |
||
78 | //#endif |
||
79 | |||
80 | } |
||
81 | |||
82 | |||
83 | /*==================================================================== |
||
84 | word Z80Run( Z80Regs *regs, int numopcodes ) |
||
85 | |||
86 | This function does the whole Z80 simulation. It consists on a |
||
87 | for(;;) loop (as stated on Marat's Fayzullin HOWTO -How to |
||
88 | Write a Computer Emulator-) which fetchs the next opcode, |
||
89 | interprets it (using a switch statement) and then it's |
||
90 | executed in the right CASE: of that switch. I've put the different |
||
91 | case statements into C files included here with #include to |
||
92 | make this more readable (and programming easier! :). |
||
93 | |||
94 | This function will change regs->ICount register and will execute |
||
95 | an interrupt when it reaches 0 (or <0). You can then do anything |
||
96 | related to your machine emulation here, using the Z80Hardware() |
||
97 | function. This function must be filled by yourself: put there |
||
98 | the code related to the emulated machine hardware, such as |
||
99 | screen redrawing, sound playing and so on. This functions can |
||
100 | return an special value to make Z80Run stop the emulation (and |
||
101 | return to the caller): that's INT_QUIT. If there is time to |
||
102 | execute an interrupt, please return INT_IRQ or INT_NMI. Return |
||
103 | INT_NOINT if there is no time for an interrupt :) . |
||
104 | |||
105 | Z80Execute() will change PC and all the z80 registers acording |
||
106 | to the executed opcode, and those values will be returned when |
||
107 | a INT_QUIT is received. |
||
108 | |||
109 | Pass as numcycles the number of clock cycle you want to execute |
||
110 | z80 opcodes for or < 0 (negative) to execute "infinite" opcodes. |
||
111 | ===================================================================*/ |
||
112 | word Z80Run( Z80Regs *regs, int numcycles ) |
||
113 | { |
||
114 | /* opcode and temp variables */ |
||
115 | register byte opcode; |
||
116 | eword tmpreg, ops, mread, tmpreg2; |
||
117 | unsigned long tempdword; |
||
118 | register int loop; |
||
119 | unsigned short tempword; |
||
120 | |||
121 | /* emulate |
||
122 | loop = (regs->ICount - numcycles); |
||
123 | |||
124 | /* this is the emulation main loop */ |
||
125 | while( regs->ICount > loop ) |
||
126 | { |
||
127 | #ifdef DEBUG |
||
128 | /* test if we have reached the trap address */ |
||
129 | if( regs->PC.W == regs->TrapAddress && regs->dobreak != 0 ) |
||
130 | return(regs->PC.W); |
||
131 | #endif |
||
132 | |||
133 | if( regs->halted == 1 ) |
||
134 | { r_PC--; AddCycles(4); } |
||
135 | |||
136 | /* read the opcode from memory (pointed by PC) */ |
||
137 | opcode = Z80ReadMem(regs->PC.W); |
||
138 | regs->PC.W++; |
||
139 | |||
140 | /* increment the R register and decode the instruction */ |
||
141 | AddR(1); |
||
142 | switch(opcode) |
||
143 | { |
||
144 | #include "opcodes.c" |
||
145 | case PREFIX_CB: |
||
146 | AddR(1); |
||
147 | #include "op_cb.c" |
||
148 | break; |
||
149 | case PREFIX_ED: |
||
150 | AddR(1); |
||
151 | #include "op_ed.c" |
||
152 | break; |
||
153 | case PREFIX_DD: |
||
154 | case PREFIX_FD: |
||
155 | AddR(1); |
||
156 | if( opcode == PREFIX_DD ) |
||
157 | { |
||
158 | #define REGISTER regs->IX |
||
159 | regs->we_are_on_ddfd = WE_ARE_ON_DD; |
||
160 | #include "op_dd_fd.c" |
||
161 | #undef REGISTER |
||
162 | } |
||
163 | else |
||
164 | { |
||
165 | #define REGISTER regs->IY |
||
166 | regs->we_are_on_ddfd = WE_ARE_ON_FD; |
||
167 | #include "op_dd_fd.c" |
||
168 | #undef REGISTER |
||
169 | } |
||
170 | regs->we_are_on_ddfd = 0; |
||
171 | break; |
||
172 | } |
||
173 | |||
174 | /* patch ROM loading routine */ |
||
175 | // address contributed by Ignacio Burgueño :) |
||
176 | // if( r_PC == 0x0569 ) |
||
177 | if( r_PC >= 0x0556 && r_PC <= 0x056c ) |
||
178 | Z80Patch( regs ); |
||
179 | |||
180 | /* check if it's time to do other hardware emulation */ |
||
181 | if( regs->ICount <= 0 ) |
||
182 | { |
||
183 | tmpreg.W = Z80Hardware(regs); |
||
184 | regs->ICount += regs->IPeriod; |
||
185 | loop = regs->ICount + loop; |
||
186 | |||
187 | /* check if we must exit the emulation or there is an INT */ |
||
188 | if( tmpreg.W == INT_QUIT ) |
||
189 | return( regs->PC.W ); |
||
190 | if( tmpreg.W != INT_NOINT ) |
||
191 | Z80Interrupt( regs, tmpreg.W ); |
||
192 | } |
||
193 | } |
||
194 | |||
195 | return(regs->PC.W); |
||
196 | } |
||
197 | |||
198 | |||
199 | |||
200 | /*==================================================================== |
||
201 | void Z80Interrupt( Z80Regs *regs, word ivec ) |
||
202 | ===================================================================*/ |
||
203 | void Z80Interrupt( Z80Regs *regs, word ivec ) |
||
204 | { |
||
205 | word intaddress; |
||
206 | |||
207 | /* unhalt the computer */ |
||
208 | if( regs->halted == 1 ) |
||
209 | regs->halted = 0; |
||
210 | |||
211 | if( regs->IFF1 ) |
||
212 | { |
||
213 | PUSH(PC); |
||
214 | regs->IFF1 = 0; |
||
215 | switch(regs->IM) |
||
216 | { |
||
217 | case 0: r_PC = 0x0038; AddCycles(12); break; |
||
218 | case 1: r_PC = 0x0038; AddCycles(13); break; |
||
219 | case 2: intaddress = (((regs->I & 0xFF)<<8) | 0xFF); |
||
220 | regs->PC.B.l = Z80ReadMem(intaddress); |
||
221 | regs->PC.B.h = Z80ReadMem(intaddress+1); |
||
222 | AddCycles(19); |
||
223 | break; |
||
224 | } |
||
225 | |||
226 | } |
||
227 | |||
228 | } |
||
229 | |||
230 | |||
231 | /*==================================================================== |
||
232 | word Z80Hardware(register Z80Regs *regs) |
||
233 | |||
234 | Do here your emulated machine hardware emulation. Read Z80Execute() |
||
235 | to know about how to quit emulation and generate interrupts. |
||
236 | ===================================================================*/ |
||
237 | word Z80Hardware( register Z80Regs *regs ) |
||
238 | { |
||
239 | if( |
||
240 | debug != 1 // && scanl >= 224 |
||
241 | ) |
||
242 | { |
||
243 | ; |
||
244 | } |
||
245 | return( INT_IRQ ); |
||
246 | } |
||
247 | |||
248 | |||
249 | /*==================================================================== |
||
250 | void Z80Patch( register Z80Regs *regs ) |
||
251 | |||
252 | Write here your patches to some z80 opcodes that are quite related |
||
253 | to the emulated machines (i.e. maybe accessing to the I/O ports |
||
254 | and so on), such as ED_FE opcode: |
||
255 | |||
256 | case ED_FE: Z80Patch(regs); |
||
257 | break; |
||
258 | |||
259 | This allows "BIOS" patching (cassette loading, keyboard ...). |
||
260 | ===================================================================*/ |
||
261 | void Z80Patch( register Z80Regs *regs ) |
||
262 | { |
||
263 | |||
264 | ///!!! if( tapfile != NULL ) |
||
265 | ///!!! { |
||
266 | ///!!! LoadTAP( regs, tapfile ); |
||
267 | ///!!! POP(PC); |
||
268 | ///!!! } |
||
269 | |||
270 | /* |
||
271 | if( strlen(tapfile) != 0 ) |
||
272 | { |
||
273 | if( LoadTapFile( regs, tapfile ) ) |
||
274 | { POP(PC); } |
||
275 | } |
||
276 | else |
||
277 | { |
||
278 | FileMenu( tfont, 3, tapfile ); |
||
279 | if( strlen(tapfile) != 0 ) |
||
280 | if( LoadTapFile( regs, tapfile ) ) |
||
281 | { POP(PC); } |
||
282 | } |
||
283 | */ |
||
284 | } |
||
285 | |||
286 | |||
287 | /*==================================================================== |
||
288 | byte Z80Debug( register Z80Regs *regs ) |
||
289 | |||
290 | This function is written for debugging purposes (it's supposed to |
||
291 | be a debugger by itself!). It will debug a single opcode, given |
||
292 | by the current PC address. |
||
293 | |||
294 | Return DEBUG_OK to state success and DEBUG_QUIT to quit emulation. |
||
295 | ===================================================================*/ |
||
296 | byte Z80Debug( register Z80Regs *regs ) |
||
297 | { |
||
298 | return( DEBUG_QUIT ); |
||
299 | } |
||
300 | |||
301 | |||
302 | |||
303 | /*==================================================================== |
||
304 | byte Z80MemRead( register word address ) |
||
305 | |||
306 | This function reads from the given memory address. It is not inlined, |
||
307 | and it's written for debugging purposes. |
||
308 | ===================================================================*/ |
||
309 | byte Z80MemRead( register word address, Z80Regs *regs ) |
||
310 | { |
||
311 | return(Z80ReadMem(address)); |
||
312 | } |
||
313 | |||
314 | |||
315 | /*==================================================================== |
||
316 | void Z80MemWrite( register word address, register byte value ) |
||
317 | |||
318 | This function writes on memory the given value. It is not inlined, |
||
319 | ands it's written for debugging purposes. |
||
320 | ===================================================================*/ |
||
321 | void Z80MemWrite( register word address, register byte value, Z80Regs *regs ) |
||
322 | { |
||
323 | Z80WriteMem( address, value, regs ); |
||
324 | } |
||
325 | |||
326 | |||
327 | /*==================================================================== |
||
328 | byte Z80InPort( register word port ) |
||
329 | |||
330 | This function reads from the given I/O port. It is not inlined, |
||
331 | and it's written for debugging purposes. |
||
332 | ===================================================================*/ |
||
333 | byte Z80InPort( register word port ) |
||
334 | { |
||
335 | int porth; |
||
336 | int code = 0xFF; |
||
337 | |||
338 | porth = port >> 8; |
||
339 | |||
340 | if (!(porth & 0x01)) code &= fila[4][1]; |
||
341 | if (!(porth & 0x02)) code &= fila[3][1]; |
||
342 | if (!(porth & 0x04)) code &= fila[2][1]; |
||
343 | if (!(porth & 0x08)) code &= fila[1][1]; |
||
344 | if (!(porth & 0x10)) code &= fila[1][2]; |
||
345 | if (!(porth & 0x20)) code &= fila[2][2]; |
||
346 | if (!(porth & 0x40)) code &= fila[3][2]; |
||
347 | if (!(porth & 0x80)) code &= fila[4][2]; |
||
348 | |||
349 | /* |
||
350 | issue 2 emulation, thx to Raul Gomez!!!!! |
||
351 | I should implement this also: |
||
352 | if( !ear_on && mic_on ) |
||
353 | code &= 0xbf; |
||
354 | where earon = bit 4 of the last OUT to the 0xFE port |
||
355 | and micon = bit 3 of the last OUT to the 0xFE port |
||
356 | */ |
||
357 | code &= 0xbf; |
||
358 | |||
359 | if( (port & 0xFF) == 0xFF ) |
||
360 | { |
||
361 | if( (rand() % 10) > 7 ) return(0xff); |
||
362 | else return( rand()%0xFF ); |
||
363 | } |
||
364 | |||
365 | return( code ); |
||
366 | } |
||
367 | |||
368 | |||
369 | /*==================================================================== |
||
370 | void Z80OutPort( register word port, register byte value ) |
||
371 | |||
372 | This function outs a value to a given I/O port. It is not inlined, |
||
373 | and it's written for debugging purposes. |
||
374 | ===================================================================*/ |
||
375 | void Z80OutPort( register Z80Regs *regs, |
||
376 | register word port, register byte value ) |
||
377 | { |
||
378 | /* change border colour */ |
||
379 | if( ! (port & 0x01) ) |
||
380 | regs->BorderColor = (value & 0x07); |
||
381 | } |
||
382 | |||
383 | |||
384 | |||
385 | /*==================================================================== |
||
386 | static void Z80FlagTables ( void ); |
||
387 | |||
388 | Creates a look-up table for future flag setting... |
||
389 | Taken from fuse's sources. Thanks to Philip Kendall. |
||
390 | ===================================================================*/ |
||
391 | void Z80FlagTables(void) |
||
392 | { |
||
393 | int i,j,k; |
||
394 | byte parity; |
||
395 | |||
396 | for(i=0;i<0x100;i++) { |
||
397 | sz53_table[i]= i & ( FLAG_3 | FLAG_5 | FLAG_S ); |
||
398 | j=i; parity=0; |
||
399 | for(k=0;k<8;k++) { parity ^= j & 1; j >>=1; } |
||
400 | parity_table[i]= ( parity ? 0 : FLAG_P ); |
||
401 | sz53p_table[i] = sz53_table[i] | parity_table[i]; |
||
402 | } |
||
403 | |||
404 | sz53_table[0] |= FLAG_Z; |
||
405 | sz53p_table[0] |= FLAG_Z; |
||
406 | }8;k++)>0x100;i++)>8)><8)>=>=>>0).> |
||
407 |