← Back to Courses
module
16

Writing to video memory

Why this matters

Every character you have printed went through RST 8. Someone else's code worked out which pixels to change, and you never saw it happen.

This section is where that stops. You are going to put a shape on the screen that you designed, in a colour you chose, without asking the ROM for anything

  • and then show the same shape in a second place for the cost of one byte.

That last part is the whole trick behind tile-based games, and by the end of this section you will have done it.

The two ports

S15 established the shape of the problem: the picture lives in the VDP's own memory, and the Z80 cannot address it. Everything goes through two ports.

Ports are a part of the Z80 you have not used yet. They are a second, much smaller numbered space - nothing to do with memory - used for talking to hardware. Two instructions reach it:

        OUT (9),A       ; send what is in A to port 9
        IN  A,(9)       ; read port 9 into A

Only A can carry the byte, which is one more reason it is the busiest register you have.

On the Einstein:

  • Port 9 is the control port. Addresses and register numbers go here.
  • Port 8 is the data port. The actual bytes go here.

Telling the VDP where you mean

VRAM holds 16K, so an address needs fourteen bits, and a port carries eight. So an address goes in two halves, both to port 9:

First, the low eight bits of the address.

Then a byte made of three things: bit 7 clear, meaning you are addressing VRAM rather than a register; bit 6 set, meaning you intend to write; and in the bottom six bits, the top six bits of the address.

Bit 6 set is 40h, so that second byte is always 40h plus the high part of the address:

        LD A,<low byte of address>
        OUT (9),A
        LD A,<40h + high part>
        OUT (9),A
        ; now every byte you send to port 8 goes into VRAM, one after another

The VDP keeps count. After the two setup bytes, each byte you send to port 8 lands at the next address along, so a run of data needs the address only once.

Three worked examples

Address 0000. Low byte 0. High part 0. So 0 then 40h.

Address 1800. Low byte 0. The high part is 18h. So 0 then 40h + 18h = 58h.

Address 3B00. Low byte 0. The high part is 3Bh. So 0 then 40h + 3Bh = 7Bh.

The pattern never changes: low byte, then 40h plus the top six bits. In hex it is barely arithmetic - 1800 becomes 58, 3B00 becomes 7B, and the low byte is just the low byte.

What you are writing into

S15 laid out the four regions. Three of them matter now.

VRAM What it holds
0000-07FF Patterns - the shapes, 8 bytes each
2000-27FF Colours - two per pattern row
3800-3AFF The name table - which pattern each cell shows

A cell on screen is 8 x 8 pixels, and there are 32 across and 24 down. To make a cell show something you need all three: a shape, its colours, and an entry in the name table pointing at it.

One limit for this section. Those addresses are right for cells in the top eight rows of the screen. Rows lower down read their patterns from somewhere else, for a reason that needs a section of its own. Stay in the top third for now and everything here works.

Designing a shape

A pattern is eight bytes, one per row, one bit per pixel. A 1 shows in one colour and a 0 in the other.

So draw it on squared paper and read off the rows:

        . . . # # . . .        00011000
        . . # # # # . .        00111100
        . # # # # # # .        01111110
        # # # # # # # #        11111111
        # # # # # # # #        11111111
        . # # # # # # .        01111110
        . . # # # # . .        00111100
        . . . # # . . .        00011000

And you can write it in binary, which keeps the picture visible in your source:

shape:  DEFB 00011000b
        DEFB 00111100b
        DEFB 01111110b
        DEFB 11111111b
        DEFB 11111111b
        DEFB 01111110b
        DEFB 00111100b
        DEFB 00011000b

That is a diamond, and you can see that it is a diamond, which you cannot with 18h, 3Ch, 7Eh, FFh. This is the one place in the whole course where binary literals earn their keep.

Which pattern to overwrite

Here is the thing that makes this section easy, and it is worth understanding rather than just copying.

Right now the name table is a list of ascending numbers. Cell 0 shows pattern 0, cell 1 shows pattern 1, cell 34 shows pattern 34, all the way up. That is what makes MOS's screen a bitmap - every cell has a private pattern, so changing a pattern changes exactly one cell.

Which means you do not need to touch the name table to see your shape. Overwrite pattern 34 and cell 34 changes. Cell 34 is row 1, column 2, counting from zero - a blank part of the boot screen.

The cell at row r, column c is cell number r * 32 + c, and pattern n lives at n * 8. So pattern 34 is at 34 * 8 = 272 = 0110h.

Its colours

A pattern's two colours do not come from register 7. They come from the colour table, laid out exactly like the patterns - eight bytes per pattern, one per row - starting at 2000.

Each byte is two nibbles. The high nibble colours the 1 bits of that row, the low nibble colours the 0 bits. So 90h means the shape is colour 9, light red, and everything around it is colour 0.

Colour 0 is transparent, not black. Where a pattern is transparent you see straight through to the backdrop, which is the blue the screen already is.

So pattern 34's colours go at 2000 + 272 = 2110h.

The program

        ORG 256

        ; --- the shape, into pattern 34
        LD A,10h        ; low byte of 0110h
        OUT (9),A
        LD A,41h        ; 40h + 01h
        OUT (9),A

        LD HL,shape
        LD B,8
send:   LD A,(HL)
        OUT (8),A
        INC HL
        DEC B
        JR NZ,send

        ; --- its colours, into the colour table
        LD A,10h        ; low byte of 2110h
        OUT (9),A
        LD A,61h        ; 40h + 21h
        OUT (9),A
        LD A,90h        ; light red shape, transparent around it
        LD B,8
col:    OUT (8),A
        DEC B
        JR NZ,col

        ; --- and show the same pattern in a second cell
        LD A,24h        ; low byte of 3824h - cell 36
        OUT (9),A
        LD A,78h        ; 40h + 38h
        OUT (9),A
        LD A,34         ; cell 36 now uses pattern 34
        OUT (8),A

        RET

shape:  DEFB 00011000b
        DEFB 00111100b
        DEFB 01111110b
        DEFB 11111111b
        DEFB 11111111b
        DEFB 01111110b
        DEFB 00111100b
        DEFB 00011000b

What you should see

Two light red diamonds, side by side with a gap between them, on the second row of the screen. The boot text is untouched.

Two light red diamonds on the second row of the boot screen

Notice how the pattern was sent: the address once, then eight bytes in a loop, using the same LD A,(HL) / INC HL walk from S11. The VDP advanced its own address each time.

Two diamonds, one shape

The second diamond is the important one.

You wrote the shape once. Then one byte into the name table - LD A,34 - made a second cell show it. Not a copy: the same eight bytes, displayed twice. Write 34 into thirty more name table entries and you get thirty more diamonds, at no cost in memory and almost none in time.

That is a tile, and it is why tile-based games can redraw a whole screen inside a single frame. A screen is not a picture; it is a list of which shape goes where, and the shapes are defined once.

Compare it with what you have been doing. Printing a character means the ROM computing pixel addresses and writing eight bytes per character. Placing a tile means writing one byte.

Change one thing

  • Change LD A,34 to LD A,35. Where does the second diamond go, and what happened to whatever cell 36 was showing before?
  • Change the low byte of the name table write from 24h to 25h. Which cell changes now?
  • Change 90h to F0h in the colour write. What colour are the diamonds, and what happened to the space around them?
  • Change 90h to 94h. Both nibbles now do something. Describe what you see before you work out why.
  • Change the name table write to cell 300 - 3800 + 300 - which is in row 9. The diamond does not appear. That is not a mistake in your arithmetic, and the next section is about why.
  • Delete the colour write entirely and run it. The diamonds still appear. What colour are they, and where did that colour come from?

Exercises

16.1 - Your own shape. Design something on an 8 x 8 grid - an arrow, a letter, a spaceship - and put it on screen. Write it in binary so you can read it back.

16.2 - A row of them. Show your shape across a whole row of the screen by writing its pattern number into consecutive name table entries. One address setup and a loop of OUT (8),A will do it, since the VDP advances the address for you.

16.3 - Two shapes. Define a second pattern in a different pattern number, give it a different colour, and put both on screen. Then alternate them along a row.

16.4 - A striped cell. Make a pattern where every row is 10101010b and give it a colour byte with two different nibbles. Look closely at the result and explain which nibble coloured which pixels.

16.5 - Find a letter in the pattern table. Pick a cell on screen that contains part of a letter - row 5, column 0 is the T of TATUNG - work out its pattern address, and read the eight bytes back. Print each as eight characters, a # for a 1 bit and a . for a 0, one row per line. The letter will be looking back at you. That is the bitmap, and there is no clearer way to see it.

16.6 - Read it back the short way. The datasheet says the second address byte is 00h rather than 40h for a read. Set up a read address, use IN A,(8), and fetch one of your own pattern bytes back. Check it against what you wrote - and be aware the VDP needs a moment after the address setup before the data is ready.

When it goes wrong

What you see What it means
Nothing changes at all The second address byte. It needs 40h added for a write - without it the VDP is set up to read.
The display corrupts wildly Bit 7 set in the second byte, which makes it a register write instead. Keep it under 80h.
The shape appears somewhere unexpected Pattern n is at n x 8, not at n. And cell r,c is r * 32 + c - 32 cells per row, not 40, even though text is 40 columns.
The shape is invisible but the cell went blank Both colour nibbles the same, or both transparent. Try 90h.
A shape in the lower two thirds of the screen never appears The pattern banks. Next section.
Text stops looking right afterwards You overwrote a pattern that MOS was using for a character's pixels. Harmless, and it will come back when the screen scrolls.

Summary

  • OUT (n),A sends a byte to a port; IN A,(n) reads one. Only A can carry it.
  • Port 9 takes control bytes, port 8 takes data.
  • To address VRAM: send the low byte, then 40h plus the top six bits. The address then advances by itself for every byte you send to port 8.
  • A pattern is 8 bytes, one per row, one bit per pixel, at n * 8.
  • Its two colours are 8 bytes at 2000 + n * 8 - high nibble for the 1 bits, low nibble for the 0 bits. Colour 0 is transparent. Register 7 does not do this.
  • The name table at 3800 says which pattern each cell shows. Cell r,c is entry r * 32 + c.
  • One shape can appear in any number of cells for one byte each. That is a tile, and it is the foundation of everything that follows.

Next

S17. Taking over the screen. Your diamonds are sitting on a screen that still belongs to MOS, and a shape in the lower two thirds will not appear at all. Next you find out why, clear all three tables properly, and build a screen that is entirely yours - which means giving up the ROM's text output for good.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.