WinAPE's Assembler

The Windows Amstrad Plus Emulator WinAPE is a freeware Amstrad CPC/Plus emulator running on Microsoft Windows. The most interresting features of this emulator for developpers are it's integrated assembler and debugger.

At the moment (in my opinion) it is the easiest “all in one” software to get started with CPC cross-developpement: You can write, compile, run, trace and debug your program with the emulator. The main drawback being the “emulator” part. There's always some quirks, timings or obscure things of the original hardware that can hardly be emulated and might ruin your program (esp. demo-like stuff) if you don't check it regularly on the real thing.

WinAPE is still being supported by it's author, so if you find something wrong, do not hesitate to harass Richard Wilson for a bugfix :).

Never trust emulators! They are great tools for developpers but none of them can simulate a real machine 100%. If you don't check your program regularly on a real machine (esp. if you're banging the hardware and/or require precise timings) then you can be sure to run into a lot of troubles in the end (eg. it might not work at all on the real machine. Hello Targhan! :).

How to make a good bugreport

If you've found something wrong in WinAPE (assembler, emulation, …), then send a proper bugreport:

  • Explain the problem:
    1. What you did
    2. How you did it
    3. What happened
    4. What you expected to happen
  • How to reproduce the problem.
  • If possible, send a screenshot, DSK, SNA, source file or whatever that could help to see, reproduce and/or fix the problem.
  • Be polite, be patient :)

You will find the contact details of Richard “Executionner” Wilson on the WinAPE's website or post your message on the CPC-Wiki forum.

Get started: Hello World!

Here we go with a very simple Hello world example program to show the most importants steps when programming software with WinAPE.

1. Open the assembler window

Launch WinAPE and open the assembler window Open a new source tab

2. Write your program and compile it

And here we go! You can write your assembly source now! When you're done, assemble it. The compiler window will popup to show some informations about the compilation and a dump of your program. Here we can see that the compiler reports 1 error.

The error is also reported in the assembler window. You can clic on the error at the bottom and the cursor will be automatically moved on the corresponding line with the error. Assemble the program again after fixing the error. The compiler reports no error, so far, so good!

3. Test your program

Your program is assembled in the memory of the emulated CPC. You just have to call it from the BASIC prompt and... it works!

Using an external text editor

Your sources in your favorite text editor The wrapper in WinAPE The integrated text editor is very minimalistic but handy to quickly test some code-snippet or for writing short programs. For bigger programs, a more sophisticated text editor may be much better. There's many of them already available (personnaly I stick with PSPad on MS-Windows), just pick one which fits your needs.

Once you have your favorite full blown text editor ready, it's pretty easy to use it along with WinAPE. First thing to do is to make a wrapper for WinAPE. The wrapper can be just a single line source file that will stay open in the integrated WinAPE's assembler. It's purpose is to read/include your main source code, the one you are editing with your full blown text editor.

When you want to compile your source, just switch to WinAPE and compile the wrapper (eg. press F9) and that's it!

The wrapper is necessary because once a source file is opened in the WinAPE assembler, it can't be externally modified anymore (eg. from your own text editor). WinAPE doesn't detect if a file in it's assembler window has changed and will just keep the file as it was when it has been opened in the assembler, that is, unmodified.

Download example


Here are all the directives I know of which can be used with the WinAPE's assembler. There's maybe more available, unfortunately the WinAPE's documentation is not as good as the emulator at the moment :)

Sorted in alphabetical order.


ALIGN <expression>

Align the current code to a given boundary. eg. align 256 will page align code.



A MAXAM legacy. This directive is actually compiled as an RST #30 (opcode &F7).


This can be used for example if your program does not use standard ASCII for a character set.


	charset ‘A’,0		; Redefine the character ‘A’ to have a value of 0
	charset ‘A’,’Z’,15	; Redefine the characters ‘A’ through ‘Z’ to have values 15 through to 40
	charset			; Set all characters back to their default ASCII values.



Occasionally it is useful to assemble a program without storing any code. The directive NOCODE achieves this. The directive CODE cancels the effect of NOCODE, and causes storage of object code to be resumed.

It can be used to assemble some routines just to get their symbols available in your program.


DEFB, DB, DEFM, BYTE and TEXT are different names for the same thing. They take a list of parameters, each of which can be an arithmetic expression or a text string . Each expression is evaluated and the result put in the objecr code. Each string is sent directly to the object code, character by character. Strings may be enclosed in either single or double quotes; if the closing quote is omitted the string is assumed to be the rest of the line.

Note: a single character string is considered a numeric constant. Expressions such as “A”+&80 are allowed.


BYTE  1,3,count*3+1,"q" or 128 
TEXT  "A string ending with cr-lf",13,10


DEFS <expression>[,<fill value>]
DS <expression>[,<fill value>]
RMEM <expression>[,<fill value>]

DEFS, DS or RMEM causes the assembler to reserve the specified number of bytes of memory. Both the object code and the storage location are incremented by the value of the expression. The reserved space is filled with zeros. The expression may not contain forward references.


.buffer256  RMEM 256
.word       DEFS 2

Occasionally the reserved space needs to be filled with a value other than zero. This can be done by giving a second expression parameter. The space is filled with the least significant byte of the expression's value.


; Fill &200 bytes with the value &FF
RMEM &200,&FF


DEFW <expression 1>[,<expression 2>,…,<expressnion n>]
DW <expression 1>[,<expression 2>,…,<expressnion n>]
WORD <expression 1>[,<expression 2>,…,<expressnion n>]

Each expression is evaluated and the 2 bytes result put in the object code, low byte first.


address	equ &6128
	WORD &C000,address
	DEFW &0001,&0002,4+6*4,&1234


<symbol> EQU <expression>

The symbol is defined and assigned the value of the expression, which must be well-defined (i.e. contain no forward references). If the symbol is already defined an error message will be given (unless the old value and the new are the same). In other words, EQU may not be used to redefine a symbol.



The END directive simply tells the assembler to stop. It may be omitted, but has two uses:

  1. To avoid assembling the whole program - temporarily put in an END directive.
  2. END causes the storage location to be output in the listing. A useful ploy is to put LIST:END as the last line of source code so you can see where the end of the program is.


INCBIN ”<filename>”

Include binary DATA from a file.

It is recommended to use NOLIST before using INCBIN, this will speed up the compilation (displaying a bunch of data slow down significantly the compiler).


LET <symbol> = <expression>

This has the same effect as EQU except that LET allows redefinition of symbols.


LIMIT <expression>

This directive set the highest memory address for storage of object code.

Some uses of the LIMIT directive:

  1. To prevent memory used by something else being overwritten.
  2. When writing a program with a fixed maximum size (e.g. the size of an EPROM).


  • LIMIT only affects storage of code In memory, not the code location (if this is different).
  • The checking is only done on pass 2, since code is only stored on pass 2.



LIST turns on the assembler listing. This is the initial state. NOLIST turns off the assembler listing. If your source file is huge or you include binary file(s) (with INCBIN), turning off the assembler listing can speed up greatly the compilation time.


ORG <expressionl> [, <expression2>]

The ORG directive tells the assembler what is the code origin (given by <expressionl>) and, optionnaly, the storage location (given by <expression2>).

  • The code origin is the address where the code is meant to be run from.
  • The storage location is the address where the assembled code will be stored.

If the storage location is not provided, the assembler will store the assembled code starting at the code origin.

Sometime it is not possible to store the code at the address where it is to run, because it is being used by something else (e.g. MAXAM or BASIC). In this case, you should provide a storage location. The assembler will evaluate both expressions, set the code origin to the first, but store the code at the second address (the 'storage location').


  • Any number of ORG directives may be used.
  • The expressions may not contain undefined symbols.


; Assemble the code starting at &8000 and store it at the same address
ORG &8000
; Assemble the code starting at &8000 and store the byte-codes at &1000
; (you will have to move the byte-code at &1000 to &8000 to execute it)
ORG &8000, &1000
org &8000
; Main program here
; Then, we compile an ISR with a code origin of &0038 (where it should run)
; but store the assembled code right after the main program using the special
; symbol $ (which is the current address).
org &38, $
; Interrupt Service Routine


PRINT <expression>

This command has been extended to allow variables to be included. To print the value of a variable in hexadecimal precede it with “$” (decimal) or “&” (hex) Example:

PRINT "The code ends at &endprog and is is $len bytes long"


READ ”<filename>”

When the assembler finds a READ directive it will open the specified file (in the same folder the current source is saved or from the include path list), assemble the contents of the file, and then return to the line in memory following the READ directive.

Include Path

If your file can't be found in the current path then WinAPE will use the include path configuration to seach for your file.

  • To configure it, go in the Assemble/Option menu :

Configure the WinAPE's Assembler include path

  • A new window will appears. There you can set one or more include path :

Configure the WinAPE's Assembler include path



Mark the start of a relocatable section of code.



Mark the end of a relocatable section of code.


RELOCATE_TABLE [byte|word] [base_address]

Generate a relocation table.

By default a table of word sized offsets is generated, override this by specifying byte for small code sections. The base_address specifies the relative origin for the values in the table.


The following code will write directly to the emulator memory, run when assembled and break when the instruction at the .break label is reached.

	org #4000
	write direct
	run start, break
	relocate_start			;Start relocatable code section
		dw relocate_count	;Number of entries in the relocation table
		relocate_table start	;Generate a relocation table relative to start
		ld de,#40
;Emulator will start running from the ld hl,break instruction
		ld hl,break
;Emulator will stop at the following NOP instruction
	relocate_end		;End of relocatable code section


Reserved symbol. Hold the number of entries in the relocation table.


Reserved symbol. Hold the size of the relocation table (assuming word entries are used).


RUN <execution_address>[, <breakpoint_address>]

The RUN directive allow you tun execute your program right after it's compilation by using the Run (F9) option in the Assemble menu instead of the usual Assemble (CTRL+F9).

When you are using RUN, your program will be executed directly by modifying the PC register of the emulated Z80 (ie. like a JP &xxxx), therefore no RETurn (eg. to BASIC) is possible.


The following example set the execution address right at the begining of the program in &3000 by using the special reference $ which mean “the current address”:

ORG &3000
LD A,1

The following example will set the execution address at the label _start and put a breakpoint to the label _break:

			RUN _start, _break
			ORG &6128
			ld hl,&C9FB
			ld (&38),hl
_start			; Execution address here
			ld a,1
			call &BC0E
			; Trigger the debugger here
_break			call _disable_firmware



The STOP command causes reading from the file containing the STOP directive to terminate and return to assemble the remainder of the program in memory. The END command would abort the assembly completely.


STR is similar to BYTE and TEXT with the option that it will only take a list of strings and the last character in each string has the top bit set. This is useful when printing a string character by character, as you then only need test if each character has its top bit set to know when you've reached the end of the string. The AMSDOS firmware stores command names in this way.


STR "dummy text string"

Will produce :



All the byte-code produced after a WRITE directive will be written to a file. The default filepath will be the same than your source file (on your hard-drive). To save the binary file in a DSK image, see the WRITE DIRECT directive below.


The following example will output 2 files, code.bin and data.bin, in your PC hard-drive.

write "code.bin"
ORG &1000
LD A,1
write "data.bin"
db "Arkos Rulez!"


WRITE DIRECT [<lower_rom>[,<upper_rom>[,<ram_bank>]]]

The WRITE DIRECT allow you to compile your code into the emulator memory. By default, the base 64Kb RAM is selected but you can select anything else with the optionnals parameters.


  • lower_rom
    Set to -1 to disable or 0 to enable the lower ROM (from &0000 to &3FFF).
  • upper_rom
    Set to -1 to disable or any upper ROM id to enable the upper ROM (from &C000 to &FFFF).
  • ram_bank
    Set it with the MMR command you want.


; Compile a routine directly in bank 0 of page 0
ORG &4000
LD HL,&C000
LD DE,&C001

Save to disk

The byte-code can also be saved directly into a dsk image. To do so, provide the WRITE DIRECT directive a filename prefixed with the CPC drive letter (eg. A: or B:).

This example will save the binary file code.bin in the currently selected DSK image for drive A:

write direct "A:code.bin"
ORG &1000
LD A,1

Conditional assembly

Conditional assembly is used when two or more versions of a program are needed (e.g. cassette version and disc version). This feature enables any number of different versions to be assembled from the same source code.


This is done by defining blocks of source code that are to be assembled only if some condition holds. The formats of IF blocks are:

IF <expression> 
<code to be assembled if expression is true>  
IF   <expression> 
<code to be assembled if expression is true>  
<code to be assembled if expression is false>  

The expression may be any arithmetic expression. In this context the value of the expression is considered to be a signed 16 bi t number, with 'true' represented by any positive number (i.e. between 1 and 32767) and 'false' by zero or any negative number.
The recommended use is to define a variable which holds the value 1 for true and 0 for false.


Suppose a program come s in two versions, for cassette and disc, and there are a few differences between the two. Define a variable at the start of the source code:

LET cassette=l   ; to assemble the cassette version  
LET cassette=0   ; to assemble the disc version

Then enclose each section where the code differs in an IF block, as follows:

IF cassette 
<code for cassette version>   
<code for disc version>  

AND, OR and XOR may be used with care in IF directives. These are bitwise logical operators, and will work as expected if true is only represented by 1 and false only by 0. So if variables which only ever hold the values 0 or 1 are used the usual results hold (1 OR 0 is true, 1 AND 0 is false, 1 XOR 1 is false, etc.)


IF 2 AND 1

Warning: although 1 and 2 both represent true, the expression 1 AND 2 evaluates to 0 (i.e. false).


As its name suggests, its a combination of if and else. Like else, it extends an if statement to execute a different statement in case the original if expression evaluates to FALSE. However, unlike else, it will execute that alternative expression only if the elseif conditional expression evaluates to TRUE.

There may be several elseifs within the same if statement. The first elseif expression (if any) that evaluates to TRUE would be executed.

if <expression1>
	; some code to compile if <expression1> is true
elseif <expression2>
	; some code to compile if <expression2> is true


It simply reverses the logic of the IF directive:

IFNOT <expression> 
<code to be assembled if expression is false>   
<code to be assembled if expression is true>   

IF1, IF2

These special forms of the IF directive return the value 'true' on p ass 1 and 2 of the assembly, respectively. They may be of some use for printing different messages on each pass, but Z80 instructions and directives should not be placed within an IF1 or IF2 block.

Nesting IF blocks

IF blocks may be nested up to a depth of 10 (maybe more). It is, however, unusual to need nesting deeper than 2 levels. Example:

IF rom_verclon  
   <ROM code>  
   IF disc_version  
      <disc code>  
      <cassette code>  


IFDEF <symbol>

Check if a symbol is defined.


IFNDEF <symbol>

Check if a symbol is not defined.


Macro local labels can be defined by prefixing with an @ symbol, they can be nested and may be called recursively. Macros can override reserved assembler symbols. The ! symbol is used to exclude the use of macros from a symbol. (eg. If the LDI symbol had been redefined, you can assemble a standard LDI using !LDI).


macro <name> [parameter1[,parameter2[…]]]

Define a new macro.

macro <name> [parameter1[,parameter2[]]]
	; some code

Your macro MUST BE DEFINED BEFORE being used in the source!


repeat <expression>

Repeat a code section.

repeat 6
	; put some code to repeat here


Beware of macros!

while <expression>

Repeat a code section until <expression> is true.

; Initialize a variable
LET counter=1
; Repeat the code section until counter>15
while counter AND &F
	; put some code to repeat here
	; and increment the variable.
	LET counter=counter+1


The assembler keeps a table of symbols, each with an assigned 16 bit value. A Symbol is similar to a BASIC variable. The assembler makes two passes; on the first pass it sets up the symbol table and on the second pass it creates the object code using the symbol table to calculate jump addresses etc. On the first pass, when a symbol that has not yet been defined is referred to it is put into the symbol table.

The value is filled in when the symbol is defined. These forward references must all be resolved on the first pass; error messages will indicate any symbols that remained undefined. No symbol may be assigned different values on the two passes - if this occurs the assembler may generate many errors.

There are some assembler directives which do not allow any forward references because the expression value must be known on pass 1. These include ORG - the code origin must be well-defined for it would otherwise be impossible for the assembler to generate the correct symbol table.


Arithmetic expressions may be used throughout the assembler - wherever a number is required. This includes operands of Z80 instructions and assembler directives. The expression evaluator works from left to right and all ows the following:


  1. decimal constants. e.g. 132.
  2. hexadecimal constants. e.g. &BB5A or #2A. Either & or # may be used for compatibilty with BASIC and the firmware documentation.
  3. binary constants. e.g. %1011101
  4. character constants. e.g . 'A',”3”,’”’. Either single or double quotes may be used - to specify a quote character enclose it in the other type of quote. The value of a character constant is the ASCII code of the character, so “3” is the same as #33. A null character constant,”” has the value 0.
  5. an identifier.
  6. one of the two special symbols:
    • $ represents the current code location (program counter).
    • @ represents the current storage location.


  1. Arithmetic operators +,-, *, /, MOD.
  2. Bitwise logical operators AND, OR, XOR.

All expressions are evaluated to 16 bit unsigned integers. Overflow is ignored, and the least significant 16 bits of the result is used.



Hardware registers

documentations/software/winape/start.txt · Last modified: 2011/01/25 02:12 by grim