19

The ZX81 has a display file which is treated rather like a serial file. And the CPU generates addresses which the ULA uses to look up the characters' bitmaps from the ROM (Yes, I know the actual explanation is rather more complicated than this). I imagined that this arrangement means that the byte in the serial file really must correspond to one characters, so that the appropriate part of the bitmap will get sent to the raster beam; there can't be any way to get the byte in the display file to be sent to the raster beam.

Yet, Some demos really do seem to address individual pixels. I've seen a video of Manic Miner, and a demo called "No Limits" which seem to make some kind of bitmap at a resolution of 256x192.

2
  • Did ZX81 has UDG like ZXS? (user defined graphics) may be they used that... also not sure about the speed but I managed to make double y resolution on ZXS by showing different things on even and odd frames ...
    – Spektre
    Apr 3, 2018 at 10:20
  • Manic Miner is essentially UDG, but trying to cobble them together row-by-row from the ROM. No Limits (and others) are true pixels though.
    – Tommy
    Apr 3, 2018 at 11:02

2 Answers 2

19

The normal process is:

CPU attempts to fetch an op-code instruction byte from the video file. ULA steals that byte as a character code and feeds the CPU a NOP op-code byte instead. Immediately following that is a refresh cycle. The ULA uses the top part of the refresh address but substitutes the character code just read and the contents of an internal three-bit counter at the bottom to form a ROM address. Hence, a row of a character pattern is returned. It ends up in a shift register within the ULA and becomes video.

All RAM is static, so this misuse of the refresh cycle isn't problematic.

The three-bit counter can be reset by software, as can the top part of the refresh address — the Z80 uses the I register for it. For Manic Miner and some other titles, the trick is simply to point to an existing region of the ROM that has close to the full 64 range of possible characters sufficiently close to each other, reset the three-bit counter every line and use a mapping table. You'll notice it's not exactly perfectly mapped; weird distortions occur. That's partly because of the mapping from 256 input values to 64 character codes and partly because there's nowhere in the ROM that has all 64 possible byte sufficiently values near each other. So the game does its best.

No Limits and others use a separate observation: that the unaltered refresh address is observed on the expansion bus and by internal RAM. The version with the character code and row counter imposed reaches the ROM chips only. So they set the refresh address outside of the ROM area and inside the RAM area. Assuming RAM responds during a refresh cycle, what then happens is: CPU attempts to read dummy display byte, it's latched and forwarded, mixed with the row counter and refresh address, to the ROM which doesn't respond. RAM responds to the refresh address instead. That value goes into the ULA's shift register and is output.

Not all third-party RAM expansions respond to the refresh cycle but it's usually a simple modification and most do, anyway.

To follow along on a schematic, see e.g. the one redrawn by Ron Reuter available from here: enter image description here

The ROM is IC2, has its low nine address pins (three for the row counter, six for the character code) connected both to address lines coming out of IC1, the ULA, and IC3, the Z80. Resistors R18–R26 prevent the ULA's address input from flowing back towards the Z80. As both the internal RAM and the expansion port are on the same side of those resistors as the Z80, they see the address the Z80 is generating only — they're unaffected by the ULA. It's almost a prototype of the ZX Spectrum's floating bus.

Chip selects are generated by the ULA; luckily they're independent of whether it knows it is expecting a character graphic. So when the generated refresh address is in RAM rather than ROM, the RAM chip select is active and the ROM isn't, and the ULA's mutation of the refresh address isn't visible to the RAM.

2
  • 1
    My guess is that for static RAM, a refresh cycle is exactly the same as a read cycle. So Sinclair must have known it would be possible to display a bitmap in this way. Why not advertise that fact? Apr 5, 2018 at 8:16
  • From what I understand of your answer, the width of the character is determined by the time taken to execute nop Sep 6, 2022 at 11:47
1

The ZX81 BASIC didn't have UDG features, but the characters were genererated in a similar same way as on the ZX Spectrum anyway, i.e. the characters were stored in an array of 8x8 pixels in ROM.

The difference is the Spectrum copied the characters' 8 bytes to its screen memory area (the part in the first 16 K of RAM that was read by the ULA to generate the picture), whereas the conversion from the ASCII codes stored in the ZX81 screen buffer to a sequence of bits to be sent to the ULA was performed by the Z80 (apparently by driving the ULA to the relevant address in ROM where the bits patterns for the current row to display were stored).

Hence the resulting "slow" mode, because the Z80 was mostly busy displaying the picture rather than running your software.

However, the display being partly done by software allowed a lot of freedom, though it was still limitated to selecting a memory area containing an adequate 8 bytes array of 8x8 pixels patterns. It also seems like they had to be located in ROM (I guess the ULA was made to address only this area in the memory map).

In particular, is was possible to generate high resolution graphics just by software, simply by redirecting the characters table's address to an area in ROM where the machine code bytes formed various relevant 8x8 bits patterns by chance.

Of course it wasn't always possible to find exactly the correct bit pattern for each 8x8 bits screen area, but the software was able to cleverly find the most appropriate location so you had least missing, unwanted or misplaced pixels on the screen.

It was even possible to draw line graphics and the software just had to convert the 8x8 pattern for each of the 32x24 character position in the screen buffer to a pseudo ASCII code that matched the desired pattern in the relocated "characters" table to display your graphics as accurately as possible (still with a few misplaced pixels here and there, depending on the existing patterns in ROM), and even some impressive video games were made that way.

Of course, depending on whether you were drawing line graphics and the translation into "character" numbers was possible on the fly (for instance if you were using premade shapes, UDG like, for instance for game objects), or you wanted to display a full screen bitmap picture, it was necessary to have enough RAM to store your desired pixels map rather than only ASCII codes, so a memory expansion module was necessary in most cases (I was using a Memopack 64k at the time).

Also, the Memotech HRG expansion module allowed full clean high resolution, probably by mapping RAM in place the ROM during display cycles. And it worked really great !

2
  • 2
    ZX81 character coding was certainly not ASCII! Sep 6, 2022 at 14:15
  • You're right, but numbers, alphabetical characters (uppercase only) and special characters were ordered in a similar way, hence the (admittedly abusive) shortcut I used. :p What I meant was the "Sinclair character codes", which is just a bit longer and maybe less familiar to regular readers ? Anyway, my bad. ^_^
    – Z80Man
    Jun 5 at 0:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .