Assembly Language Fundamentals

Beginner
Version:
1.0

Hello World

Introduction

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.

How Programs Work on the Einstein

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.

Why We Use Assembly Language

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.

From Assembly Source to Executable Program

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:

  1. Write the program in assembly language and save it as a .asm file.
  2. Assemble the source code to convert it into machine code.
  3. Run the resulting .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.

Dsk Images

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:

  1. Write your assembly code and save it as a .asm file.

  2. Assemble the source code to produce a .com machine code file.

  3. Create a .dsk disk image, which acts as a virtual floppy disk.

  4. Add your .com file to the disk image.

  5. Load the .dsk image into the emulator.

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.

Good News: AsmToDsk Handles It All

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.

Writing Your First Assembly Program

Now it’s time to write your first Z80 assembly program.

Step 1: Create a New Source File

  1. Open your text editor of choice
  2. Save the file with a .asm extension

Step 2: Write the 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)

Step 3: Assemble and Run the 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.

1. Save Your Source File

Make sure your assembly program (for example, hello.asm) is saved before continuing.

2. Open the Toolkit

Launch the Dev_Tools tool by clicking its icon on the desktop. This tool will assemble your program and prepare it for the emulator.

3. Select Your Source File

4. Build the Program

Click the green “Build COM + DSK” button.

The toolkit will now:

5. Run the Program in the Emulator

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.

6. Switch to the Disk with Your Program

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.

7. View the Output

If everything worked correctly, your program will run and display:

After printing the message, it will return you to the command prompt.

8. Exiting the Emulator

When you’re finished, press the Esc key to close the emulator and return to your desktop environment.

Alternative Version: Using ASCII Codes

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.

Printing New Lines

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.

Understanding the Commands in Your Program

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.

ORG — Origin

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.”

LD — Load

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

RST — Restart

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.

DEFB — Define Byte

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.

RET — Return

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.

Putting It All Together

Here’s how these commands work together in your “HELLO” program:

  1. ORG 256 — Tell the assembler where the program starts in memory.

  2. LD A, 'H' — Load a character into the accumulator.

  3. RST 8 — Call the system routine dispatcher.

  4. DEFB 158 — Specify the print routine.

  5. Repeat for each letter.

  6. RET — Exit back to the system when done.

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.

Add Two Numbers

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

Why This Fails to Show a Digit

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.

Show the Actual Digit

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

Exercises

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.)

Summary

Previous Module
Next Module