Beside this being a duplicate and in addition to LVD's spot on answer maybe a few remarks regarding your problem:
As he already mentions, Z80 (or better 8080) code is not made for being relocatable. That's something that didn't come to mind when designing it. Everything, even branches, is absolute.
It's the reason why the TPA in CP/M starts at 0100h and MP/M running more than one program needs them to be paged in at address 0000h (*1). CP/M Programs are not relocated when loaded, but simply read in.
The Z80 relaxes this a bit by introducing relative jumps for unconditional, carry and zero, but only with a signed 8 bit offset, so still leaving all other to be relocated.
Do It Yourself
If an 8080 program has to be relocated, then it must be done by an internal or external relocator. Much like on a 6502 (*2). Yes, within limits 6502 code can be made relocatable, but it's a dirty job. But not as dirty as on an x80. Having relative branches does help quite a bit :))
The limited case of a relocator is quite similar to what Apple DOS did. Have
- a table of all target addresses that need to be patched (in your case all within the ROM size)
- a table of code regions to be patched (to avoid patching data)
- Walk through all code in the marked code regions looking for
- 11001001b (11xx1001b for 8080)
- (11001011b only 8080)
- Add to the next two bytes whatever the relocation distance is.
Done. The last step is eased if the base address of the ROM would be 0000h, right?
- Writing a relocator stub moving your monitor to its target address in RAM might be the solution.
As already mentioned, any attempt to make 8080 code relocatable without relocation is an extreme fruitless job. The x80 does not have any kind of relative addressing, so
- All JMP need to be either direct via
JMP [HL] pairs or via a table
- All CALL need to be emulated by some way of going through some call gate
- Some derivative way to do conditional jumps has to be developed, as these are, on an x80, always absolute 16-bit addresses.
The last may in fact be the trap you took when coming from a 6500 - assuming that the x80 offers relative branches in all cases. Surprise, it doesn't and the Z80 only offers very limited relief by adding relative (8-bit signed offset) versions for unconditional jump and jumps on Carry or Zero set or cleared. While this may help in short code sections, it will not really eradicate the need for relocation of long jumps. Absolute jumps are still needed for longer distance and/or conditions based on other flags than Zero and Carry.
So for anything other than very short cases, making relocatable code for an x80 is quite a task
Still, let's look at ways to get at least your own address.
Doesn't that ROM always start at address 0000h after RESET? If so, wouldn't that make every address as it is during compile?
- It always starts at 0000h
Now, if you insist on not relocating the ROM, but wasting code on runtime retargetting, then why not store the new, relocated base address to be used for later action? When resetting, the ROM will be at address 0000h, right? So pick some RAM location to store that base value and have all jumps (and other ROM access) use it.
- Use a RAM location to keep track of the new ROM address.
Know It Yourself
Next, while there is no way to access the PC directly, there's always the 'Known RTS' trick like used on an Apple II by I/O cards to determinate their position/slot they are in. It's based on jumping to a known return instruction in Monitor-ROM (
JSR $FF58), thus delivering the PC on the stack. Accessing the stack pointer on an 8080 is done by simply adding it to HL (which also answers your question about SP (*3)). The address is stored at SP-2, so
will get you your return address.
Since it's your ROM, you may place that RET at any location you like. See below at Know Where for some idea.
- Create your own Known-RTS as part of your ROM
Know It Faster
Of course, since the ROM code is all yours, it's possible to directly return the address from a function located at a known address. Here the mentioned
will do the trick; best of all, it's only two bytes (E1h, E9h).
- Have a fixed 'Get-My-Addr' function as part of your ROM instead of a known RTS
Since the ROM is yours, it should be fairly easy to put it at a location that always will be accessible. More so, if it's always at the same visible ROM address in all versions; then it doesn't matter if a newer version accesses the code from an older one, or vice versa.
Being for an x80 CPU, I assume your Z80 ROM starts at address 0000h with a 3 byte jump to your reset routine. So why not take the same road Kildall did for CP/M, using the following 5 bytes for some important fixed values/entry points? In your case above, the routine to get a caller's address:
Now your ROM may do a
CALL 0003h at any point and will get the address after that call returned in HL.
Since this address is the same for all of your ROM images, no address fiddling is to be done.
- Locate the 'Get-My-Addr' function at a fixed address.
Ask Someone Who Knows
Last but maybe not least, why not ask someone who could look it up, like the mentioned ATmega328? It already monitors the address lines of the Z80 to see if it gets addresses as part of an IN or OUT instruction, by sampling IORQ, right? It could also do so whenever a a code fetch is signalled by M1 being active, except now recording address lines instead.
Unless you want to relocate your ROM on byte borders, it might be enough to just copy the top 8 bits or even less (*4), so maybe only three or four.
A follow up IN (after setting it up by what ever protocol you're using) will report the top 1..8 bits of the actual ROM code location.
*1 - In CP/M 3 only the first bank must start at 0000h as there are no multiple programs supported.
*2 - That's why there are Master Disks for Apple DOS, they contain a relocator on Sector $A/$B of track 0, loaded into $1B00. The relocator has a dedicated table of all addresses to be patched when moving DOS to a different address.
*3 - Which also answers your question how to access SP: Add it to HL :)) I always found this to be an unexpectedly decent solution, as it allows applying an offset right away. Of course, I assume it has been selected due to being symmetric with the remaining 16-bit adds, while a move would have been a new addition - SPHL being an outlier anyway.
*4 - When that ROM gets moved only by a multiple of its own size, the address patch would be reduced to ORing to the upper byte, as by default all values will have zero bits in these positions which differ after relocation.