Z80: What's the deal with the IN / OUT operands?

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
presh
Manic Miner
Posts: 237
Joined: Tue Feb 25, 2020 8:52 pm
Location: York, UK

Z80: What's the deal with the IN / OUT operands?

Post by presh »

So as a kid, one of the many awesome things Your Sinclair taught me was that to read Sinclair Interface 2 (Port 1) from BASIC you had to use:

Code: Select all

LET si2 = IN 61438
Yup, fine. It gives me a number that I can use. All good. I can hide behind the dashboard, safe from the mechanics of the engine under the hood... I'm fine with that to get the job done.

But then when I was learning Z80, this crops up:

Code: Select all

IN

Allowed Instructions

in a,(n)        ;8-bit constant
in a,(c)
in b,(c)
in c,(c)
in d,(c)
in e,(c)
in h,(c)
in l,(c)
in (c)          ;undocumented command
Wuuuuuhhhh... that's an 8 bit register. Where's B gone? Is it even required? And if so, why were we told to read port 61438 (0xEFFE) and not just 254 (0xFE)? Is B still there, hiding in the shadows in some undocumented manner?

And to top it off, (C) is in brackets... which generally means a pointer is being referenced, like you'd find in OR (HL) or similar... that doesn't make sense, does it? BC is an address, not a pointer!!! Eh?? ARGH!!!! :?

Nevertheless, if I do:

Code: Select all

    LD BC, 61438    ; Port to read = Sinclair Interface 2 (Port 1)
    IN D, (C)       ; see what keys are pressed.
...I get exactly what I expected from LET si2 = IN 61438

So my mind wonders... can anybody explain what the heckers is going on here?!?



Furthermore, if I blanked the B register would it still work? I'll be honest, I couldn't be bothered to test that and observe the results, just in case (regardless of the result, because curiosity) I took ages diving down a rabbit hole without yielding anything provable enough to inspire confidence in someone with my level of ability. I've more pressing (read: fun) concerns in the Z80 sphere at the moment, but it's an occasional itch that I'm basically asking you to scratch, if you that's your wont ;)
presh
Manic Miner
Posts: 237
Joined: Tue Feb 25, 2020 8:52 pm
Location: York, UK

Re: Z80: What's the deal with the IN / OUT operands?

Post by presh »

Oh yeah, and how would you use

Code: Select all

in a,(n)        ;8-bit constant
...to read port 61438?

This might be the TL;DR actually
User avatar
djnzx48
Manic Miner
Posts: 730
Joined: Wed Dec 06, 2017 2:13 am
Location: New Zealand

Re: Z80: What's the deal with the IN / OUT operands?

Post by djnzx48 »

Yeah, this used to really confuse me as well. Basically, the Z80 was designed so that I/O only dealt with 8-bit port addresses - for an instruction such as 'out (c), a' the port was just C, and whatever value was in B deemed irrelevant. The opcodes omit B because it's not part of the 'official' port address.

However, when you execute 'out (c), a' the CPU still puts the entire contents of BC onto the address bus to produce a 16-bit port number. It works like when you do a memory read/write, except using /IORQ rather than /MREQ. The reason this was done was presumably to simplify the design.

The designers of the Spectrum decided to take of advantage of this "side effect" and use the entire 16 bits to specify port addresses. Originally, 16-bit ports were only used to select rows when reading the keyboard, but later devices used 16-bit addresses, such as the AY chip and paging register.

(This is also why the OUTI, INI, etc. instructions decrement B for use as a loop counter, causing the port address to get messed up - it doesn't matter if the address is only 8 bits.)
presh wrote: Wed Mar 25, 2020 1:14 am Oh yeah, and how would you use

Code: Select all

in a,(n)        ;8-bit constant
...to read port 61438?
When I/O instructions take an immediate value, the A register is used as the high byte. So you would do it like this:

Code: Select all

ld a, $ef   ; the high byte of the port address
in a, ($fe) ; read the 16-bit port $effe
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: Z80: What's the deal with the IN / OUT operands?

Post by Alcoholics Anonymous »

Agree with all the above with one exception -- the z80 was designed for 16-bit io deliberately. The 8080 was designed for 8-bit io as it puts the same byte on A15-A8 as A7-A0 when doing io cycles. In contrast, the z80 invests extra gates to put a unique 16-bit io address on the bus. The skew toward 8-bit io ports in the instruction set is due to compatibility with the 8080. The use of B as a byte counter in io instructions like INI and OUTI can also act as a register index on a device selected in the lower 8-bits.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Z80: What's the deal with the IN / OUT operands?

Post by Ast A. Moore »

presh wrote: Wed Mar 25, 2020 1:11 am And to top it off, (C) is in brackets... which generally means a pointer is being referenced, like you'd find in OR (HL) or similar... that doesn't make sense, does it? BC is an address, not a pointer!!! Eh?? ARGH!!!! :?
Yeah, the Z80 mnemonics are a bit inconsistent. Mind that only the actual conventional mnemonics are inconsistent; the instructions themselves are interpreted by the CPU in a very consistent manner. In fact, the CPU couldn’t operate if that weren’t the case. Remember, mnemonics are simple shortcuts intended for the ease of keeping track of what’s going on for the human programmer. The Z80 couldn’t care less about how they’re written.

So, IN A,(n) actually means “read a byte from port An (A being the high byte and n being the low byte) and store it in A.” You could just as well rewrite it as IN A,(An). Similarly, IN A,(C) could be written as IN A,(BC).

Regarding the parentheses, there are some inconsistencies there, too. For example, LD A,(HL) means “load the value stored in memory location pointed to by HL into A.” This makes sense—if HL has a value of $4000 and that memory location has a value of $53, then after the instruction LD A,(HL) is executed, the A register will hold $53. All is good. However, if you take an instruction like JP (HL), this logic breaks. One would assume that this means “jump to the address stored in memory locations pointed to by HL and HL+1” (an address is a two-byte value after all), but no, it actually means “jump to the address stored in the HL register pair.” It would be more reasonable to write it as JP HL.

As for the undocumented instructions, well, it is precisely because they’re undocumented that there’s no conventional way of writing them, and each assembler will use a slightly different notation (although, most will try to handle as many as is technically feasible). Take IN (C), for example. Another widely used mnemonic for it is IN F,(C). What this instruction does is it reads a byte from port BC, but instead of loading that value into any particular register, it only analyzes it and changes the flags (i.e. affects the F register). Mind that any IN instruction does that anyway, except that IN F,(C) discards the actual value. Again, it could just as well be written as IN (BC) or IN F,(BC).

Similarly, the undocumented instruction SLL A is sometimes written as SL1 A, because it writes a one into Bit 0 of A. I guess this is done by analogy with SRL (shift right logical) where a zero is written to Bit 7.

Don’t get too hung up on the mnemonics themselves. If instead of IN A,(C) you want to hand-assemble your code and write DEFB $ED,$78—I’m not going to stop you. :D
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
1024MAK
Bugaboo
Posts: 3118
Joined: Wed Nov 15, 2017 2:52 pm
Location: Sunny Somerset in the U.K. in Europe

Re: Z80: What's the deal with the IN / OUT operands?

Post by 1024MAK »

It’s interesting to note that ALL address pins are active no matter what instruction or cycle (instruction fetch, read memory, write memory, refresh memory, read IO, write IO, get interrupt vector from the bus) is being processed.

Of course where the top eight bits of the address comes from does vary depending on which actual instruction or operation is being performed.

More Z80 information than you can cope with on this web site including comprehensive details of the undocumented instructions, undocumented flags and undocumented / poorly documented operations.

Mark
:!: Standby alert :!:
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb :dance
Looking forward to summer later in the year.
User avatar
Seven.FFF
Manic Miner
Posts: 744
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: Z80: What's the deal with the IN / OUT operands?

Post by Seven.FFF »

16-bit I/O has been in Zilog and second-source documentation from the very start. I have a very early manual which makes it crystal clear that B or A is put on the upper 8 address lines.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
Post Reply