Preface:
a) This is kind of borderline as it's about generic circuit design. Then again, it's about Z80 and I do see some RC.SE relevant insight that may come from comparing different solutions.
b) There are zillions of ways to do this and it depends on whatever main and side goals there are. The following is my personal take on this issue with an emphasis on pure TTL.
It depends a bit on what the startup code is supposed to allow.
Minimal Setup
If it's strictly about being able to boot from a device, then the most simple configuration is to handle chip select according to read/write access. The goal is to deliver a boot-configuration where all read access is from ROM while writes are directed to RAM and a run-configuration where RAM is enabled for read and write.
The basic setup is like this:
A Flip-Flop (half a 7474)
Four NAND gates (one 7400)
The FF gets reset by system reset
(Z80 /Reset -> '74 1CLR)
The FF clear output is NANDed with inverted write to form ROM /CS
(Z80 /WR -> '00 1A+1B; '00 1Y -> '00 2A; '74 /1Q -> '00 2B; '00 2Y -> ROM /CS)
A15 is NANDed with inverted read to set the FF
(Z80 /RD -> '00 4A+4B; '00 4Y -> '00 3A; A15 -> '00 3B; '00 3Y -> '74 1PR)
Note, RAM CS generation has been left out as the existing circuitry is unknown. It must not be asserted while ROM CS is active (low). With a bit of thinking the inverting NANDs may be used to incorporate it.
How does it work?
- After Reset RAM is write only, while ROM is read only (duh).
- Execution starts at 0000h.
- All read will come from ROM, Code, as well as data.
- All write will go to RAM.
- The boot code can load data from a device (must be in I/O space *1) and write it into RAM.
- Any read access above 8000h will disable the ROM.
- When ready it can jump to any address at or above 8000h.
- Code execution is reading.
- This will disable the boot ROM and enable read and write (or whatever the usual configuration here is).
- If the code loaded is to be executed below 8000h, a trampoline function (aka a jump to destination) has to be moved into RAM above 8000h and called.
With just two very basic TTL this is about the minimum possible in classic systems. The main point is to be invisible after use. It will not inflict any constraints on operation and system resources. It does not reserve any resource past its operation.
Of course it could be extended - for example using the set FF to distinguish between power-on reset and later reset (cold vs. warm boot), plus maybe using the second FF to detect two consecutive resets to be interpreted as hard-reset (doing cold boot instead of warm boot). Or have the second FF be cleared by some I/O port, to enable cold boot from software. Possibilities are a lot.
In later years, ca. post 1980, this would be integrated into a PAL, offering many more ways to combine this. So if your system already includes a PAL, then these two chips may easily move inside, while still keeping the main function of a boot time only solution, invisible to any software later on.
And yes, it's of course possible to write software that moves some boot image into RAM without needing RAM itself. The Z80 got plenty of registers to hold pointers and state variables. Even rather complex things can be done that way. Boot code is a great way to hone your skills.
*1 - This is an x80 CPU, so I/O belongs into I/O address space. Even more as the Z80 offers full 64 KiB I/O address space.