An address error happens on real hardware (or in good emulators such as Regen) whenever a word- or long-sized read or write happens at an odd address. So, for example, if you try to read the contents of address $FFFF0001 as if it were a word or a long, an address error will happen. Sonic 1 displays a simple error message; Sonic 2 locks up; Sonic 3 and Sonic & Knuckles reset the game. None of these scenarios are useful for finding out what caused the error, which is usually what prevents hacks from working on real hardware.
I decided to include the bus error in the debugger solely because it has the same data available for debugging as an address error does; the changes were minor enough that I had no real reason not to include it, but I have doubts anyone will need it.
Here is the debugger: address/bus error debugger; I explain below how it is used. I have tried it with the Hg disassemblies of S2 and S&K; I think it will work on Xenowhirl's 2007 disassembly for all those masochistic enough to insist on using an outdated disassembly; if it doesn't, I would appreciate knowing what errors are printed when assembling so I can fix them. At present, only AS is supported.
To use the debugger, proceed as follows:
1) Extract all files to the base directory on your disassembly. At any point (for example, at the end of the disassembly), add the following line:
include "Address Error.asm"
;Vectors: dc.l System_Stack, EntryPoint, ErrorTrap, ErrorTrap; 4
and change it to this:
;Vectors: dc.l System_Stack, EntryPoint, BusError , AddressError; 4
Vectors: dc.l Vectors, EntryPoint, ErrorTrap, ErrorTrap ; 0
and change it to look like this:
Vectors: dc.l Vectors, EntryPoint, BusError, AddressError ; 0
? @ [ ] \ ( ) + , - . / : ; # $ % & ! *
5) When building the hack for testing, add '-g MAP' to the line invoking asw/asl. This will generate a file '*.map' file that associates each file/line of the source file(s) to an address on the final ROM. The build will be a lot slower, though.
6) If you are using the Hg disassemblies, you are done. If you are using the S2 2007 disassembly, you still have to do a few things:
dma68kToVDP macro source,dest,length,type lea (VDP_control_port).l,a5 move.l #(($9400|((((length)>>1)&$FF00)>>8))<<16)|($9300|(((length)>>1)&$FF)),(a5) move.l #(($9600|((((source)>>1)&$FF00)>>8))<<16)|($9500|(((source)>>1)&$FF)),(a5) move.w #$9700|(((((source)>>1)&$FF0000)>>16)&$7F),(a5) move.w #((dest)&$3FFF)|((type&1)<<15)|$4000,(a5) move.w #$80|(((dest)&$C000)>>14)|((type&2)<<3),(DMA_data_thunk).w move.w (DMA_data_thunk).w,(a5) endm ; values for the type argument enum VRAM=0,CRAM=1,VSRAM=2 ; tells the VDP to fill a region of VRAM with a certain byte dmaFillVRAM macro byte,addr,length lea (VDP_control_port).l,a5 move.w #$8F01,(a5) ; VRAM pointer increment: $0001 move.l #(($9400|((((length)-1)&$FF00)>>8))<<16)|($9300|(((length)-1)&$FF)),(a5) ; DMA length ... move.w #$9780,(a5) ; VRAM fill move.l #$40000080|(((addr)&$3FFF)<<16)|(((addr)&$C000)>>14),(a5) ; Start at ... move.w #(byte)<<8,(VDP_data_port).l ; Fill with byte - move.w (a5),d1 btst #1,d1 bne.s - ; busy loop until the VDP is finished filling... move.w #$8F02,(a5) ; VRAM pointer increment: $0002 endm
vdpComm function addr,type,rwd,(((type&rwd)&3)<<30)|((addr&$3FFF)<<16)|(((type&rwd)&$FC)<<2)|((addr&$C000)>>14) ; values for the type argument VRAM = %100001 CRAM = %101011 VSRAM = %100101 ; values for the rwd argument READ = %001100 WRITE = %000111 DMA = %100111 dma68kToVDP macro source,dest,length,type lea (VDP_control_port).l,a5 move.l #(($9400|((((length)>>1)&$FF00)>>8))<<16)|($9300|(((length)>>1)&$FF)),(a5) move.l #(($9600|((((source)>>1)&$FF00)>>8))<<16)|($9500|(((source)>>1)&$FF)),(a5) move.w #$9700|(((((source)>>1)&$FF0000)>>16)&$7F),(a5) move.w #((vdpComm(dest,type,DMA)>>16)&$FFFF),(a5) move.w #(vdpComm(dest,type,DMA)&$FFFF),(DMA_data_thunk).w move.w (DMA_data_thunk).w,(a5) endm ; tells the VDP to fill a region of VRAM with a certain byte dmaFillVRAM macro byte,addr,length lea (VDP_control_port).l,a5 move.w #$8F01,(a5) ; VRAM pointer increment: $0001 move.l #(($9400|((((length)-1)&$FF00)>>8))<<16)|($9300|(((length)-1)&$FF)),(a5) ; DMA length ... move.w #$9780,(a5) ; VRAM fill move.l #$40000080|(((addr)&$3FFF)<<16)|(((addr)&$C000)>>14),(a5) ; Start at ... move.w #(byte)<<8,(VDP_data_port).l ; Fill with byte loop: loop: move.w (a5),d1 btst #1,d1 bne.s loop ; busy loop until the VDP is finished filling... move.w #$8F02,(a5) ; VRAM pointer increment: $0002 endm
EniDec := Eni_Decomp KosDec := Kos_Decomp PlaneMapToVRAM := Plane_Map_To_VRAM
with this:
Chunk_Table := Metablock_Table PlaneMapToVRAM := ShowVDPGraphics
movea.w #$0001,a0 jmp (a0)
Of course, the disassembler does not initialize the z80 or the VDP, instead assuming that they have been initialized already; so the test code must go after the initialization code for them both, or you will get a black screen. Here is a sample of the output:

It has a readable version of all data saved on a bus/address error, all registers, and a disassembly of the instruction that caused the address error. The disassembler was hand-coded, and recovers as much information as possible. Jumps, branches and loops usually do not allow full recovery, with jsr, bsr, and being exceptions. Nevertheless, only jmp will be an issue because all the others are caught by the assembler when building the ROM. The disassembler also warns when the disassembly could not retrieve all information.
Being in possession of all this data and of the map file allows you to quickly find where the error happened and give you an idea why.
I would appreciate if anyone could test this on real hardware; I know it works on Regen, but I don't know if Regen works the same way as real hardware does with respect to the behavior of the program counter.
For those that got this earlier in another thread: this version has a bug fix on the disassembler, and does not need separate versions for S2 and S&K disassemblies; you should grab this version.
Please ignore this attachment, it is of the older version:
Attached File(s)
-
AddressError.bin (170bytes)
Number of downloads: 4


31



