<--  back   Last regenerated: 2022-04-20 17:31:36 kio

zasm - Z80 Assembler – Version 4.4

Assembler directives

#test

#test <name>,<start>,<size>

Define code segment for automated tests after successful assembly.

The test code will be loaded on top of the assembled code and executed. During execution i/o addresses are monitored and compared with supplied values. Finally – or at any time during the test – registers and the cpu cycle count can be compared against expected values.

There can be any number of tests in a test segment and there can be any number of test segments which are loaded and executed separately. Test errors are handled like assembly errors. Only Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if all tests pass the assembled code is finally written.

Technical Specification of the Test Code Runner

CPU emulation

zasm can emulate the 8080, Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 and Command Line Options: --z180
Pseudo instructions: .z80, .z180 and .8080
Z180 cpu.

The instruction Pseudo instructions: defl, set and '='
Labels: SET
set is limited to the selected CPU, eventually modified by Differences from v3 to v4: Command line options
Command Line Options
Command Line Options: Command line options
command line options --Command Line Options: --ixcbxh, .ixcbxh, _ixcbxh_
--ixcbr2, .ixcbr2, _ixcbr2_

Commands for command line options: --ixcbxh, .ixcbxh, _ixcbxh_
--ixcbr2, .ixcbr2, _ixcbr2_
ixcbr2 or --Command Line Options: --ixcbxh, .ixcbxh, _ixcbxh_
--ixcbr2, .ixcbr2, _ixcbr2_

Commands for command line options: --ixcbxh, .ixcbxh, _ixcbxh_
--ixcbr2, .ixcbr2, _ixcbr2_
ixcbxh. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If an unimplemented / illegal instruction is executed, the emulation stops with an error and the test fails.

8080 emulation

The 8080 cpu has no unhandled/undocumented opcodes, but some opcodes have aliases which were used by the Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 for additional functions, e.g. relative jumps, 2nd register Pseudo instructions: defl, set and '='
Labels: SET
set and prefix opcodes.
Only the official opcodes, which are also those which are generated by zasm, are allowed. Execution of an alias opcode, which was reused by the Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80, is handled like an illegal opcode and will terminate the test. This is to prevent you from accidentially writing code which will not run on a Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80.

Z80 emulation
Z180 emulation

All Command Line Options: --z180
Pseudo instructions: .z80, .z180 and .8080
Z180 opcodes are emulated and the different timing is implemented. The Command Line Options: --z180
Pseudo instructions: .z80, .z180 and .8080
Z180 traps illegal instructions and so does the emulation. [TODO: the exact behavior of the SLP opcode is unclear]

The test code is executed by a variant of Kio's Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 engine.
The emulation speed is approx. 800MHz@Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 per GHz@Host (2.6GHz on 3.2GHz AMD Ryzen5 2400G) or let's say, depending on the emulated system and the speed of your host, the emulated Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 is running 1000 times faster than the original. :-)
I found out that the emulation speed is very sensitive to the loading position of the Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 engine and can vary widely - for my PC from 1MHz emulated speed to 2.5MHz! I'll try to check the speed before releases of zasm but will probably frequenty fail to do so. After all it can be completely different on someone else's PC.

The emulation of the Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 is very precise but i cannot guarantee correctness to the last bit and cycle. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If you find any difference please report a bug. Replication of the undefined behavior of flags is 80% exact.

Memory, interrupts and i/o

The assembled code is loaded into 64kB ram, each segment at the address defined in the Assembler directives: #code
Including C Source Files: #code
#code directive, ignoring possible overlap. Finally the test segment is loaded, again happily overwriting already loaded segments.

  • Memory mapping is not supported.
  • Wait states are not supported.
  • Memory mapped i/o is not supported.

The emulation can be run at maximum speed or limited to a fixed frequency.

A timer interrupt can be enabled and used in multiple ways:

  • A timer interrupt after a fixed number of cpu cycles (cc)
  • A timer interrupt with a given frequency when the cpu is running at a fixed speed
  • A timer interrupt with a given frequency when the cpu is running at maximum speed
  • Other interrupt sources are not supported.
  • The non-maskable interrupt NMI is not supported.

A timeout for a test can be specified in realtime milliseconds.

In and Out access is monitored and must be prepared for. Access to an unprepared address immediately terminates the test.

I/o addresses can be prepared in multiple ways:

  • Assign an address to the console (stdin+stdout)
  • provide input data for an In address
  • provide test data for an Out address

Not yet supported in zasm v4.4.4

  • Loading of a rom file
  • read/write/compare to file

Setting up a test case

#test <name>, <addr>, <size>
;
.test-clock     <frequency>
.test-int       <frequency>  [, <duration>]
.test-int       <cpu_cycles> [, <duration>]
.test-intack    <int_ack_byte>
.test-timeout   <duration>
;
.test-in        <addr>, <values>
.test-out       <addr>, <expected_values>
.test-console   <addr>
.test-clock

Define a fixed speed for the emulation. The emulation will be throttled to the given frequency. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If .test-clock is not specified or Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if the clock is Pseudo instructions: defl, set and '='
Labels: SET
set to 0 then the test runs at the fastest possible speed.

.test-clock 4000000 Hz ; run at 4 MHz.   mind the space!
.test-clock 3500 kHz   ; run at 3.5 MHz. mind the space!
.test-clock 6 MHz      ; run at 6 MHz.   mind the space!
.test-int

Enable a timer interrupt and define the interrupt frequency.
The frequency can be given in cpu cycles 'cc' or in real-world frequency. Depending on whether the cpu is running at a fixed speed or unlimited speed the executed cpu cycles per interrupt are predictable or may vary widely.

.test-int  50 Hz       ; interrupt with real-world frequency
.test-int  70000 cc    ; interrupt after 70000 cc (50 Hz at 3.5 MHz: the ZX Spectrum :-)
.test-int  50          ; interrupt with 50 Hz
.test-int  70000       ; interrupt after 70000 cc

The units cc or Hz are optional. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If present, a sanity test is included.
Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If the unit is omitted, zasm uses values up to 1000 as Hz and larger values as cc.
1000 is the highest value which can be used for frequency.

Since version 4.0.3 a second argument can be added to specify the duration, for which the interrupt should be asserted. This can be used to test whether interrupts are missed due to longisch sequences with interrupts disabled or whether they interrupt itself, Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if enabled in the interrupt handler too early.

It is recommended to use at least 32 cc for the duration, because even Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if your code never disables interrupts (which will easily take longer than that) the longest Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 opcodes takes 23 cpu cycles! And, of course, best use the actual duration of your real system. :-)

Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If no interrupt duration is specified, then the interrupt is asserted until it is acknowledged (handled).

.test-int  50 Hz, 40 cc    ; interrupt with real-world frequency, asserted for 40 cc
.test-int  70000 cc, 48 cc ; interrupt after 70000 cc, asserted for 48 cc
.test-int  50, 128         ; interrupt with 50 Hz, asserted for 128 cc
.test-int  70000, 64 cc    ; interrupt after 70000 cc, asserted for 64 cc

There is one more subtable difference between auto-off mode and int-with-duration mode:
In auto-off mode the first interrupt, which would occur at cpu cycle cc = 0 is suppressed.
In interrupt-with-duration mode the first interrupt at cc = 0 is not suppressed, and Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if the test code enables interrupts very early, then the first interrupt is immediately taken.

.test-intack

Define byte to be read from the bus in an interrupt acknowldge cycle.
This byte is 0xFF by default (floating bus on a TTL system) but different values can be defined as provided by some peripheral hardware, to supply a RST opcode or an index in the interrupt vector table, depending on the current interrupt mode of the cpu.

.test-intack  255-8            ; this would be a RST 48
.test-intack  opcode(rst 48)   ; the same, just readable :-)
.test-intack  lo(int_vector)   ; low byte of memory address of an interrupt vector
                               ; the high byte would go into the i register.
.test-timeout

Define a timeout for the test. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If the test takes longer it is aborted and fails.
The timeout is measured in realtime.
Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If no timeout is Pseudo instructions: defl, set and '='
Labels: SET
set or the timeout is Pseudo instructions: defl, set and '='
Labels: SET
set to 0 then there is no timeout.

.test-timeout  1 s     ; timeout after 1 second.        mind the space!
.test-timeout  500 ms  ; timeout after 500 millisecond. mind the space!
.test-timeout  1 m     ; timeout after 1 minute.        mind the space!
.test-console

Attach an i/o address to stdin and stdout.
This is an easy way to print messages from the running code.
A little more effort must be taken to input text, because the input is not blocking.
Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If no input is available, then the port reads as 0x00.

.test-console  0xFE    ; use port address 0xFE,   high byte is ignored
.test-console  0xFFFE  ; use port address 0xFFFE, you must use in(c) and out(c)
.test-in

Supply input data for an input port. The supplied data is read by inputs from this port.

The test fails, Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if the test code reads more data than supplied and it fails Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if there is some unread data left after the test.

Basic example
.test-in  0xF8, 0,0,0,"Hello world!",13,0,0,0

This prepares port 0xF8 to return 3 times 0x00 (probably meaning 'no data'), then a text and finally again 3 times 0x00. All data must be read by the test code.

.test-in  0xF8, 0,0,0,"Hello world!",13,0,0,0
.test-in  0xF8, 0,0,0,"Here i go again!",13,0,0,0

This prepares port 0xF8 with more data. The input data is combined from multiple definitions.

Repeated data

You can repeat blocks of data:

.test-in  0xF8, {0}*10, "Hello World!", 13, {0}*10
.test-in  0xF8, {0}*10, {"Hello", 32, "World!", 13} * 5, {0}*10

You can add a final block to repeat forever. This won't make the test fail:

.test-in  0xF8, {0}*10, "Hello World!", 13, {0}*
.test-out

Supply output test data for an output port. The supplied data is compared against data written to this port.

The test fails, Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if a byte written doesn't match the expectation, Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if the test code writes more data than supplied and it fails Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if there is some unwritten data left after the test.

Basic example
.test-out  0xF8, "Hello world!",10

This prepares port 0xF8 to Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect a text and a linebreak. All data must be written by the test code.

.test-out  0xF8, "Hello world!",10
.test-out  0xF8, "Here i go again!",10

This prepares port 0xF8 with more data. The output test data is combined from multiple definitions.

Repeated data

You can repeat blocks of data:

.test-out  0xF8, {'-'}*12, 10, "Hello World!", 10, {'-'}*12, 10
.test-out  0xF8, {'-'}*12, 10, {"Hello World!", 10} * 5, {'-'}*12, 10

You can add a final block to repeat forever. This won't make the test fail:

.test-out  0xF8, {"Hello World!",10,}*  ; any number of repetitions of this block
.test-out  0xF8, "Hello World!", 10, *  ; any data whatever after text

Run test code and test your expectations

After the test segment is setup, you can add your test code. It starts up with all registers Pseudo instructions: defl, set and '='
Labels: SET
set to 0x00 and interrupts disabled. Typically you load some registers and call a function to test. It is recommended to wrap all tests in a Assembler directives: #local, #endlocal, .local and .endlocal
Pseudo instructions: #local, #endlocal, .local and .endlocal
Including C Source Files: #local, #endlocal, .local and .endlocal
#local context so you can reuse Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
label names:

Assembler directives: #local, #endlocal, .local and .endlocal
Pseudo instructions: #local, #endlocal, .local and .endlocal
Including C Source Files: #local, #endlocal, .local and .endlocal
#local ld sp,0 ld a,3 ld de,7 call A_times_DE ; .Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect a' = 0 .Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect a = 3 .Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect de = 7 .Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect hl = 3*7 .Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect dehl = 7*$10000 + 3*7 .expect cc < 1000 Assembler directives: #local, #endlocal, .local and .endlocal
Pseudo instructions: #local, #endlocal, .local and .endlocal
Including C Source Files: #local, #endlocal, .local and .endlocal
#endlocal
.expect register

.Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect
can be used to test register values.

You can compare the value of any register, register pair or quad register to match an expected value. Ordered comparisons ('>' or '<') are not allowed.

Recognized register names are:

 a f b c d e h l a2 f2 b2 c2 d2 e2 h2 l2 a' f' b' c' d' e' h' l'
xh xl yh yl ixh ixl iyh iyl pch pcl sph spl i r
af bc de hl af2 bc2 de2 hl2 af' bc' de' hl' ix iy pc sp
bcde bchl bcix bciy debc dehl deix deiy etc.
im iff1 iff2
.expect cc

.Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect
can also test the cpu cycle counter.

You can compare the value of the cpu cycle counter to match an expected value or use an ordered comparisons '>' … '<'.

The cycle counter is reset after each block of .Run test code and test your expectations: .expect register
Run test code and test your expectations: .expect cc
expect
s, either cc or registers.

.expect cc = 456
.expect cc >= 400
.expect cc > 400
.expect cc <= 500
.expect cc < 500

Tips

Hint: assemble your source with Differences from v3 to v4: Command line options
Command Line Options
Command Line Options: Command line options
command line option -uwy to see the generated code and cycle count.

Hint: run tests with Differences from v3 to v4: Command line options
Command Line Options
Command Line Options: Command line options
command line option -v to see more interesting output. B-)

Example files

source file

listing

Valid HTML   Valid CSS