This first module gives you a quick and satisfying win: printing the word HELLO to the screen. Along the way, you’ll see how an assembly program is written, assembled, and executed on the Tatung Einstein.
If you’ve never programmed before, don’t worry — every step is explained clearly and simply. By the end of this module, you’ll have your first Einstein program running successfully.
Every computer, including the Tatung Einstein, ultimately runs machine code — the low-level instructions that tell the processor exactly what to do. These instructions control everything, from loading data into memory to displaying text on the screen.
However, machine code isn’t written in words or symbols that humans find easy to read. It’s a sequence of bytes — individual units of data — which we normally represent in hexadecimal (hex) for readability. For example, a simple instruction might look like this when written in hex:
3E 48
To the Z80 processor inside the Einstein, that is a valid instruction. But to a human, it’s meaningless without a reference table. This is why we don’t usually write programs directly in machine code.
To make programming more practical, we use assembly language — a human-readable representation of machine instructions. Instead of typing numeric codes like 3E, we use short words called mnemonics. These mnemonics directly correspond to machine instructions, but they’re far easier to understand and remember.
For example:
LD A, 48h
This single line means “load the value 48h into register A.” When assembled, it becomes the two machine code bytes shown earlier.
Assembly sits very close to the hardware, which makes it an ideal starting point if you want to truly understand how computers work. By learning assembly, you’ll see exactly how software interacts with the processor and memory — knowledge that will benefit you even when you later move on to higher-level languages.
When you write an assembly program, you save it as a source file with the .asm extension. At this point, it’s just text — the computer can’t execute it yet.
To run it on the Tatung Einstein, we need to assemble it. An assembler is a special tool that reads your .asm file and translates each mnemonic into the correct machine code bytes. The result is a .com file: a binary file containing the instructions the Einstein’s Z80 processor understands.
The process looks like this:
.asm file..com file on the Einstein.This workflow is the foundation of everything we’ll do next. Once you understand this process, you’ll be able to write programs that directly control the machine — starting with a simple HELLO program, and later building up to more powerful and complex software.
There’s one extra step before you can run the program. The emulator expects programs to be stored on a disk image, a single file that mimics a real 3-inch floppy disk used by the original Tatung Einstein. This disk image uses the .dsk extension.
Here’s how the process works:
Once the emulator is running, it will detect the disk image as the second virtual floppy drive, allowing you to access and run your program exactly as you would on the original hardware.
Although this process might sound complicated, you don’t need to worry about the details. The AsmToDsk tool automates most of it for you through pre-built scripts. From your perspective, assembling the code, creating the disk image, and preparing it for the emulator is reduced to just clicking a couple of buttons.
Now it’s time to write your first Z80 assembly program.
We will write a simple Z80 assembly program for the Tatung Einstein. This first program is designed to teach one of the most essential tasks in programming: printing text to the screen.
To keep things simple, we will write a short program that displays the word HELLO when it runs. You can type the following into KWrite, or copy and paste. In the Debian virtual image, copy is Shift + Ctrl + C, and paste is Shift + Ctrl + V. However, in KWrite, Ctrl + C, and Ctrl + V are used.
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 ; Tell the system to print the character in A
LD A, 'E'
RST 8
DEFB 158
LD A, 'L'
RST 8
DEFB 158
LD A, 'L'
RST 8
DEFB 158
LD A, 'O'
RST 8
DEFB 158
RET ; Return to prompt (end of program)
Once you’ve written your program, the next step is to assemble it into machine code and run it on the Tatung Einstein emulator. The toolkit makes this process simple.
Make sure your assembly program (for example, hello.asm) is saved before continuing.
Launch the Dev_Tools tool by clicking its icon on the desktop. This tool will assemble your program and prepare it for the emulator.
Click the green “Build COM + DSK” button.
The toolkit will now:
Click the blue “Run in Mame” button. This launches the Mame emulator and mounts your new .dsk disk image as a virtual floppy disk.
When the emulator starts, you’ll see the Einstein command prompt. By default, this prompt is connected to drive 0.
Your program is stored on drive 1, so you need to switch to it and run the .com file. At the prompt, type:
Replace HELLO.COM with whatever name you chose when saving your .asm file.
Tip: On some keyboards, the : character might not map correctly inside the emulator. If that happens, try pressing the ' key instead.
If everything worked correctly, your program will run and display:
After printing the message, it will return you to the command prompt.
When you’re finished, press the Esc key to close the emulator and return to your desktop environment.
The above version used character literals like 'H' and 'E'. You can also use their ASCII numeric values directly:
ORG 256
LD A, 72 ; ASCII for 'H'
RST 8
DEFB 158
LD A, 69 ; ASCII for 'E'
RST 8
DEFB 158
LD A, 76 ; ASCII for 'L'
RST 8
DEFB 158
LD A, 76 ; ASCII for 'L'
RST 8
DEFB 158
LD A, 79 ; ASCII for 'O'
RST 8
DEFB 158
RET
Both versions do exactly the same thing. Use whichever you find clearer.
If you want to print text on multiple lines, you need a carriage return. In ASCII, this is code 13.
LD A, 13 ; ASCII for carriage return
RST 8
DEFB 158
This moves the cursor to the start of the next line.
I’s important to understand the meaning and purpose of each of the instructions you’ve used so far. These are fundamental building blocks in Z80 assembly programming, and you will use them again and again in almost every program you write.
Purpose:
ORG tells the assembler where in memory your program should begin. It does not generate machine code itself — instead, it instructs the assembler to assemble the following instructions starting at a specific memory address.
Why it matters:
On the Tatung Einstein, .COM programs are loaded into memory at address 256 (decimal), which is 0100h in hexadecimal. This is because the first 256 bytes of memory (0–255) are reserved for the system — things like interrupt vectors, system variables, and firmware routines.
By placing your program at ORG 256, you ensure that it’s assembled to run in the correct location where the operating system expects it.
Example:
ORG 256
This means: “Assemble the following instructions as if they start at memory address 256.”
Purpose:
LD (short for load) moves data into a register or memory location. It’s one of the most frequently used instructions in Z80 assembly because almost every operation — printing, arithmetic, comparisons — begins with loading data somewhere.
Registers and A:
The Z80 has several general-purpose registers (A, B, C, D, E, H, L), but the most important one is A, also known as the accumulator. It’s called this because most arithmetic and data manipulation instructions use A as their main working register.
In the example:
LD A, 'H'
We are loading the ASCII value for the character H into register A. Once A contains this value, we can use it for printing or arithmetic.
More examples:
LD A, 65 ; Load ASCII for 'A'
LD B, 10 ; Load the number 10 into register B
LD A, (4000h) ; Load the value stored at memory address 4000h into A
Purpose:
RST stands for restart. It’s a special kind of subroutine call that jumps to a fixed memory address. There are eight restart addresses in the Z80 (0, 8, 16, 24, 32, 40, 48, and 56). These addresses are usually used by firmware or operating systems for quick access to important routines.
How it works on the Einstein:
The Tatung Einstein firmware uses RST 8 as an entry point into the MCAL (Machine Call Abstraction Layer). This is a set of built-in system routines for common tasks like printing characters, reading the keyboard, or controlling graphics.
When you write:
RST 8
The CPU jumps to a special routine at memory address 0008h. That routine then looks at the byte immediately following the RST 8 instruction (which we specify using DEFB) to decide what action to take.
Think of RST 8 as a “door” into the Einstein’s operating system — you knock on the door with RST 8, then tell it exactly what you want by following it with a code.
Purpose:
DEFB (define byte) tells the assembler to insert a specific byte value directly into memory at that point in the program. It’s not an instruction for the CPU — rather, it’s a way to include data alongside your code.
In the context of RST 8, the DEFB value acts as a function selector. It tells the MCAL system what routine you want to run.
For example:
DEFB 158
158 is the code for the ZOUTC routine, which prints the character currently stored in register A to the screen.
In other words:
Other examples:
You can use DEFB to include data:
DEFB 13 ; Insert carriage return character
DEFB 'A' ; Insert ASCII for 'A'
Or even to store multiple bytes:
DEFB 'H','E','L','L','O',0
This would insert the word “HELLO” into memory, followed by a zero terminator.
Purpose:
RET (return) tells the CPU to return from a subroutine. It pops the return address off the stack and jumps back to where the program was called from.
In the case of a standalone .COM program on the Einstein, RET is used to return control back to the operating system once your program has finished running.
Example:
RET
When the CPU reaches this instruction, it exits your program and returns to the system prompt.
Why this matters:
Without a RET, the CPU would continue executing whatever code follows in memory — which is almost certainly not part of your program. This could cause the system to crash or behave unpredictably. Ending with RET ensures your program finishes cleanly and hands control back to the operating system.
Here’s how these commands work together in your “HELLO” program:
Understanding these five instructions gives you the foundation for nearly every Z80 program you’ll write. From printing text to handling user input or performing calculations, these building blocks will appear again and again as you move on to more advanced programming tasks.
To add a number to the number stored in register A you can use the ADD instruction, as follows:
ADD A, [number]
The following program demonstrates loading 5 into the A register then adding 3.
ORG 256
LD A,5
ADD A,3 ; A = 5 + 3 = 8
RST 8
DEFB 158
RET
If you run that program, it won’t output the digit 8.
The number 8 in binary (00001000) is not the ASCII code for '8'. It’s a control character — the system doesn’t know how to print it.
To show '8', we must convert it to its ASCII code.
ORG 256
LD A,5
ADD A,3
ADD A,48 ; A = 56 (which is ASCII '8')
RST 8
DEFB 158
RET
Adding 48 converts a number 0–9 into the ASCII character '0'–'9'.
See Appendix I for a full ASCII table
Exercise 1.1 — Print Your Name
Use LD A, '<letter>' to load each character of your name. Print it on one line.
Exercise 1.2 — Build a Word Generator
Print three different words, each on a new line. Use ASCII 13 (\r) for line breaks.
Exercise 1.3 — Display Custom Sums
Try new sums that result in single-digit answers:
(We’ll cover multiple digits later.)