1st step to Hacker's Guide -------------------------- 0) Content ---------- 1) Types 2) Interaction with memory 3) Exceptions 4) HW emulation 5) Emulated opcode 6) Native features 7) Sources 8) debuggers 9) Checking memory boundary 10) Threads and interrupts 1) Types -------- In "sysdeps.h" there are declared platform independent integer types: int8 - signed 8 bit uint8 - unsigned 8 bit int16 - signed 16 bit uint16 - unsigned 16 bit int32 - signed 32 bit uint32 - unsigned 32 bit int64 - signed 64 bit uint64 - unsigned 64 bit uintptr - unsigned type with size of (void *) intptr - signed type with size of (void *) memptr - this type represents Atari's pointer For compatibility between ARAnyM and UAE CPU there are declared also these types: uae_s8 ~ int8 uae_u8 ~ uint8 uae_s16 ~ int16 uae_u16 ~ uint16 uae_s32 ~ int32 uae_u32 ~ uint32 uae_s64 ~ int64 uae_u64 ~ uint64 uaecptr - this type represents Atari's pointer These types are only for interaction with CPU core. It is better to use uintXX/intXX types than uae_XXX types. For compatibility between ARAnyM and Bochs IDE emulation there are declared also these types: Bit8u ~ uint8 Bit16u ~ uint16 Bit32u ~ uint32 These types are only for internal IDE emulation, not for ARAnyM. 2) Interaction with memory -------------------------- For interaction with memory there are six functions declared in "cpu_emulation.h", three for reading, three for writing: uint32 ReadAtariInt32(uint32 addr) uint16 ReadAtariInt16(uint32 addr) uint8 ReadAtariInt8(uint32 addr) void WriteAtariInt32(uint32 addr, uint32 l) void WriteAtariInt16(uint32 addr, uint16 w) void WriteAtariInt8(uint32 addr, uint8 b) These functions have direct access to memory space. They ignore all settings of CPU (inclusive of MMU). All functions for an emulation of HW (DMA) in ARAnyM must become only physical addresses, not logical. For all emulations of SW, there are another six functions declared in the same place: uint32 ReadInt32(uint32 addr) uint16 ReadInt16(uint32 addr) uint8 ReadInt8(uint32 addr) void WriteInt32(uint32 addr, uint32 l) void WriteInt16(uint32 addr, uint16 w) void WriteInt8(uint32 addr, uint8 b) If you want to use underlying memory (memory allocated in HW I/O address space), you can use special, quick functions (they don't make any checking): uint32 ReadHWMemInt32(uint32 addr) uint16 ReadHWMemInt16(uint32 addr) uint8 ReadHWMemInt8(uint32 addr) void WriteHWMemInt32(uint32 addr, uint32 l) void WriteHWMemInt16(uint32 addr, uint16 w) void WriteHWMemInt8(uint32 addr, uint8 b) If you want make some large operations in ST-RAM or FastRAM, you can use two functions for address validation: The 1st is for non-MMU access: bool ValidAtariAddr(uint32 addr, bool write, uint32 len) You can use ReadHWMemIntXX and WriteHWMemIntXX after this function for ST-RAM and FastRAM with no problems. It doesn't check HW registers! The 2nd is for MMU access: bool ValidAddr(uint32 addr, bool write, uint32 len) This function is secure only for len = 1, 2 or 4! 3) Exceptions ------------- If you want to generate some Exception, it can be done with this sample code: regs.mmu_fault_addr = addr; THROW(No. of Exception); If you need to generate bus error, use macro BUS_ERROR(fault_address) defined in cpu_emulation.h 4) HW emulation --------------- In ARAnyM there are two possibilities how to emulate HW - old, classic, Motorola's - to use HW address space mapped to RAM address space and the second, which needs special drivers - to use emulated opcode. For the second possibility look at sections 5th and 6th of these guide. If you want to write new emulated "HW" from original Atari computers for ARAnyM you must use older (but better) access for it - mapped address space. For inspiration look at "hardware.cpp" and e.g. "mmu.cpp". 5) Emulated opcode ------------------ For implementation of ARAnyM's specific HW, which cannot be supported by any emulator of Atari computer, you can use one opcode from 0x71XX. Look at "emul_op.cpp". 6) Native features ------------------ HW (or better functions) which are "emulator-independent" can use Native features. In progress. 7) Sources -------------- CPU: BasiliskII (CVS 28.1.2006) AmigaXL (Amithlon, AmigaOSXL) (14.9.2002) hatari (CVS 2.2.2002) UAE 0.8.22 MMU patch 2 against 0.8.20 IDE: Bochs (CVS 29.4.2005) 8) debuggers ------------ ARAnyM has four different debuggers: a) uae - based on UAE's debugger, most portable b) ndebug c) mon - based on BasiliskII's monitor, it isn't included in ARAnyM's sources, you could download it from CVS anoncvs@down.physik.uni-mainz.de:/cvs You can select a debugger with ./configure script. There are some functions and macros defined in debug.h for debugger output. 9) Checking memory boundary --------------------------- There are many possibilities in ARAnyM how it checks memory boundary. You can choose one before a compilation process (configure script, parameter --enable-addr-check). The most stupid solution is no check memory boundary. It removes all checks of memory boundary, also ROM space can be rewritten. The second possibility is full checking. Before every access to Atari memory it is used an inlined function check_ram_boundary() (uae_cpu/memory.h). This function checks if desired address and operation (R/W) is correct. If not then Bus error is generated. The third, default, is page check. It is inspired (I think, Johan?) ATC (Motorola's TLB). If desired address is in the same memory page as previous used one then check_ram_boundary() isn't needed and the operation is correct. There are three page buffers - for PC, read and write operation. The fourth can be used only on some platforms (today x86 Linux only). check_ram_boundary() is removed and host's segmentation fault signal is used for checking memory boundary. On some processor it can be quicker than the previous possibility. The fifth is combination of the third and the fourth possibility. The sixth is special possibility, there is sigsegv handler used also for handling access to HW pace. It isn't completely committed today. It is used for JIT compiler mainly. 10) Threads and interrupts -------------------------- I tried several different models of threading in ARAnyM. Although they always worked OK in Linux there often were problems in non-Linux operating systems so the current (0.6.2+?) ARAnyM internals are based on the following model: - CPU MC68040 emulation runs in the main thread. The main() function first calls main.cpp:InitAll(), then Start680x0() (which starts the CPU emulation loop) and when the CPU emulation ends it calls main.cpp:ExitAll() and quits. So not only MC68040 emulation runs in the main thread but also all the EmulOp and Native Features, hardware emulation and VIDEL/fVDI screen output. - Then there is a SDL timer thread that runs with 10 ms resolution (wish it was 5 ms but unfortunately SDL is internally locked to 10 ms resolution). It calls the main.cpp:my_callback_function() that in turn calls TriggerInternalIRQ(). This Trigger does one simple and quick thing: it sets SPCFLAG_INTERNAL_IRQ for the CPU. That's all it does and the timer function then ends. It's a very short function (takes only a few CPU cycles) and so it could be called from a real timer interrupt (is there such available?), preferably running at 200 Hz. - There is also a SDL_GUI thread but this one is running only if the GUI is active. The SDL_GUI thread ensures that the active GUI does not stop the emulation - ARAnyM continues running in background! This is an unusual behaviour and was implemented because I wanted to keep the Atari time in sync. We basically don't have a Hibernate option for the Atari so I had to let the CPU running (including all interrupts and stuff). If I didn't do that the Atari time would be delayed. This allows for very cool effects (imagine Lines.app running in TOS and the GUI dialog above it). If we went for GUI translucence it would be astonishing :-) All the user interactivity and interrupt emulation now relies on the CPU: When the SPCFLAG_INTERNAL_IRQ is set we have to wait until CPU emulation calls SERVE_INTERNAL_IRQ() (which normally happens in m68k_do_specialties() after processing each instruction). This function calls external function main.cpp:invoke200HzInterrupt() (as you see from the name I wish it was called 200 times per second). The invoke200HzInterrupt() then does a whole bunch of things including: - emulates 200 Hz MFP TimerC interrupt (calls mfp.IRQ(5)) - emulates VBL (calls TriggerVBL()) - updates screen (VIDEL emulation) (calls videl.renderScreen()) - process user input: keyboard and mouse events (calls check_event()) - shows visual activity indicator (calls heartBeat()) As you see from the model once the heart beat stops you can no longer expect ARAnyM to follow your keyboard or mouse commands. It also means it's essential to have the CPU emulation correct, not falling into any neverending loops or traps. Luckily we have just implemented solutions for the double bus fault as well as bus error loops so we should be on the safe side now.