← Back to Courses
module
3

Your first program

Why this matters

Everything in this course happens on the screen, so the first thing to get working is putting something there. This section prints the word HELLO.

That sounds modest, and it is - but by the time it appears you will have written assembly, turned it into machine code, loaded it onto an Einstein and watched it run, which is the entire cycle you will repeat for every program after this one. You will also have met five instructions that appear in almost everything you write from here.

Setting up your workspace

VS Code works with folders, not loose files, so give it one to work in before you create anything.

  1. Create a folder. Anywhere you like - your Desktop, your home folder - call it something like einstein-course. Every program in this course can live inside it, one .asm file per program.
  2. Open that folder in VS Code: File > Open Folder..., select the folder you just made, and click Open. It now appears in the Explorer panel down the left-hand side, empty.
  3. Create a new file: right-click the folder's name in the Explorer panel and choose New File. (There is also a small icon that appears when you hover over the folder name, if you prefer that.) Type hello.asm as the name and press Return. An empty editor opens for it.

You only need to do this once. Later sections just mean "create a new file in this same folder."

How a program reaches the machine

The processor inside the Einstein - a Zilog Z80 - does not understand words. It understands numbers. Every instruction it can carry out is one or more bytes, and a program is nothing but a run of those bytes in memory.

Here is a real one:

3E 48

Two bytes, written in hexadecimal because that is the conventional way to write bytes. To the Z80 this says "put the value 48 into register A". To a human it says nothing at all without a lookup table, which is why almost nobody writes programs this way.

Instead you write assembly language, where each instruction gets a short readable name called a mnemonic:

        LD A,'H'

That is the same instruction. LD means load, A is where to put it, and 'H' is the character to load. Run it through an assembler and you get back 3E 48 - the exact same two bytes.

Assembly is as close to the hardware as you can get while still writing something readable. There is no translation layer doing clever things behind your back: one line in, one instruction out.

From your source file to a running program

Three steps, and the extension does the last two for you.

  1. You write the program and save it with a .asm extension. At this point it is just text.
  2. The assembler reads your .asm file and turns each line into the bytes the Z80 understands, producing a .COM file - your program as machine code.
  3. The emulator loads the .COM into the Einstein's memory and runs it.

When you press Run, steps 2 and 3 both happen, and about a second later your program is running.

The program

Click inside the empty hello.asm editor and type - or paste - this:

        ORG 256        ; Start of the program in memory

        LD A,'H'       ; Load the letter 'H' into register A
        RST 8          ; Call a system routine
        DEFB 158       ; ZOUTC - print the character in A

        LD A,'E'
        RST 8
        DEFB 158       ; ZOUTC

        LD A,'L'
        RST 8
        DEFB 158       ; ZOUTC

        LD A,'L'
        RST 8
        DEFB 158       ; ZOUTC

        LD A,'O'
        RST 8
        DEFB 158       ; ZOUTC

        RET            ; Return to the prompt

The layout matters less than it looks. Instructions are indented, comments start with a semicolon and run to the end of the line, and blank lines are free. Type it in as it appears here and you will be fine.

Save the file with Cmd+S.

Run it. Two plain-text buttons, Run and Restart, appear at the top right of the editor tab whenever a .asm file is open and active - they are text, not icons, so they are easy to overlook the first time. Click Run.

The Run and Restart buttons at the top right of the editor tab

If you don't see them, the editor panel may be too narrow to show them directly - look for a ... (more actions) icon in the same top-right corner instead, and choose Run from there.

You do not need to start the Einstein yourself. If it is not already running, Run starts it: a window opens, the machine boots, and a moment later your program runs. If the window is already open from an earlier run, the program runs in it straight away. Either way, look for the output in the Einstein window, not in VS Code.

Restart shuts the Einstein down and starts a fresh one. You will not need it yet; it is there for when a program has left the machine in a state you cannot get out of.

What you should see

Ready
>HELLO
 A  BC   DE   HL   PC   SZ-H-PNC
 4F 3D41 1EFF FB3E 0769 10010010
>

There is your word.

The line of numbers

The two lines under HELLO are not part of your program's output:

 A  BC   DE   HL   PC   SZ-H-PNC
 4F 3D41 1EFF FB3E 0769 10010010

When your program finishes, the machine shows you what was in the processor's registers at the moment it stopped, and then hands you back the prompt. You will see this after every program you run, and it is worth ten minutes now, because it is the only debugging tool you have and it costs nothing.

Everything on that line is in hexadecimal.

  • A is the accumulator. It reads 4F, which is 79 in decimal - the code for O, the last character the program loaded. Your program left it there and nothing since has changed it.
  • BC, DE and HL are the other registers, shown in pairs. This program never touched them, so they hold whatever happened to be in them beforehand. When you do use them, this is where you check what you put there.
  • PC is the program counter: the address of the instruction the processor was about to carry out. 0769 is inside the machine's own built-in software, which is where your RET handed control back to.
  • The last column is the flags - single bits recording things like whether the last result was zero or negative. They matter later, when you start making decisions.

Two habits worth forming immediately.

If nothing appears on screen, look at A. It tells you what your program was actually trying to print. If you meant to print the digit 8 and A reads 08, you have the number rather than the character - which is the mistake the end of this section is about.

If the program did not get as far as you expected, look at PC. For a program that finished properly it reads 0769, as above. Anything starting 01 means it stopped inside your own code, and the exact value tells you roughly where.

The five instructions

Five lines did all that. They are worth going through one at a time, because you will be using all five for the rest of the course.

ORG - origin

        ORG 256

ORG is not an instruction for the processor. It is an instruction for the assembler, and it produces no bytes at all. It says: assemble everything that follows as though it will be sitting at address 256.

That matters because assembly is not position-independent. A program has to be built for the address it is actually going to run at, and the Einstein loads .COM programs at address 256. Assemble for the wrong address and the finished program will not work, even though every instruction in it is correct.

256 in hexadecimal is 0100h. You will see both written down.

LD - load

        LD A,'H'

LD moves data into a register or a memory location. It is the most-used instruction in Z80 assembly by a wide margin, because almost everything else - printing, arithmetic, comparing - starts by loading a value somewhere first.

A register is a storage slot inside the processor itself. There are only a handful, they hold one byte each, and they are the fastest storage the machine has. The Z80's general-purpose ones are A, B, C, D, E, H and L.

A is the important one. It is called the accumulator, because most arithmetic and data-handling instructions use it as their working register. When an instruction operates on "a value" without saying where, that value is usually in A.

More examples:

        LD A,65        ; put the number 65 in A
        LD B,10        ; put the number 10 in B
        LD A,(4000h)   ; put whatever is stored at address 4000h into A

Those brackets mean "the contents of that address" rather than the address itself - a distinction that matters enormously later on.

RST - restart

        RST 8

RST calls a routine at a fixed address. There are eight of them, at addresses 0, 8, 16, 24, 32, 40, 48 and 56, and they exist because they are compact: a RST is a single byte where a general-purpose call would be three. Machines built around the Z80 typically use them as quick entry points into their own built-in software.

The Einstein uses RST 8 as the way a program asks the system to do something for it. Think of it as knocking on a door. On its own it is not enough, because the system needs to know what you want - and that is the next instruction's job.

There is a real door there, and it is worth knowing what is behind it. The Einstein has software built into it, and part of that software is a library of routines for the jobs every program needs: printing a character, reading a key, loading from disc, making a sound. RST 8 is how you reach them. They are usually called MCALs, for machine calls, and there are around sixty.

Which means that when you print a character, you are not talking to the hardware. You are asking somebody else's code to talk to it for you. That is a very good deal at this stage - the routine works, it is three bytes to call, and it looks after details you have not met yet, like moving the cursor along and scrolling the screen when the text reaches the bottom.

It is not a good deal forever. Doing work for you means doing work you did not ask for, and a game redrawing the whole screen sixty times a second cannot afford any of it. That is why games talk to the video hardware directly, and why this course will too, once you know the processor well enough for it to make sense. Appendix III has the full trade if you want it now; you lose nothing by carrying on instead.

DEFB - define byte

        DEFB 158       ; ZOUTC

DEFB is another assembler directive rather than an instruction. It says: put this exact byte here. It is how you place data in among your code.

Immediately after a RST 8, the byte you place says which service you are asking for. 158 is the one that prints the character currently in register A - officially named ZOUTC. It has that name so it can be referred to by name rather than number, the same as any other routine; this course keeps using the number, since that is what actually goes in the byte, but comments in the code name the routine too.

So the pair works like this:

  • RST 8 says "system, do something for me".
  • DEFB 158 says "specifically, print the character in A".

The system reads that byte, does the job, and then carries on with the instruction after it. This is worth being clear about, because it looks alarming: 158 is sitting in the middle of your program where an instruction would normally go, and yet it is never executed as one. The system takes it as data and steps over it.

DEFB is not only for system calls. It places any byte you like, and takes a list:

        DEFB 13                        ; one byte
        DEFB 'A'                       ; the same as DEFB 65
        DEFB 'H','E','L','L','O',0     ; six bytes in a row

RET - return

        RET

RET returns from a routine. In a standalone program like this one it hands control back to the Einstein, which is why you end up at a > prompt when it finishes.

You need it. Without a RET the processor does not stop at the end of your program - there is no "end" for it to notice. It carries straight on into whatever bytes happen to sit in memory after your last instruction and treats them as instructions. What that does depends entirely on what was there, which means it is not something you can reason about or rely on. RET is how a program finishes on purpose.

Characters are numbers

The program used 'H', but you could equally write:

        LD A,72        ; 72 is the code for 'H'

Both produce exactly the same two bytes, because 'H' is 72. Characters are stored as numbers, using a standard set of codes called ASCII. The assembler lets you write the character so you do not have to remember the number, and quietly substitutes the number for you.

The codes you need in this section:

Code Character
48 to 57 The digits 0 to 9
65 to 90 A to Z
97 to 122 a to z
13 then 10 Start a new line - both are needed
32 Space

Use whichever form is clearer. LD A,'H' is easier to read; LD A,72 is sometimes easier when you are calculating a character rather than naming one, which is exactly what happens at the end of this section.

The full list is in Appendix I - Character codes, which is worth a glance now and worth keeping to hand later. A few codes on this machine are not what a standard ASCII chart would tell you: 91 to 94 are arrows rather than brackets, and there is no [, ], {, } or backslash to print at all.

Printing on more than one line

Printing a character moves the cursor one place to the right. Getting onto a new line takes two codes, not one, and it catches everybody out the first time.

  • Code 13 returns the cursor to the start of the line it is already on.
  • Code 10 moves the cursor down one line, staying in the same column.

Neither one is a new line by itself. You need both:

        LD A,13        ; back to the start of the line
        RST 8
        DEFB 158       ; ZOUTC

        LD A,10        ; and down one
        RST 8
        DEFB 158       ; ZOUTC

Send only the 10 and your output walks diagonally down the screen, each line starting where the last one ended. Send only the 13 and every line lands on top of the one before. Send both and you get what you wanted.

This is the reason so many file formats and network protocols end their lines with two characters instead of one. You have just met the original.

Printing a number

Now for something that catches everybody out.

The Z80 can do arithmetic, and the instruction for addition is ADD:

        ADD A,3        ; add 3 to whatever is in A

So a program to work out 5 + 3 and print the answer looks obvious enough:

        ORG 256

        LD A,5
        ADD A,3        ; A is now 8
        RST 8
        DEFB 158       ; ZOUTC

        RET

Run it. No 8 appears. Worse than that - if there had been anything already on the line, some of it would have vanished.

The reason is the section above. Call 158 prints the character whose code is in A, and it does not care what you meant. You put 8 in A, so it printed the character with code 8 - and code 8 is not the digit eight. It is a backspace. The system dutifully moved the cursor back one place.

The digit 8 is character code 56, not 8.

The fix is to convert the number into its character code, and for a single digit the conversion is simple: add 48, because the digit 0 has code 48.

        ORG 256

        LD A,5
        ADD A,3        ; A is now 8 - the number
        ADD A,48       ; A is now 56 - the character '8'
        RST 8
        DEFB 158       ; ZOUTC

        RET

Now you get 8.

This distinction between a number and the character that represents it runs through everything. A score of 8 held in memory is the number 8; showing it to the player means converting it to 56 first. It only works this neatly for single digits - anything larger needs splitting up, which is a later section.

Change one thing

Make one edit at a time, guess what will happen before you run it, then run it.

  • Change LD A,'E' to LD A,69, and leave everything else alone. What should the program print now?
  • Put a full line break - 13 then 10 - between the two Ls. Where does LO end up?
  • Now delete just the 13 part, leaving the 10. Then put the 13 back and delete the 10 instead. Three different results from the same three characters, and knowing which is which will save you an afternoon later.
  • In the working addition program, change ADD A,48 to ADD A,47. What character do you get, and why that one?
  • In the same program, change ADD A,3 to ADD A,5. Does the answer still print correctly? What would happen with ADD A,6?

Exercises

3.1 - Print your name. Use LD A,'<letter>' for each character. One line.

3.2 - Three words, three lines. Print three different words, each on its own line, with a full line break - 13 then 10 - between them.

3.3 - Sums. Print the answers to 3 + 4, 6 + 2 and 1 + 7, each on its own line. Keep the answers single digits for now.

When it goes wrong

What you see What it means
hello.asm:7: error: command or comment expected (was RTE ) A typo in a mnemonic. The number is the line, and the assembler shows you what it could not understand. Problems from the assembler also appear in VS Code's Problems panel.
hello.asm:4: error: unable to resolve reference: ONE You used a name the assembler has never seen - often a letter left unquoted, like LD A,ONE instead of LD A,'O'.
The active file is not a .asm file Run works on the .asm file you are editing. Click into the editor tab and try again.
No Run or Restart button visible The editor is too narrow to show them as text. Look for a ... icon in the same top-right corner and choose Run from there, or widen the window.
A character you printed has disappeared You printed a small number as though it were a character. Codes below 32 are controls, not text - see "Printing a number".
Everything lands on one line, on top of itself You sent 13 without a 10. Code 13 returns to the start of the line you are already on; nothing moved down.
Output steps diagonally down the screen You sent 10 without a 13. Code 10 moves down a line but keeps the column, so each line starts where the last one finished.

Summary

  • A program is bytes. Assembly is a readable form of those bytes, and the assembler converts one to the other.
  • ORG 256 tells the assembler where the program will run. .COM programs on the Einstein load at 256.
  • LD puts a value somewhere. A is the accumulator, the register most instructions work with.
  • RST 8 followed by DEFB 158 prints the character in A. The DEFB byte selects the service, and is stepped over rather than executed.
  • RST 8 reaches the machine's own library of routines, the MCALs. Convenient now; something a game eventually has to go around.
  • RET ends the program and returns to the prompt. Without it the processor runs off into whatever follows.
  • Characters are numbers. 'H' is 72 and the digit 8 is 56. A line break is two codes, 13 then 10.
  • The line of registers printed after your program shows its own state at the moment it stopped. A is what it last had in the accumulator, PC is where it got to.
  • To print a single-digit number, add 48 to turn it into a character first.

Next

S4. Loops and memory. Five copies of the same three instructions to print one word is not a way to live. Next you store your text in memory once and walk through it, which needs somewhere to keep it and a way to repeat.

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.