Learning Z-80 Assembly Language on the TRS-80
Originally published on: Sun, 08 Nov 2009
My first computer was a second-hand TRS-80 Model I with 16K of RAM and a cassette-recorder for auxiliary storage. I was 17 years old when I received this computer in the Fall of 1982. My goal? Like many kids my age at the time, I had intended to write a video game or two and live happily on the riches that would befall me.
The computer was purchased at an auction and came with a fair amount of books on BASIC including many that just contained BASIC games.
My eldest brother had his own TRS-80, so he let me borrow from his vast library of 80 Micro magazines as well.
My first task was to really try to learn BASIC. I did so by typing in programs from the books and by trying to write my own. I had been familiar with BASIC for a couple of years, but only some of the more simple aspects. I had known that if I wanted to write games, I was going to have to code using some mysterious black art known as machine-language. All of the good games from Big Five seemed to be written in machine-language.
As I kept poring through the backlog of issues of 80 Micro, I learned how to use little machine-language subroutines via the USR() function. In order to execute machine-language from BASIC, you had to first reserve some space at the top of BASIC at the MEMORY SIZE? when the computer powered up. ( Note: there are ways of getting around this that I’ll explain in a future post. ) After that, you had to POKE your machine-language routine into reserved memory a byte at a time. Authors usually placed these bytes in a series of DATA statements and used the READ command to read each one in a loop.
After the subroutine was placed in memory, the last task was to point the USR() function to the routine. The address of the routine had to be broken down into two bytes, the least-significant-byte first. I began to grow comfortable with the conversion routine … dividing by 256 to get the high-byte and taking the remainder to get the low-byte.
I also became very comfortable with hexadecimal notation and conversion to and from decimal notation. The only thing I was really lacking at this point, was learning machine-language itself.
I began reading Hardin Brothers’ 80 Micro column The Next Step. This column was a tutorial on machine-language in a sort of cookbook approach. Brothers would present some sort of a short program in assembly-language ( the human-readable syntax that is then assembled into machine-language. )
Most of his programs were very compact and were wonderful to study. He would present the assembly listing for the machine-language routine complete with hex codes by each mnemonic. I found that by converting those hex codes to decimal, I would see the same numbers in the DATA statements for the BASIC loader for the particular routine.
This revelation enabled me to tinker a little with some of the subroutines presented.
One of the machine-language subroutines that was presented in the Radio Shack Level II BASIC manual was a simple routine that would fill the screen with white space. This was done by storing a character with the ASCII code 191 at each location in the machine’s video memory ( located at locations 15360 to 16383 inclusively. )
To see what the program was doing, the BASIC equivalent was easy enough to understand:
As the program ran, each character position on the screen would fill with a white block:
The equivalent assembly language program usually looked something like this:
ORG 0H
LD HL,3C00H ; 15360 in hex
LD A, 191
LD [HL],A
LD DE,3C01H
LD BC,1023
LDIR
RET
The first line is the ORiGin directive that tells the assembler ( the program that translates assembly-language to machine-language ) what address we plan on starting at. This little routine is relocatable; it can be placed anywhere in memory because it does not internally depend on addresses within itself.
The major work is performed by a Z-80 instruction called LDIR ( LoaD-Increment-Repeat ). LDIR is a block-memory move command that begins by taking the byte at the address held in the HL register pair and stores it at the address in the DE register pair. Then, it decrements the BC register pair by 1 and if BC is not zero, it will increment HL and DE repeat the load-from-HL / store-at-DE / dec BC operation until BC reaches zero.
This routine uses a trick with overlapping memory locations. Note that HL starts at 3C00H and DE 3C01H. When the second iteration of the LDIR loop commences, the value it reads from 3C01H to store in 3C02H was the same as the one in 3C00H. This trick with the source and destination addresses differing by a byte causes the memory at DE for a length of BC to fill with the first byte specified.
So, this is a quick memory-fill routine.
The assembler’s output of this routine might look like the following:
ORG 0000
0000:21003C LD HL, 3C00
0003:3EBF LD A, BF
0005:77 LD (HL), A
0006:11013C LD DE, 3C01
0009:01FF03 LD BC, 03FF
000C:EDB0 LDIR
000E:C9 RET
The hex digits to the left of each instruction comprise the instruction and its operands (if any). Note the line LD A, BF. This is the line that loads a character 191 into the A register. I found that I could poke any character I wanted into byte number five of this routine and could fill the screen with that value.
I was slowly making headway to learning assembly language itself as I was beginning to understand that the output of the assembler program was ultimately a binary with a bunch of bytes that the Z-80 processor understood.
As I continued to study examples in 80 Micro, I showed some of these programs to my brother. He brought over a couple of items that he’d gotten at a clearance sale at Radio Shack: The Radio Shack Editor/Assembler on cassette and William Barden Jr’s book “TRS-80 Assembly Language Programming.”
I tried to leap into the middle of the book as I had done when learning BASIC, but failed miserably. I started anew and took it step by step. I finally started getting somewhere.
It took a while, but I was able to separate the pseudo-operation command from the actual Z-80 commands and began to make my own subroutines. I also began to use tricks with the cassette load-module format that I had seen some video games use. I figured out how to auto-start a program ( no need to type in a slash at the SYSTEM prompt), scroll the contents of the screen and blur the video in a manner similar to the effects that began the game Attack Force. I also figured out how to load a message immediately on the screen and that the asterisk that normally flashed during a casette load could be replaced with a character 191 for a more graphical effect.
By the Summer of 1983, my goal was to finally write this game. Alas, I knew nothing about software design. I would design fragments, but I was used to just sloppily coding something together and cajoling it to work. I really didn’t have a game idea. I was just working on different effects and animations and was trying to stitch them together into a game.
Unfortunately, my cassette recorder ( like many ) was unreliable when saving / loading my games. The EDTASM program required one to assemble to cassette, then reload to test. This process took forever. I had found a way to put my program at a high-enough spot in memory that I luckily found a re-entry point for EDTASM, so that once my code was tested, I didn’t have to reload EDTASM … I just entered SYSTEM and then an address ( that now escapes me ) that would leave me back at EDTASM’s “*” prompt with my source-code intact.
I spent many late summer nights working on that code while my TV was affixed on a then-independent cable channel from Kansas City. I coded while listening to the audio for The Three Stooges followed by a mix of Get Smart, Hogan’s Heroes, and similar syndicated television shows from the 1970’s.
As I toiled away, the TRS-80 video games market crashed. New computers were becoming popular as was the Atari 2600 video game console system. If I had finished a game and had tried to market it through Big Five Software, chances are they wouldn’t have taken it. Nor would any big-league publisher likely have taken on any new games for the TRS-80.
However, I didn’t finish a game. The slow development cycle and the inability to save reliably ultimately left me no choice but to move on. I had looked at getting an expansion interface so that I could then buy a disk drive and a Disk Operating System to minimize my turnaround time, but those options were costly. More attractive computers with color video and relatively inexpensive disk drive options started to take over the landscape.
Using my knowledge of Z-80 assembly-language, I taught myself 6502 assembly-language on one of the Apple II‘s at school. In the Summer of 1984, I bought a Commodore 64 and 1541 disk drive. My days of hacking the TRS-80 were over.