Search code examples
z80

Understanding opcodes in Z80 emulator


I'm currently working with an Z80 processor emulator, as a beginner I found an example with values assigned to the memory in order to show "Hello World!".

1. 0x21, 0x0C, 0X00,// ld hl, 0008
2. 0x06, 0x0F, // ld b,0f
3. 0x7e, // ld a,(hl)
4. 0x23, // inc hl
5. 0xD3, 0x00, // out (00), a
6. 0x10, 0xFA, // djnz
7. 0x76, // halt
8. 0x48, 0x65, 0x6C, 0x6C, 0x6F, // Hello
9. 0x2D, 0x77, 0x6F, 0x72, 0x6C, //  Worl  
10. 0x64, 0x21, 0x20, 0x21//d! !  

I have some questions about the values in memory:

  1. In the first line, for the command 0x21 defines the LD HL,XX instruction, so it would be LD HL,0CH in assembler, what is that 0x00 doing there? I thought LD HL,xx is a 16-bit instruction so is complementing the 0C with eight zeros but I think that is not correct.

  2. In line #5, what is the OUT (00),A doing exactly? I found with some research that OUT transfers the data from acumulator to the output port, but is not so clear for me what is doing with that 0x00 specially.

I would really appreciate any hint or help with my questions, thanks in advance for taking the time to read them.


Solution

  • Answering your questions:

    1. 0x21 is the op-code for the three-byte LD HL, nn instruction. The nn part is a 16-bit immediate operand. In Z80 all 16-bit immedidates are encoded in the little-endian order of bytes, meaning the byte that contains the least significant bits (LSB) comes first and is followed by the byte that contains the most significant bits (MSB) of the value. In your example the bytes are 0x0c, 0x00. The resulting 16-bit value is thus 0x000c and the instruction is ld hl, 0x000c, even though the comment mentions a different value.

    2. out (n), a is a Z80 instruction that outputs the value of the register a to a port. Despite that Z80 ports have 16-bit addresses (just like memory addresses), the n operand that specifies the port is an 8-bit immediate. This immediate goes to the low byte of the full port address whereas the high byte of the address is the value of the register a. Here's what it looks like in my emulator:

        void on_out_n_a(fast_u8 n) {
            fast_u8 a = self().on_get_a();
            self().on_output_cycle(make16(a, n), a);
            self().on_set_wz(make16(a, inc8(n)));
        }
    

    OUT (n), A

    What happens on the hardware level is, the processor sets its signal lines, such as ~IORQ, ~RD, ~WR and the address bus pins, to a specific state so that peripheral devices connected to the processor can recognize the output cycle and "see" which value and to which port is written. You can find further details on what happens during the output cycle in the Z80 user manual, section "Input or Output Cycles".

    Z80 User Manual

    For your emulator to be able to actually output values to peripheral devices you would need that devices, be they real or emulated ones, to be acknowledged of the output and provided with the port address and the output value, so they could figure out if they should react and how specifically they should do that. For example, in ZX Spectrum the three lowest bits of the output port 0xfe (decimal 254) determine the color of the screen border. A Spectrum emulator would thus catch writings to that port, extract the color code from the value and update the screen to show the border of the new color.