-
Notifications
You must be signed in to change notification settings - Fork 1
/
stardoc.txt
1067 lines (789 loc) · 39.6 KB
/
stardoc.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-----------------------------------------------------------------------------
Starscream 680x0 emulation library version 0.26d
Copyright 1997, 1998, 1999 Neill Corlett
Modified by Stéphane Dallongeville
-----------------------------------------------------------------------------
"Pathetic flesh creatures... can't you see we're INVINCIBLE? Come,
Decepticons, follow me to victory!"
How to contact Neill Corlett:
email: corlett@elwha.nrrc.ncsu.edu
www: http://www4.ncsu.edu/~nscorlet/
Contents
--------
0. Terms of Use
1. What's New
2. Getting Started
2.1. What You Will Need
2.2. How to Compile
3. The Starscream Interface
3.1. Function Reference
3.2. Contexts
3.3. Address Spaces
3.4. Executing Code
3.5. Read and Write Handlers
3.6. The Odometer
3.7. Interrupts
3.8. Emulating More Than One CPU
3.9. RESET and BKPT Instructions
3.10. Using the Interactive Debugger
3.11. Emulating the 68010
4. Tricks of the Trade
4.1. Inter-CPU Communication
4.2. Other Helpful Tips
4.3. Pitfalls
4.4. Ways to Abuse Starscream
4.5. Emulating Other 68K-Series CPUs
5. Known Bugs
6. Credits
-----------------------------------------------------------------------------
0. Terms of Use
-----------------------------------------------------------------------------
"Starscream" refers to the following files:
* STAR.C
* STARCPU.H
* CPUDEBUG.C
* CPUDEBUG.H
* STARDOC.TXT
* any object file or executable compiled from the above
* any source code generated from STAR.C, or object file assembled from such
code
Starscream may be distributed freely in unmodified form, as long as this
documentation is included.
No money, goods, or services may be charged or solicited for Starscream, or
any emulator or other program which includes Starscream, in whole or in part.
Using Starscream in a shareware or commercial application is forbidden.
Contact Neill Corlett (corlett@elwha.nrrc.ncsu.edu) if you'd like to license
Starscream for commercial use.
Any program which uses Starscream must include the following credit text, in
its documentation or in the program itself:
"Starscream 680x0 emulation library by Neill Corlett
(corlett@elwha.nrrc.ncsu.edu)"
-----------------------------------------------------------------------------
1. What's New
-----------------------------------------------------------------------------
Version 0.26d:
* CHK generation fixed.
* IDBcc generation fixed (IDBtr added).
Version 0.26c:
* Shift bits instructions fixed.
Version 0.26b:
* IO DWord Write bug fixed.
* Interrupt processing bug fixed.
(all lines added are followed by "// Stef Fix (Gens)" )
Version 0.26a:
* Egregious GetContext/SetContext bug fixed.
* Minor tweaking was done to the alignment rules.
Version 0.26:
* First publicly redistributable version.
* Added the following exceptions:
Trace
Format Error (68010+)
* Added a Double Fault mechanism. Currently the only thing that can cause
this is s68000reset().
* The memory map interface has changed to support different function codes /
address spaces (see section 3.3).
* Many improvements have been made to interrupt handling (see section 3.7).
* The unused bits of PC are now handled and preserved properly.
* Added fetch region caching. Jumps within the same program region are
now faster.
* Added a user-definable BKPT instruction handler (68010+).
* All Illegal Instruction exceptions are now emulated. The -illegal and
-noillegal options have been removed.
* Some features were added to the interactive debugger (see section 3.10).
* A few experimental 68020-related functions and variables have been added.
Ignore them for now.
Version 0.25 and earlier:
Ask if interested.
-----------------------------------------------------------------------------
2. Getting Started
-----------------------------------------------------------------------------
2.1. What You Will Need
------------------------
* A 32-bit C or C++ compiler. So far, the following compilers have been
proven to work:
- DJGPP 2.01
- Watcom 10.6
- GCC, running under Linux
- Microsoft Visual C++
* Netwide Assembler (NASM) version 0.95 or higher. NASM is freeware, with
publicly available source code, and available on a multitude of x86-based
platforms. It's available from Programmer's Heaven:
http://www.programmersheaven.com/
* Basic knowledge about the 680x0. You don't (theoretically) need to know
any 680x0 assembly, but it helps. A lot.
You can get a copy of the M68000 Family Programmer's Reference Manual (in
Adobe Acrobat .PDF format) from this web page:
http://www.mot.com/SPS/HPESD/prod/0X0/frames/68K.html
2.2. How to Compile
--------------------
Step 1
Generate the Starscream "code builder" executable by compiling STAR.C.
(for example: gcc star.c -o star)
Step 2
Generate the CPU source code. Run the code builder with the following
command line:
star source.asm [options]
Replace "source.asm" with the source code filename of your choice.
Options that you might need:
-regcall Use register calling conventions.
-stackcall Use stack-based calling conventions (default).
-hog Enable Hog mode. This inlines the fetch-decode-
execute loop, resulting in a decent speed increase,
at the cost of about 130K of executable size.
-nohog Disable Hog mode (default).
-cputype <type> Specify the CPU type, 68000 or 68010 (default=68000).
Options that you should never need (but they're here anyway):
-addressbits n Use n-bit addresses. The default value depends on
the CPU type (currently it's just 24).
"-addressbits 32" might allow some 68020/68070 code
to work. No guarantees, though.
-name <name> Appends the string <name> to all identifiers. This
overrides the default of s68000 or s68010 (depending
on the CPU type).
Step 3
Assemble the CPU source code. Refer to the NASM documentation for how to
do this.
If you run out of memory, try using NASMW.EXE instead of NASM.EXE (note,
however, that the NASMW.EXE executable from v0.97 is somewhat unstable).
Better yet, just recompile NASM yourself. Worked for me.
To generate a COFF object for DJGPP or GNUWin32, try this:
nasm -f coff source.asm
or to generate a Win32 object for Watcom or Visual C++:
nasm -f win32 source.asm
Step 4
Strip your executable when you're done! Starscream spews out symbols all
over the place (over 7000 of them), and you can save a ton of space by
stripping them once you're finished debugging.
For GCC, this means use the -s option in the linking phase.
-----------------------------------------------------------------------------
3. The Starscream Interface
-----------------------------------------------------------------------------
3.1. Function Reference
------------------------
The following is a brief summary of the functions declared in STARCPU.H. For
more information on a particular function, read the associated section.
int s68000init(void); (section 3.4)
Initializes Starscream. Always returns 0.
int s68000reset(void); (section 3.4)
Resets the current CPU.
Return value:
0 Success
1 Failure: Reset vector couldn't be fetched
-1 Failure: Double fault
unsigned s68000exec(int n); (section 3.4)
Executes n cycles' worth of instructions on the current CPU.
Return value:
= 0x80000000 Success
= 0x80000001 Out of bounds
= 0x80000002 Unsupported stack frame (68010+)
= 0xFFFFFFFF Double fault
< 0x80000000 Invalid instruction; the value returned is the address
of the instruction
int s68000interrupt(int level, int vector); (section 3.7)
Generates a hardware interrupt on the current CPU, with a given priority
level and vector number. The interrupt is queued for processing as soon
as possible.
Valid levels: 1-7
Valid vectors:
0-255 Deliberately vectored interrupt
-1 Auto-vectored interrupt
-2 Spurious interrupt
Return value:
0 The interrupt was queued successfully.
1 The interrupt was not queued, because a previously queued
interrupt exists at the same level.
2 The interrupt was not queued, because either "level" or "vector"
were invalid.
void s68000flushInterrupts(void); (section 3.7)
Check if there are any unmasked interrupts pending, and if so, process
them.
int s68000GetContextSize(void);
Returns the size of the context structure. Useful for verifying that
the context struct is packed correctly, i.e.:
ASSERT(s68000GetContextSize() == sizeof(struct S68000CONTEXT));
void s68000GetContext(void *context); (section 3.2)
Copies the current context into another context structure.
void s68000SetContext(void *context); (section 3.2)
Copies a context structure into the current context.
int s68000fetch(unsigned address);
Fetches the word at the specified address, using the memoryfetch array.
Returns -1 if the address is out of bounds.
unsigned s68000readOdometer(void); (section 3.6)
Returns the value of the odometer for the current CPU. Works anywhere,
even from within memory read/write, RESET, or BKPT handlers.
unsigned s68000tripOdometer(void); (section 3.6)
Returns the value of the odometer for the current CPU, and resets it to
zero. Works anywhere.
unsigned s68000controlOdometer(int n); (section 3.6)
Returns the value of the odometer for the current CPU. If n!=0, it also
resets the odometer to zero. Works anywhere.
void s68000releaseTimeslice(void); (section 3.5)
When called from inside a memory read/write, RESET, or BKPT handler,
this causes s68000exec to end prematurely. The early exit is reflected
in the odometer.
unsigned s68000readPC(void);
Returns the current program counter. Works anywhere.
3.2. Contexts
--------------
A "context" is a memory structure which holds all the information needed to
emulate a single CPU. Starscream defines a 68000 context as follows:
struct S68000CONTEXT {
struct STARSCREAM_PROGRAMREGION *fetch;
struct STARSCREAM_DATAREGION *readbyte;
struct STARSCREAM_DATAREGION *readword;
struct STARSCREAM_DATAREGION *writebyte;
struct STARSCREAM_DATAREGION *writeword;
struct STARSCREAM_PROGRAMREGION *s_fetch;
struct STARSCREAM_DATAREGION *s_readbyte;
struct STARSCREAM_DATAREGION *s_readword;
struct STARSCREAM_DATAREGION *s_writebyte;
struct STARSCREAM_DATAREGION *s_writeword;
struct STARSCREAM_PROGRAMREGION *u_fetch;
struct STARSCREAM_DATAREGION *u_readbyte;
struct STARSCREAM_DATAREGION *u_readword;
struct STARSCREAM_DATAREGION *u_writebyte;
struct STARSCREAM_DATAREGION *u_writeword;
void (*resethandler)(void);
unsigned dreg[8];
unsigned areg[8];
unsigned asp;
unsigned pc;
unsigned odometer;
unsigned char interrupts[8];
unsigned short sr;
};
There is a built-in context, called s68000context, which contains information
about the current CPU. If you're only emulating one 68000, then
s68000context is the only context you need. On the other hand, if you're
emulating more than one 68000, you'll need to define your own context for
each one.
Here is a detailed description of the contents of a S68000CONTEXT structure:
* "s_fetch", "s_readbyte", "s_readword", "s_writebyte", and "s_writeword"
are used to define the supervisor address space. "u_fetch", etc. are
used to define the user address space. (see section 3.3)
* "resethandler" points to your reset handler routine. (see section 3.9)
* "pc" is the program counter.
* "dreg" holds the eight data registers, d0-d7, in order.
* "areg" holds the eight address registers, a0-a7, in order. In supervisor
mode, areg[7] is the interrupt stack pointer. Otherwise, it's the user
stack pointer.
* "asp" is whatever stack register areg[7] isn't. In supervisor mode, asp
is the user stack pointer. Othewise, it's the supervisor stack pointer.
* "odometer" holds the number of cycles executed so far. It's incremented
as necessary on calls to s68000exec or s68000flushInterrupts.
* "interrupts" is an array containing information about pending interrupts.
(see section 3.7)
* "sr" is the status register. The lower 8 bits are the condition code
register.
3.3. Address Spaces
--------------------
The 68000 communicates with other devices via a number of address spaces.
There are different address spaces for program code and data, as well as
different address spaces for Supervisor and User modes. Most 68000-based
architectures use the same address space for all these, but the distinction
is there nonetheless.
Starscream emulates program address spaces by translating the 68K address
into a native host address, and fetching data directly from host memory (from
an array of words with native byte order). A program address space is
defined using an array of STARSCREAM_PROGRAMREGION structures:
struct STARSCREAM_PROGRAMREGION {
unsigned lowaddr;
unsigned highaddr;
unsigned offset;
};
Each structure contains the range of addresses in a program region, and an
offset which is added to the 68K address to obtain the equivalent host
address.
IMPORTANT NOTE: Program regions must be stored as an array of words with
native byte order. Since Intel's byte order is different than Motorola's,
this means every other byte must be swapped.
The s68000context fields "s_fetch" and "u_fetch" should be set to point to
arrays of STARSCREAM_PROGRAMREGION structures in order to define the
Supervisor Program and User Program address spaces, respectively. Each array
may contain any number of entries, and is terminated by an entry containing
-1 for both lowaddr and highaddr, and NULL for the offset.
Starscream emulates data address spaces in a completely different way. Since
the 68000 has no dedicated I/O bus, device interfaces (video/sound chips,
input devices, etc.) are often mapped directly into a data address space.
This raises some complex issues; for example, writing to an emulated serial
port address might require that an extra routine be called, in order to pass
the data through a real serial port, append it to a log file, etc. To this
end, Starscream defines data address spaces using an array of
STARSCREAM_DATAREGION structures:
struct STARSCREAM_DATAREGION {
unsigned lowaddr;
unsigned highaddr;
void *memorycall;
void *userdata;
};
Each structure contains the low and high addresses in a region, as before.
Rather than simply adding a constant offset to translate a 68K address into
a native one, Starscream offers two different ways to handle accesses to a
data region:
1. Call a user-defined handler routine whenever this region is accessed.
unsigned read_handler(unsigned address);
void write_handler(unsigned address, unsigned data);
memorycall = pointer to user handler
userdata = unused
(For some important information about read and write handlers, refer
to section 3.5.)
2. Handle accesses internally by reading/writing to an area of native
memory. Don't trap anything. This is ideal for a simple RAM or ROM
area as it requires the least processing.
memorycall = NULL
userdata = pointer to native memory area where reads or writes
will occur
NOTE: The native memory area must be byte swapped, same as program
regions are.
The s68000context fields "s_readbyte", "u_readbyte", "s_writebyte", etc.
should be set to point to arrays of STARSCREAM_DATAREGION structures in order
to define the Supervisor Data and User Data address spaces. Each array may
contain any number of entries, and is terminated by an entry containing -1
for lowaddr and highaddr, and NULL for memorycall and userdata.
And now, for an example of address space definition. Consider the following
make-believe memory map:
000000-03FFFF: Program ROM
300000-307FFF: Main RAM
400000-407FFF: Extra RAM
800000-81FFFF: Video RAM
A00000-A00007: Sound chip
Assume that:
* Program ROM is in an array called program_rom
* Main RAM is in an array called main_ram
* Extra RAM is in an array called extra_ram
* Video RAM is in an array called video_ram
* You've set up two routines to write to the sound chip:
void soundchip_writebyte(unsigned address, unsigned data);
void soundchip_writeword(unsigned address, unsigned data);
The first step is to build the program address space. Program code is
probably only going to be fetched from Program ROM, Main RAM, or Extra RAM.
The address space would thus be defined:
struct STARSCREAM_PROGRAMREGION pretend_programfetch[] = {
{0x000000, 0x03FFFF, (unsigned)program_rom - 0x000000},
{0x300000, 0x307FFF, (unsigned)main_ram - 0x300000},
{0x400000, 0x407FFF, (unsigned)extra_ram - 0x400000},
{-1, -1, NULL}
};
The last entry must be {-1, -1, NULL}.
The next step is to build the data address space. This is accomplished with
four arrays of STARSCREAM_DATAREGION structures: one for byte reads, word
reads, byte writes, and word writes. For example:
struct STARSCREAM_DATAREGION pretend_readbyte[] = {
{0x000000, 0x03FFFF, NULL, program_rom},
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{-1, -1, NULL, NULL}
};
struct STARSCREAM_DATAREGION pretend_readword[] = {
{0x000000, 0x03FFFF, NULL, program_rom},
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{-1, -1, NULL, NULL}
};
struct STARSCREAM_DATAREGION pretend_writebyte[] = {
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{0xA00000, 0xA00007, soundchip_writebyte, NULL},
{-1, -1, NULL, NULL}
};
struct STARSCREAM_DATAREGION pretend_writeword[] = {
{0x300000, 0x307FFF, NULL, main_ram},
{0x400000, 0x407FFF, NULL, extra_ram},
{0x800000, 0x81FFFF, NULL, video_ram},
{0xA00000, 0xA00007, soundchip_writeword, NULL},
{-1, -1, NULL, NULL}
};
The last entry of each array must be {-1, -1, NULL, NULL}.
The above structures take advantage of Starscream's internal handling for the
Program ROM, Main RAM, Extra RAM, and Video RAM, and use the custom handler
when writing to the sound chip.
Now that you've defined your address spaces, you need to tie it in with the
context (since each CPU can have different address spaces). This is done by
setting the address space pointers (s_fetch, u_fetch, etc.) to point to your
addres space definitions.
s68000context.s_fetch = pretend_programfetch;
s68000context.u_fetch = pretend_programfetch;
s68000context.s_readbyte = pretend_readbyte;
s68000context.u_readbyte = pretend_readbyte;
s68000context.s_readword = pretend_readword;
s68000context.u_readword = pretend_readword;
s68000context.s_writebyte = pretend_writebyte;
s68000context.u_writebyte = pretend_writebyte;
s68000context.s_writeword = pretend_writeword;
s68000context.u_writeword = pretend_writeword;
In this example, the Supervisor and User address spaces are set up to point
to the same definitions. For most 68000-based architectures, this is
correct.
If you are dynamically allocating memory for your emulated RAM or ROM areas
(using malloc() or a similar function), you will have to set up the address
space definitions at run time. This is left as a trivial exercise for the
reader.
Veteran users of the UAE 680x0 core might wonder why there is no "readlong"
or "writelong". This is because the real 68000 has a 16-bit data bus, and
therefore breaks up each 32-bit access into two 16-bit accesses. Using
separate handlers for 32-bit accesses is cumbersome and unnecessary. For
speed considerations, Starscream's internal memory read/write handlers access
32 bits at a time wherever possible (using the readword/writeword map).
3.4. Executing Code
--------------------
Before you execute any code, you must call s68000init(). It only needs to
be called one time.
Once that's done, and you've set up the memory map, call s68000reset() to
reset the CPU. If everything works, it should read the initial stack pointer
from address 0x000000, and the initial program counter from 0x000004, using
the Supervisor Program address space. (You can verify this by examining
s68000context.areg[7] and s68000context.pc.)
At this point, you can call the interactive debugger to try disassembling or
stepping through code, and to make sure everything is in the right place.
(see section 3.10 for details)
To execute 68000 code, call s68000exec(n), where n is the number of cycles.
s68000exec will return one of the following:
= 0x80000000: Success.
= 0x80000001: Out of range - the program "went off into the weeds".
More specifically, the program jumped to an address which was
not defined in the s_fetch or u_fetch array.
= 0x80000002: Unsupported stack frame (68010+). This happens when an RTE
instruction encounters a stack frame format that hasn't been
implemented (see section 5).
= 0xFFFFFFFF: Double fault. The CPU is dead and will stay dead until it
gets reset again.
< 0x80000000: Invalid instruction. The value returned is the address of
the instruction. For 32-bit addresses, the highest bit is
cut off (s68000context.pc contains the complete address,
however).
If you get a return code of 0x80000001, 0x80000002, or 0xFFFFFFFF, then the
68000 CPU will be in an unstable state and should be reset.
The real 68000 generates an exception for illegal instructions. As of v0.26,
this exception is always emulated. You will only get a return code less than
0x80000000 in an unexpected circumstance; upon encountering an invalid
instruction that isn't supposed to be. For example, a RESET instruction will
cause this if the user-defined RESET handler is null (see section 3.9 for
details on RESET handlers).
Whenever you call s68000exec, it increments the odometer (see section 3.6).
3.5. Read and Write Handlers
-----------------------------
Memory read and write handlers (as described in section 3.3) are a special
case, as they involve Starscream calling your code instead of the other way
around. Since Starscream is not re-entrant, there are some rules which all
read and write handlers must follow.
1. They must not call any of the following routines:
s68000reset s68000exec
s68000SetContext s68000GetContext
2. They must not attempt to read or modify s68000context directly. All
data in s68000context is undefined.
The primary purpose of a read handler is to return data, and the primary
purpose of a write handler is to take data and store it somewhere. However,
read and write handlers do have some control over the emulation process,
beyond the data that they send or receive:
* You can call s68000interrupt() as much as you like (see section 3.7).
* You can cause s68000exec to quit early, by calling the
s68000releaseTimeslice() function. This is useful if you need to transfer
control over to another emulated CPU.
* You can read and/or clear the odometer, with some specialized functions
(see section 3.6).
* You can read the program counter with s68000readPC(), but it probably
won't be located exactly at the beginning of the instruction.
3.6. The Odometer
------------------
Every context contains an integer field called "odometer". Whenever you
call s68000exec, the number of elapsed clock cycles is added to
s68000context.odometer.
Note: When calling s68000exec, the number of elapsed cycles is not
necessarily the same as the number of cycles you specify. Imagine this
scenario: You call s68000exec(100). The next instruction in the code stream
is...
movem.l ($100000).L,d0-d7/a0-a7
s68000exec would quit after that one instruction, returning the success code,
80000000h (assuming nothing else went wrong), and s68000context.odometer
would be incremented by 148.
You can freely change the odometer between calls to s68000exec, but not from
within read/write handlers (as it's part of the context). If you need to use
the odometer from a read or write handler, there are some specialized
functions for that:
s68000readOdometer() - Returns the value of the odometer.
s68000tripOdometer() - Returns the value of the odometer, and sets it to
zero in the process.
s68000controlOdometer(n) - If n==0, it works just like s68000readOdometer.
If n!=0, it works just like s68000tripOdometer.
(We have Neil Bradley to thank for this gem...)
3.7. Interrupts
----------------
Hardware interrupts can be generated at any time, including within read/write
handlers, by calling s68000interrupt(l, v) where l is the interrupt priority
level, and v is the vector number. Levels 1-7 are valid; vector numbers
0-255 are valid. Vector number -1 means "auto-vectored". -2 will generate a
Spurious Interrupt.
The interrupt in question will be made pending until the Processor Priority
Level allows it to be processed. You can tell which interrupt levels are
pending, if any, by examining s68000context.interrupts[0]. Bits 1-7
correspond to interrupt level 1-7; bit 0 is set if the CPU is in a stopped
state (waiting for an interrupt). The vector numbers are stored in
s68000context.interrupts[n] where n is the interrupt level.
s68000interrupt returns one of the following values:
0 The interrupt was added successfully.
1 The interrupt was not added, because there was already a pending
interrupt at the same level.
2 The interrupt was not added, because the parameters were invalid.
Pending interrupts are checked at the following times:
* s68000exec checks for pending interrupts before executing any code. The
cycles taken by interrupt processing are subtracted from the total "cycle
budget", though s68000exec is still guaranteed to execute at least one
instruction.
* Interrupts generated from within a read/write, RESET, or BKPT handler will
be checked when the current instruction is finished.
* You can force a pending interrupt check by calling s68000flushInterrupts.
This has no effect if called from within a read/write, RESET, or BKPT
handler.
Starscream can call a user-defined function when handling interrupts.
The handler must take no arguments, and return void.
s68000context.inthandler = my_int_handler;
3.8. Emulating More Than One CPU
---------------------------------
If you want to emulate multiple 68000s, you'll need to create a context and a
memory map for each one (see sections 3.2 and 3.3). For example:
struct S68000CONTEXT myContext[number_of_68000s];
When you use your own contexts, it's a good idea to initialize all the bytes
in the new contexts to zero:
memset (myContext, 0, sizeof(myContext));
To use one of your own contexts, you must follow these three steps:
1. Copy your context into s68000context:
s68000SetContext(myContext);
2. Use a function which reads or modifies the context - s68000exec,
s68000reset, s68000interrupt, etc.
3. Copy s68000context back into your context:
s68000GetContext(myContext);
Expanding on the example in section 3.2 - say you're emulating two 68000s,
and you've set up two contexts for them:
struct S68000CONTEXT myContext[2];
To reset both CPUs, you'd do this:
for(i = 0; i < 2; i++) {
s68000SetContext(&myContext[i]);
s68000reset();
s68000GetContext(&myContext[i]);
}
An emulation loop involving both CPUs might look something like this. (Note:
This is a CRUDE example.)
while(!done) {
/* Emulate primary 68000 */
s68000SetContext(&myContext[0]);
s68000exec(100000);
s68000GetContext(&myContext[0]);
/* Emulate secondary 68000 */
s68000SetContext(&myContext[1]);
s68000exec(100000);
s68000GetContext(&myContext[1]);
}
The overhead of copying CPU contexts is compensated by the fact that you can
emulate the CPUs in large timeslices.
3.9. RESET and BKPT Instructions
---------------------------------
Starscream can call a user-defined function to emulate the RESET instruction,
and to notify when a BKPT instruction is executed. The handlers must take no
arguments, and return void. For example:
void my_reset_handler(void) {
/* Reset some hardware */
}
Note: RESET and BKPT handlers must follow the same rules as memory
read/write handlers (see section 3.5).
To register a handler function, set the "resethandler" and/or "bkpthandler"
field of the appropriate context to point to the function, i.e.:
s68000context.resethandler = my_reset_handler;
You can also set "resethandler" or "bkpthandler" to NULL, in which case:
* A RESET instruction will cause an Invalid Instruction case. s68000exec
will halt and return its address.
* A BKPT instruction will behave normally, but without notification.
Calling s68000readPC() during a RESET or BKPT handler will return the address
directly after the RESET or BKPT instruction. So, to determine the exact
BKPT opcode, you can do this:
opcode = s68000fetch(s68000readPC() - 2);
3.10. Using the Interactive Debugger
-------------------------------------
Included in the Starscream package is an interactive 68000 cross-debugger and
built in disassembler, with an interface similar to the MS-DOS DEBUG utility.
CPUDEBUG.C contains everything needed for the debugger. If you don't need
it, you can leave out CPUDEBUG.C and CPUDEBUG.H altogether.
Before using the debugger, make sure to set up your memory map, and call
s68000init() and s68000reset().
To start the debugger, call the cpudebug_interactive routine, which is
defined in CPUDEBUG.H as:
int cpudebug_interactive(
int cpun,
void (*put)(const char*),
void (*get)(char*, int),
void (*execstep)(void),
void (*dump)(void)
);
"put", "get", "execstep", and "dump" are all optional - you can pass NULL
values for any or all of them. Their purpose is explained below.
If you're only emulating one CPU, use a value of 1 for "cpun", and keep
calling cpudebug_interactive until the return value is -1.
If you're debugging more than one CPU, assign an ID number to each (starting
at 1), including any non-68000 CPUs. Switch to the context of the CPU you
want to debug, and then call cpudebug_interactive with the appropriate ID
number in "cpun". Then, take one of the following actions depending on the
return value:
-1 Quit
0 Switch to the next CPU in your list (ID + 1), wrap around if necessary,
and call cpudebug_interactive again. Or, if the CPU in question is not
a 680x0, then use whatever debugger is appropriate.
N Switch to CPU #N. If N is invalid, don't switch. In either case, call
the appropriate debugger.
While you're at the debug prompt, enter "?" for a list of commands.
Normally, the interactive debugger uses stdin and stdout for its input and
output. If you need to use another source of input or output, you can write
custom replacement functions for puts() and gets(), and pass their addresses
via "put" and "get".
Normally, the debugger calls s68000exec() directly to step through each
instruction. If your emulator manages its own timing, you can write a custom
replacement single-step function, and pass its address via "execstep".
The debugger command 'h' (Hardware dump) will call the function specified by
"dump", if it exists.
3.11. Emulating the 68010
--------------------------
To generate a 68010 emulator, use the "-cputype 68010" option when building
your source code file. The names of all identifiers will begin with s68010
instead of s68000 (unless overridden by the -name option).
When emulating the 68010, use the S68010CONTEXT structure instead of
S68000CONTEXT.
S68010CONTEXT has six new fields:
unsigned char sfc;
unsigned char dfc;
unsigned vbr;
void (*bkpthandler)(void);
unsigned char loopmode;
unsigned char contextfiller10[3];
"vbr", "sfc", and "dfc" are control registers.
"bkpthandler" is a user-defined routine for BKPT notification (see section
3.8).
"loopmode" is used internally for loop mode timing. (Did I mention the loop
mode timing is insanely accurate?)
"contextfiller10" is there just to maintain alignment.
An invalid MOVEC register code will cause s68010exec to return with an
invalid instruction error, pointing to the MOVEC instruction (not the code
itself).
NOTE: The interactive debugger currently does not support the 68010.
-----------------------------------------------------------------------------
4. Tricks of the Trade
-----------------------------------------------------------------------------
4.1. Inter-CPU Communication
-----------------------------
Many arcade and console game systems which use multiple CPUs use a sort of
"mailbox" technique to allow the main CPU to send messages to the sub CPU
(sound effect numbers, etc.)
When one of your 68000s writes to another CPU's mailbox, it's generally a
good idea to transfer control over to the other CPU temporarily. This can be
achieved by calling s68000releaseTimeslice() from your I/O handler. This
will cause s68000exec to exit, even if it's not finished. (Always check the
odometer to see how many cycles were really executed!)
4.2. Other Helpful Tips
------------------------
When defining your memory map(s):
* Take advantage of the built-in RAM and ROM handling as much as possible.
They involve much less "red tape" than using your own handlers.
* The addresses don't have to be in order. You can rearrange them so that
the most commonly-accessed areas are at the beginning. This will shave a
few cycles off each access.
* If you have two areas of fetchable RAM or ROM that are right next to each
other, consider coalescing them into one area. This will make the memory
map simpler and facilitate fetch region caching. It will also allow the
680x0 code to wander from one area into the next. Starscream only checks
boundaries after JMP, JSR, RTS, RTR, RTD, RTE, and exceptions.
When executing code:
* Instead of calling s68000exec with a fixed number of cycles, keep track of
how many cycles overflowed from the last call to s68000exec, and subtract
them:
#define TIMESLICE 100000
s68000context.odometer = 0;
while(!done) {
if (s68000context.odometer < TIMESLICE) {
s68000exec(TIMESLICE - s68000context.odometer);
}
s68000context.odometer -= TIMESLICE;
}
* Use big timeslices. The fewer calls you make to s68000exec, the better.
4.3. Pitfalls
--------------
* Make sure to include "starcpu.h" in any of your C modules that use
Starscream. Also remember that starcpu.h is subject to change in new
versions. (Hint hint.)
* Remember to call s68000init before executing any code. s68000init doesn't
automatically call s68000reset, so you must call s68000reset also.
* Remember to set up your memory map before calling s68000reset. The memory
map is required in order to read the initial SSP/PC from the vector table.
* When you store a context via s68000SetContext, remember to read it back
via s68000GetContext.
* The interactive debugger can only read memory areas that are defined in
your STARSCREAM_PROGRAMREGION array. Everything else will appear as FFFF.
The upshot of this is that a memory dump is guaranteed not to trigger any
I/O hardware.
* Simplify your STARSCREAM_PROGRAMREGION map as much as possible, in case
the emulated code decides to wander from one area to another.
* Remember the rules for read/write handlers (section 3.5), and remember
that they also apply to RESET and BKPT handlers (section 3.9).
* When you use your own contexts, make sure to explicitly set "resethandler"
and "bkpthandler" to NULL if you're not using them. (This is why it's a
good idea to initialize every byte of a new context to zero.)
4.4. Ways to Abuse Starscream
------------------------------
For the adventurous...
* Upon encountering an unrecognized opcode, s68000exec will return the
address of the opcode, instead of the success code, 0x80000000. If you
need to emulate an instruction that's not implemented yet, you can take
advantage of this behavior and implement it in your own code.
Outside of s68000exec, the context can be modified freely. You can use
the s68000fetch routine to fetch the unrecognized opcode and anything
after it (see section 3.1 for the declaration of s68000fetch).
* Using 32-bit addresses (-addressbits 32) saves one cycle for every memory
access, and simplifies the PC re-basing process. This is safe only for
software that is "32-bit clean", however.
* Make a stand-alone 68000 disassembler out of CPUDEBUG.C. ;)