• Please review our updated Terms and Rules here

Help with z80asm, compiles correctly, but address ranges corrupt Hex file output.

I'm still trying to get my head around whether 0FE0AH was I/O in memory space, or whether the original objective was to use 0FE0AH as I/O space?

Aren't all ports I/O space? In which case,

LD BC,0FE0AH
IN A,(C)
OUT (C),A
Would load and output the accumulator to a 16 bit I/O port by placing BC on the address bus during an I/O cycle.

Port input could also be done by

LD A,0FEH
IN A,(00AH)
As the A register appears on A8-A15 during an I/O cycle.

Or was the 0FE0AH address literally a memory address being used as a defacto I/O address by the memory space decode? I didn't notice which he said, but language like Port usually makes me thing of I/O.

I know a lot of people don't consider the z80 to have 16 bit I/O address space, but I use it like that at times and while it's not 8080/8085 backwards compatible it works fine on the z80 and with z80 assemblers. Pretty much any old Spectrum programmers always think in 16 bits for I/O.
 
I know a lot of people don't consider the z80 to have 16 bit I/O address space, but I use it like that at times and while it's not 8080/8085 backwards compatible it works fine on the z80 and with z80 assemblers. Pretty much any old Spectrum programmers always think in 16 bits for I/O.

It's extremely uncommon on anything not a Spectrum, however. And it's very common for non-Spectrum busses to make specific assumptions about I/O transactions only having eight significant address bits, meaning if you tried to mix a peripheral that tried to do "16 bit I/O" with existing devices you'd only experience sadness. (IE, they do things like having separate R/W signals for I/O and memory, allowing I/O devices to cut corners on address decoding.) S-100 would fall into that category.

Anyway, yeah, per that other thread, the device in question was actually using memory mapped I/O. That actually apparently wasn't that uncommon with S100 cards, the well-known NorthStar disk controller used it.
 
Similarly, x86 uses a 16-bit I/O space (reserved for the INxx and OUTxx insttructions), x80 is only 8-bit (as mentioned) and accessible only with the 8080 IN and OUT instructions and the Z80 INSxxx and OTSxxx instructions. However, it is possible to expand that to 16 bits by using specific I/O instructions from the 0xeb clan. See here and the Z80 chip documentation. This behavior extends to the ez80 processors, Z280, Z180 CPUs apparently.
 
Reading the other thread (can we somehow combine them or at least lock one?), it seems that someone is giving the impression that the 16-bit I/O space is the same as the 16-bit memory-mapped space. This is not true. In other words, I/O ports 0-255 do not correspond to memory locations 0-255; neither do I/O ports 256-65535 correspond to memory addresses 256-65535. They are two entirely separate address spaces that have nothing in common. I/O instructions INxxx and OUTxxx can move data between the two spaces.

If one comes from the 68xxx or 65xx CPU world or even the DEC PDP/VAX world, this may seem like a foreign notion.

ARM memory-maps its I/O to great advantage, but then, ARM starts with a much larger address space where there is room for both data and I/O. I believe the same situation applies to the MIPS CPUs.
 
I think we had a discussion here earlier on why the z80 has 16 bit I/O space, yet was only designed to use 8 bits and the two most likely reasons were;

A) It was intentional, and while you can use 16 bit space for a specific architecture, it was probably intended for situations where you've mapped a large number of registers to a single I/O address or

B) It only ever had 8 bits of space, and for reasons unknown to us, perhaps even backwardly reflecting (A) they documented it and it's pure coincidence it happened like that.

It would be interesting if anyone comes along who was there at the time and knew the truth. My current project architecture does use this, but I only make
use of 128 indexes per address, which is still quite a few. I would have used 256, but CP/M prefers blocks of 128. Also if you map memory into I/O space, then the most you can block transfer is 255 bytes, so if you want to fall on a binary boundary, 128 bytes is it.

I keep dreaming that, one day, they'll build a super-chip with hundreds or thousands of z80s each running at 1GHz+ with some kind of super-bus installed and common memory access. Like a thread-ripper, except a Z-ripper. I'm not sure it would serve any real purpose, but I miss 8 bit architectures.
 
B) It only ever had 8 bits of space, and for reasons unknown to us, perhaps even backwardly reflecting (A) they documented it and it's pure coincidence it happened like that.

Per that thread, “B” aligns with how the I/O pins that connect with the register multiplexers are laid out, IE, it would have required additional circuitry to *not* have the “unused” half of certain register pairs show up on those pins during states where the value presented on those pins “officially” doesn’t matter.

Because there’s a chance that this behavior could affect hardware design in, say, cases where a Z80 is being subbed for an 8080 (I’m too lazy to pull out the datasheet right now to see if, say, the value present on the upper address lines during 8080 I/O cycles is always either a specific value or has a relationship to another register or the lower address lines, but I wouldn’t rule it out) it makes sense they’d document it. And, hey, if someone finds a use for it more power to them. (Someone finding a use for it probably explains why they’d carry it forward into future designs.) You can read whatever subtext into it that you want, but the *text* of the documentation is very explicit about the Z80 having 256 I/O ports.
 
The behavior of the B register is documented in the Z80 Assembly language manual (03-0002-00) the case of IN r,(C) and other 0xed instructions. In particular, the INI (and the OUTI mention that B likewise is placed on A15-A8 but mentions that it can be used as a byte counter. Well, we know it is in the case of INIR and OTIR (used as a byte counter), which can be handy, I suppose, for external logic. So, thinking about this, it could certainly be that the non-repeat instructions were simply inheritors of the behavior. After all, on early MPUs, PLA space is at a premium. The reflection of A in the case of 0xdb and 0xd3 to the high byte of the address was probably coincidental; on the 8080 and 8085, the high-order address byte is a duplicate of the low-order one; the Z80's behavior might be traced back to the operation of the WZ registers.

Ken Shirriff has written a few interesting articles on both the 8085 and Z80--well worth reading. Often, the internal operation isn't what you thought it was.
 
Ken Shirriff has written a few interesting articles on both the 8085 and Z80--well worth reading. Often, the internal operation isn't what you thought it was.

@Chuck(G) - Do you know if he ever discussed the topic of the 16-bit i/o address question?

Also, INIR is kind of problematic, because B always decreases. This means either the block direction is reversed ( HL increases, but B decreases ) or you need to use INDR/OTDR instead. Then you get the zero problem next, in that the 0th byte is a 1 byte transfer, while 0 in B means 256 iterations, which is why I ended up going with INDR and OTDR in my architecture, and setting B to 127, to give 127 cycles, then following with a single IND command to complete the last byte transfer.
Though the combination of INIR and INDR does provide some interesting block capabilities for reversing the order of bytes in a sequence.
 
My recommendation is to read the "Z80 Assembly-Language Programming Manual". It describes in a fair amount of detail what's going on. Many people tend to overthink the x80 family as if it were micro-programmed. It's far simpler than that, using a PLA and some fancy logic, which results in a reasonable execution speed.

In the case of the I/O instructions employing the C register as the I/O port designator, BC is transfered to the address bus, so you see a 16 bit I/O address, but only for the non-repeating instructions. On the repeating instructions B is used as a repeat count, the check for zero is made after the decrement operation, so B=0 at the start means that 256 bytes will be transferred, as it goes from 00, ff, fe....to 00.

One error in thinking in the x80 CPUs is why the 16-bit increments/decrements; e.g. INC HL; don't affect the flags. The answer is pretty simple--the increment doesn't go through the ALU! There's a separate 16-bit incrementer/decrementer used not only to advance PC, but also do 16 bit arithmetic. Since it's not part of the ALU, it delivers no flag status. This is true on the 8080, 8085 and Z80. The decrementer can increment/decrement by 1 or 2--that's it.

Another misconception is that the register exchange e.g., EX DE,HL actually moves data between the registers. It doesn't; it merely toggles a flip-flop that says what was DE is now HL, and vice-versa. Called "register renaming".
 
In the case of the I/O instructions employing the C register as the I/O port designator, BC is transfered to the address bus, so you see a 16 bit I/O address, but only for the non-repeating instructions. On the repeating instructions B is used as a repeat count, the check for zero is made after the decrement operation, so B=0 at the start means that 256 bytes will be transferred, as it goes from 00, ff, fe....to 00.

Not quite, the manual does state that BC is placed on the address bus during the repeating instructions.

From the manual;
Description The contents of Register C are placed on the bottom half (A0 through A7) of the address bus to select the I/O device at one of 256 possible ports. Register B can be used as a byte counter, and its contents are placed on the top half (A8 through A15) of the address bus at this time.

David.
 
One error in thinking in the x80 CPUs is why the 16-bit increments/decrements; e.g. INC HL; don't affect the flags. The answer is pretty simple--the increment doesn't go through the ALU! There's a separate 16-bit incrementer/decrementer used not only to advance PC, but also do 16 bit arithmetic. Since it's not part of the ALU, it delivers no flag status. This is true on the 8080, 8085 and Z80. The decrementer can increment/decrement by 1 or 2--that's it.
I noticed that at the blog you referenced earlier - And I had wondered about that also. It can be frustrating when coding and I still make that mistake from time to time.
 
Not quite, the manual does state that BC is placed on the address bus during the repeating instructions.
Perhaps I'm being misunderstood, but that's exactly what I said. Since B is being decremented with every transfer--and so appears with every transfer, it can't be reasonably used as part of a 16-bit I/O address in repeating I/O instructions. BC is also placed on the address bus in any I/O operation that refers to the I/O port in C. (i.e., the ED xx I/O instructions)
 
Since B is being decremented with every transfer--and so appears with every transfer, it can't be reasonably used as part of a 16-bit I/O address in repeating I/O instructions.

This is the QED in my opinion that when Zilog documented this behavior they never intended this to be interpreted as the CPU supporting according-to-Hoyle “16 bit addressing”. I mean, we’re splitting hairs here, but the broadest reading I see in these docs is the behavior as implemented gives you the *option* of using what’s on A8-A15 as an 8-bit ’sub-address’ on each port, and yes, that’s a total of 16 bits worth of address entropy, but:

A: If you use this feature to decode 16 bits worth of “arbitrary” port space you break compatibility with the 8080 on both hardware and software level. (The 8080-compatible IN/OUT instructions using the accumulator will put nonsense port addresses on the bus, right? *only* the “C” variants will work. And on the flip side, any legacy bus like S100 with partially decoded peripherals can’t work unless you modify everything.)

B: The repeating I/O instructions in this universe run from “port to port”, they’re not repeating on a “single” port, and they suddenly come off as really, really broken compared to LDIR and friends. If you want memory mapped IO why not use it?

Anyway, it just seems cleaner/more logical/more accurate to think of this as “sub-port addressing” or “byte counter output” or whatever than true “16 bit port addressing”, but, eh, whatever floats your boat?
 
I think I get what you meant now - we're talking at crossed purposes.

If I understand you correctly, you're stating that you can't set a 16 bit address in BC and repeatedly pull information from the same address into multiple memory locations - As the upper addresses will change. Which practically limits the I/O space to a single dimension of 256 addresses.

While I'm taking about I/O space mapped as 64K of 256 x 256 "Array" and moving information between this array and memory, which works because B is changing.

Though I think we're both agreed that the z80 should usually be limited in concept to 256 I/O addresses.

Although I am planning on adding an ISA bus to my z80 system, so will need to use the upper bits A8 and A9 - but all my reserved ports are from 000 to 07F so it's not too difficult to address the clashes in hardware since the PC ISA architecture uses bit 7 as a user-I/O space decode line. The only hardware I won't be able to use from the ISA architecture is the Joystick Port, and honestly, I think I'd rather redesign my analog joystick input to use ADCs.
 
8080-compatible IN/OUT instructions using the accumulator will put nonsense port addresses on the bus, right?
Well, not nonsense, obviously, the low-order address bus byte contains the port address and it's duplicated in the high-order byte. Different from the Z80, for certain, which in the case of the 8080-compatible IN and OUT, puts the content of A on the upper address bits. Not particularly useful either.

There were a few z80-equipped systems that used the 16-bit I/O address trick. I think that the ZX Spectrum 128 did so with its PSG. At least that's what I gather from the service manual Page 16
 
Last edited:
Well, not nonsense, obviously, the low-order address bus byte contains the port address and it's duplicated in the high-order byte. Different from the Z80, for certain, which in the case of the 8080-compatible IN and OUT, puts the content of A on the upper address bits. Not particularly useful either.

It's more useful than just repeating an address and is a practical way to read and write 8 bits of I/O at the same time with the same instruction...

Which could be pretty useful if your architecture accepts it.

Such as using it to read an array such as a keyboard matrix with a single opcode.
 
Here's where we get into deep-weeds territory. Although the feature is documented, there's no assurance that it's so across the entire range of Z80-ish CPUs. For example, does that behavior apply with the NSC800? I don't know. How about the Rabbit chips?
I can see why Intel was reluctant to publicize the "added/undocumented instructions" on the 8085. A few could be useful to cut a few cycles out of the code, but none provided functionality that couldn't be had using the standard documented instructions. In addition, there are no exact equivalents in the 8086 instruction set, no matter what the original designers thought.

Thinking of the NSC800, an 8-bit I/O port address (0xbb) was hard-wired into the MPU to handle interrupt control. My recollection is that it was written only by the 0xeb IN and OUT and that the regular 0xd3 and 0xdb 8080 instructions had no effect.
 
Last edited:
Well, it is a *documented* practice, with a specific documented hardware outcome, and a specific hardware specification, so I guess anything that didn't support it is as much a z80 as a z80 is an 8080.

And programmers do whatever they feel like, not what the hardware designers wanted.

That said, it's also purely I/O related, and that makes it specifically related to the hardware the code is designed to run on, and CP/M didn't have any code consistencies around I/O and wisely left this to the BIOS.

So any programmer that is going to play it safe is going to program in 8080 as the lowest common denominator. And anything written for a specific platform will know the function is supported if it's a z80 system.

And even then, let's assume it was ephemeral in nature and zilog fully intended to get rid of it ( and documented it anyway for the entire family ) then anyone with the source code can change it to make it work on any new system.

It's not like there's not a precedent for that kind of behaviour either - eg, Intel and AMD Processors.

End users just factored it into their decisions.

Reminds me of the old joke train.

Q. How many computer designers does it take to change a lightbulb?
A. None. We'll fix it in firmware.
Q. How many firmware programmers does it take to change a lightbulb?
A. None. We'll fix it in the Operating System.
Q. How many OS programmers does it take to change a lightbulb?
A. None. We'll document it in the manuals.
Q. How many technical writers does it take to change a lightbulb?
A. None. The user won't be able to read the manuals anyway.
 
It's pretty much all academic at this point. Other than the retro community, I'm not aware of any new product designs using the original Z80 CPU. Even the Z80-based embedded designs are fading out because they're being out-competed by other cheaper architectures.
 
Back
Top