.png)
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.
VS Code works with folders, not loose files, so give it one to work in before you create anything.
einstein-course. Every program in this course
can live inside it, one .asm file per program.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."
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.
Three steps, and the extension does the last two for you.
.asm extension. At this
point it is just text..asm file and turns each line into the bytes
the Z80 understands, producing a .COM file - your program as machine code..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.
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.

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 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.
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.0769 is inside the machine's own built-in
software, which is where your RET handed control back to.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.
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 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 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 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 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
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.
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 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.
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.
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.
Make one edit at a time, guess what will happen before you run it, then run it.
LD A,'E' to LD A,69, and leave everything else alone. What should
the program print now?13 then 10 - between the two Ls. Where does
LO end up?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.ADD A,48 to ADD A,47. What
character do you get, and why that one?ADD A,3 to ADD A,5. Does the answer still
print correctly? What would happen with ADD A,6?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.
| 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. |
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.'H' is 72 and the digit 8 is 56. A line break is
two codes, 13 then 10.A is what it last had in the accumulator, PC is where
it got to.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.