Where to learn?
Note: This article was originally a post at the CPC Zone forums
Well, here's some info that I hope will be useful to both you and others. I know very little about the MSX though, this is mostly about the CPC, although the Z80 assembler is the same for all computers; read on.
First you need to make up your mind on what programme/game you are going to make. BASIC offers easiness and stability and quick development. Assembly code runs fast, but takes a while (so to speak) to write.
On the CPC there's a choice of writing your program in assembler only, in a combination of assembler and BASIC, or in BASIC only (or of course in some other language(s)). You can use a BASIC compiler for speeding up BASIC code. FABACOM compiler speeds up a BASIC program by about a factor of 8. However there are no BASIC sprite routines; these have to be written in assembly anyway or use some ready-made routines.
- 1 Assembly
- 2 The CPC
- 3 BASIC
- 4 Other languages
- 5 A somewhat advanced example: How to use Locomotive BASIC & your own pipe commands
What assembly is
I think you could divide the problem of low level coding into three distinct parts.
- Z80 assembly language
- Assembler syntax
- CPC specifics (memory map, firmware (i.e. OS) and hardware)
By saying "assembly", "assembler" or "asm" (for short) 1. and 2. get intermixed and sometimes confuse people. By saying "assembly" the connotations point more to the z80 assembly language (its commands, a.k.a. "mnemonics". This is source code.); this is a mirror of the z80 architecture as every mnemonic has a direct machine code equivalent (i.e. object code). "Assembler" is a term more properly used for a full-blown program that gets the work done of converting source code into machine code and adds its own made-up commands (which are words that look like z80 mnemonics, but are actually only offered for convenience by the assembler) or directives (words that set up and take care of various things) in the syntax for ease of programming. "asm" may mean both.
Also how a CPC does something, like printing a character on the screen depends on its firmware and hardware and how its memory is organized and used by the firmware, hardware and user-programs. The MSX has totally different hardware so here's where your code starts to differentiate. Since most of the asm code is general purpose, this is why Spectrum ports were so easy to make; just use different screen, sound and keyboard drivers and voila... Of course code that is specifically written for the CPC will take more advantage of its hardware (it will be less portable/general-purpose but the programme/game has the potential to run faster and have more features).
Z80 assembly language reference documentation (not a tutorial)
For general info on the Z80, your best bet I think is Sean Young's (an MSX emulator author) pdf's. These are not tutorials, but contain a list of every z80 mnemonic and how it works (i.e. what parameters it takes and where it stores the result, if there is one.) You should use these mostly for reference, rather for learning.
Pretty complete documentation on the z80, but not a tutorial:
An older version of this file may be found here:
A couple short documents that's more of the same thing:
If you have a look in the latter files, you'll see that there are Z80 commands for moving things around (mostly LD, LDI, PUSH and POP), adding & subtracting & increment & decrement, logical operations, bit operations (bit tests, shifts, rotates), comparing two values & jumps (goto's) & calls ... basically that's all.
What instruction to use depends on two things:
- What you need to do. e.g. You need to add two 8-bit numbers, you use 8-bit addition. You need to add 1 to an 8-bit number, then you increment it by 1. You can do these two things (addition and increment) for 16-bit numbers too.
- What the Z80 is able to do. It just can't do some things, for example it can't multiply two numbers. You'll need to use a multiplication routine for that. You can add a 16-bit value to the HL register pair, but you can't add a 16-bit value to any other register pair (like BC or DE). In order to get around this you'll have to move data around like exchanging the values of registers or saving data into other registers or on the stack or in a memory location. All CISC processors are full of such quirks (they are not "orthogonal", which means you can't combine every command with every possible parameter type(s). Some commands only take specific parameters and you have to live with that).
Assembler is in a class of languages considered error-prone, so you need to take care that your program is stable. Also, by learning z80 assembler you make a step forward for learning x86 assembler since this is a related family of processors.
Z80 Assembly tutorial
So there's the Z80 general stuff that you can learn in any Z80 tutorial. A good tutorial I know of is the Independent Z80 Assembly Guide. Available online at http://sgate.emt.bme.hu/patai/publications/z80guide/. It "was written in a way that it can be used for ANY Z80 calculator or computer". You can use all of this code with a CPC or CPC emulator and it will work. No graphics, sound, file or input routines are included of course; all this is CPC specific, but once you've learnt some of the general z80 stuff, then you'll be able to access the CPC's specific features. I'd recommend using WinAPE's built-in assembler, assemble everything in CPC's memory and then check the results with WinAPE's debugger (or in BASIC with peeks and pokes and/or short BASIC programs). Just put a
in front of each example, and a
if necessary at the end, assemble and then use
from BASIC to run the machine code.
You don't need to know everything in the above tutorial to get you started. I'd say "The Basics" and "Simple Structures" chapters, the "Moving Data Blocks" paragraph and then the multiplication stuff (or skip that and just use a ready-made multiplication routine for now).
Some useful Z80 links:
Really "heavy" official Zilog documentation:
- Z80 Family CPU User Manual]
That tutorial teaches everything about Z80 assembly, but not much in the way of what is not actually a z80 mnemonic (e.g. LD) but an assembler command (e.g. DEFB). I think most directives don't even get a mention, like how to tell the assembler where to put the object code.
Assembly on the CPC is usually MAXAM compatible (like WinAPE's assembler) since that assembler (MAXAM) set a standard on the CPC. (I personally use the WinAPE built-in assembler. You might also like Pasmo which has a feature similar to using local variables in high level language subroutines. In most assemblers all variables are global.) So in CPC assembly, directives like ORG, LIMIT, INCLUDE, EQU, END, BYTE - TEXT - DEFB - DB, WORD - DEFW - DW, RMEM - DEFS - DS, etc. usually depend on how MAXAM defines them. These commands are not actual Z80 commands, but assembler commands/directives. Assembler commands/directives define where to put the compiled code, they define constants, they tell the assembler to include other source files or header files, where and how to reserve space for and how to name variables, etc. Same goes for macros; some assemblers offer the possibility to define and use these for convenience, but they are not part of the z80 mnemonics. Thus macros may result in a little slower code than hand-optimized code.
CPC memory layout and usage
Only 64K is directly accessible by the Z80. On the CPC architecture the last 16K is by default used as the screen data (addresses &C000 to &FFFF). Addresses 0 to &170 and HIMEM+1 to &BFFF is used by the Firmware, BASIC and AMSDOS ROM's. Himem is a Locomotive BASIC variable that reports the highest available free memory location when the CPC boots up. By lowering this value, you assign less space for the BASIC part of your program and its BASIC data (strings, variables, arrays). The rest (from the new HIMEM's value up to the initial HIMEM value when the computer boots up) can be used by your machine code and its data. You alter the HIMEM value by e.g.
from BASIC. Obviously it shouldn't be lower than &170 or higher than HIMEM's initial value. (Sometimes I personally don't use this at all. If I compile something at &4000 and my BASIC program is small, then I just don't bother with MEMORY and HIMEM because usually nothing gets overwritten.)
The CPCEMU Amstrad CPC Firmware Guide has, among other things, detailed information about the RAM layout on 464 and 6128. Yet it's not good practice to just POKE there as it may be incompatible with other models (e.g. the Plus range) or make your program crash whenever the system is not exactly like yours (e.g. expansion boards, which are now a substantial fraction of the remaining CPC users).
CPC Hardware & Firmware
Once you're through learning the z80 features and z80 assembly syntax, you'll have to learn a little (or a lot) about the CPC Hardware and how it works. You may also like to use some of the ready made routines offered by the CPC OS (the "Firmware").
For the CPC specifics I believe your number one source of information should be The Unofficial Amstrad WWW Resource, especially the documents and source code sections.
If you'd like to put some graphics (like tiles and sprites) on the screen you'll need some ready-made routines or start learning on how the CPC's screen works and write your own ones.
The firmware documentation:
The Amstrad CPC Firmware Guide (in .dsk, text and pdf - you have to be a little patient with this link) which describes the ROM routines for loading files, printing characters on the screen, reading keyboard input, drawing lines, using floats/reals and more. It also describes how and what memory is used by the OS. Also online at: http://www.cantrell.org.uk/david/tech/cpc/cpc-firmware/
BASIC and BASIC compilers
CPC 664 user's manual. In case you know some Locomotive BASIC (or you'd wish to learn) it contains general info on the computer along with some short Locomotive BASIC tutorials and full BASIC reference. (There's also a 464 OCR'd .pdf if anyone can upload it somewhere.)
Here's an ok BASIC compiler:
FABACOM/FAST BASIC COMPILER, it compiles Locomotive BASIC source. It seems to not like some commands or parameters of commands. You'll know if your program crashes.
There's at least another two Compilers that are supposed to compile Locomotive BASIC code, but I haven't tried them:
Sean McManus has a nice BASIC tutorial including some assembler routines at:
A VB-style compiler is supposedly coming soon for SymbOS.
A list of languages available on the CPC:
A somewhat advanced example: How to use Locomotive BASIC & your own pipe commands
You can use this for all asm source you have available, like for the tutorial source code.
When calling some asm code from BASIC, you would use something like:
20 MEMORY &3FFF
30 CALL &4000
The Call command calls a machine code routine. If there's nothing at address &4000 then the computer will probably crash.
When you assemble a z80 program it's important to instruct the assembler as to where it should put the resultant machine code. You do this by using the ORG assembler directive, like:
for the BASIC code above. If you have several machine code routines that you would like to call from BASIC then where to call depends on where the routine you want to call starts in memory. However calculating or copying and typing in specific addresses is cumbersome because each time you reassemble your program these memory locations may also change.
The easiest way for calling machine code routines from BASIC is to set up a pipe-commands table. These are similar to the |DIR, |B, |CPM, etc. AMSDOS commands. How to do this is described in official Amstrad documentation: SOFT 968 Section 10 (Expansion ROMs, Resident System Extensions and RAM Programs)
For example, start up WinAPE, press F6, create a New File from the assembler's File menu and copy and paste this code:
KL_LOG_EXT equ &bcd1 org #4000 INITIALIZE: LD HL,WORK_SPACE ;RSX power-up Routine LD BC,RSX_TABLE JP KL_LOG_EXT WORK_SPACE: DEFS 4 ;Area for Kernel to use RSX_TABLE: DEFW NAME_TABLE jp print jp multiply jp mem_move NAME_TABLE: defb 'PRIN','T'+#80 defb 'MULTIPL','Y'+#80 defb 'MEMMOV','E'+#80 DEFB #00 .print ret .multiply ret .mem_move ret
Assemble and type
from BASIC. Then you can call each of these routines from BASIC by name, like:
Note that there's no code for all three commands (|PRINT, |MULTIPLY, |MEMMOVE) in this example, they just ret-urn to BASIC. You'll have to write the code for each command between each label and it's RET mnemonic or paste some existent code and rename accordingly the command names (below the NAME_TABLE label) and the jump table (the jp commands). Every jp corresponds one-to-one with an entry in the NAME_TABLE; the order is significant. NAME_TABLE entries must be all upper case with no underscores or spaces.
Also you'd probably need to supply a number of parameters to each of the routines like you would in a high-level language function call. In this case A contains the number of parameters and (IX+offset) points to each parameter (each parameter has a word width so offset increases by 2).
If your code is 100% assembler you don't need all of this. Just Call to some memory location and never RET-urn to BASIC.