//----------------------------------------------------------------- h3 #test pre #test ,, p Define code segment for automated tests after successful assembly. p 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. p 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 if all tests pass the assembled code is finally written. h4 Technical Specification of the Test Code Runner h5 CPU emulation p zasm can emulate the 8080, Z80 and Z180 cpu. p The instruction set is limited to the selected CPU, eventually modified by command line options --ixcbr2 or --ixcbxh. If an unimplemented / illegal instruction is executed, the emulation stops with an error and the test fails. h6 8080 emulation p The 8080 cpu has no unhandled/undocumented opcodes, but some opcodes have aliases which were used by the Z80 for additional functions, e.g. relative jumps, 2nd register 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 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 Z80. h6 Z80 emulation The Z80 cpu has lots of undocumented / illegal opcodes. Depending on their type they are handled differently: ul li Widely used undocumented opcodes are always executed without generating an error. These are SLL and access to the upper and lower half of index registers in LD and arithmetical/logical operations. li Illegal index register opcodes after after prefix IX/IY + prefix 0xCB are allowed, if the corresponding command line option --ixcbr2 or --ixcbxh was set. Then they are performed according to this setting. [TODO: the timing of ixcbih opcodes is unclear] li All other undocumented / illegal opcodes are rejected and will terminate the test with an error. h6 Z180 emulation p All Z180 opcodes are emulated and the different timing is implemented. The Z180 traps illegal instructions and so does the emulation. [TODO: the exact behavior of the SLP opcode is unclear] p The test code is executed by a variant of Kio's Z80 engine. The emulation speed is approx. 800MHz@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 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 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. p The emulation of the Z80 is very precise but i cannot guarantee correctness to the last bit and cycle. If you find any difference please report a bug. Replication of the undefined behavior of flags is 80% exact. h5 Memory, interrupts and i/o p The assembled code is loaded into 64kB ram, each segment at the address defined in the #code directive, ignoring possible overlap. Finally the test segment is loaded, again happily overwriting already loaded segments. ul li Memory mapping is not supported. li Wait states are not supported. li Memory mapped i/o is not supported. p The emulation can be run at maximum speed or limited to a fixed frequency. p A timer interrupt can be enabled and used in multiple ways: ul li A timer interrupt after a fixed number of cpu cycles (cc) li A timer interrupt with a given frequency when the cpu is running at a fixed speed li A timer interrupt with a given frequency when the cpu is running at maximum speed li Other interrupt sources are not supported. li The non-maskable interrupt NMI is not supported. p A timeout for a test can be specified in realtime milliseconds. p In and Out access is monitored and must be prepared for. Access to an unprepared address immediately terminates the test. p I/o addresses can be prepared in multiple ways: ul li Assign an address to the console (stdin+stdout) li provide input data for an In address li provide test data for an Out address p Not yet supported in zasm v4.4.4 ul li Loading of a rom file li read/write/compare to file h4 Setting up a test case pre #test , , ; .test-clock .test-int [, ] .test-int [, ] .test-intack .test-timeout ; .test-in , .test-out , .test-console h5 .test-clock p Define a fixed speed for the emulation. The emulation will be throttled to the given frequency. If .test-clock is not specified or if the clock is set to 0 then the test runs at the fastest possible speed. pre .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! h5 .test-int p 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. pre .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 p The units cc or Hz are optional. If present, a sanity test is included. 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. p 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, if enabled in the interrupt handler too early. p It is recommended to use at least 32 cc for the duration, because even if your code never disables interrupts (which will easily take longer than that) the longest Z80 opcodes takes 23 cpu cycles! And, of course, best use the actual duration of your real system. :-) p If no interrupt duration is specified, then the interrupt is asserted until it is acknowledged (handled). pre .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 p 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 if the test code enables interrupts very early, then the first interrupt is immediately taken. h5 .test-intack p 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. pre .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. h5 .test-timeout p Define a timeout for the test. If the test takes longer it is aborted and fails. The timeout is measured in realtime. If no timeout is set or the timeout is set to 0 then there is no timeout. pre .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! h5 .test-console p 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. If no input is available, then the port reads as 0x00. pre .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) h5 .test-in p Supply input data for an input port. The supplied data is read by inputs from this port. p The test fails, if the test code reads more data than supplied and it fails if there is some unread data left after the test. h6 Basic example pre .test-in 0xF8, 0,0,0,"Hello world!",13,0,0,0 p 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. pre .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 p This prepares port 0xF8 with more data. The input data is combined from multiple definitions. h6 Repeated data p You can repeat blocks of data: pre .test-in 0xF8, {0}*10, "Hello World!", 13, {0}*10 pre .test-in 0xF8, {0}*10, {"Hello", 32, "World!", 13} * 5, {0}*10 p You can add a final block to repeat forever. This won't make the test fail: pre .test-in 0xF8, {0}*10, "Hello World!", 13, {0}* h5 .test-out p Supply output test data for an output port. The supplied data is compared against data written to this port. p The test fails, if a byte written doesn't match the expectation, if the test code writes more data than supplied and it fails if there is some unwritten data left after the test. h6 Basic example pre .test-out 0xF8, "Hello world!",10 p This prepares port 0xF8 to expect a text and a linebreak. All data must be written by the test code. pre .test-out 0xF8, "Hello world!",10 .test-out 0xF8, "Here i go again!",10 p This prepares port 0xF8 with more data. The output test data is combined from multiple definitions. h6 Repeated data p You can repeat blocks of data: pre .test-out 0xF8, {'-'}*12, 10, "Hello World!", 10, {'-'}*12, 10 pre .test-out 0xF8, {'-'}*12, 10, {"Hello World!", 10} * 5, {'-'}*12, 10 p You can add a final block to repeat forever. This won't make the test fail: pre .test-out 0xF8, {"Hello World!",10,}* ; any number of repetitions of this block pre .test-out 0xF8, "Hello World!", 10, * ; any data whatever after text h4 Run test code and test your expectations p After the test segment is setup, you can add your test code. It starts up with all registers 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 #local context so you can reuse label names: pre #local ld sp,0 ld a,3 ld de,7 call A_times_DE ; .expect a' = 0 .expect a = 3 .expect de = 7 .expect hl = 3*7 .expect dehl = 7*$10000 + 3*7 .expect cc < 1000 #endlocal h5 .expect register expect p .expect can be used to test register values. p You can compare the value of any register, register pair or quad register to match an expected value. Ordered comparisons ('>' or '<') are not allowed. p Recognized register names are: pre 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 h5 .expect cc expect p .expect can also test the cpu cycle counter. p You can compare the value of the cpu cycle counter to match an expected value or use an ordered comparisons '>' … '<'. p The cycle counter is reset after each block of .expects, either cc or registers. pre .expect cc = 456 .expect cc >= 400 .expect cc > 400 .expect cc <= 500 .expect cc < 500 h4 Tips p.i Hint: assemble your source with command line option -uwy to see the generated code and cycle count. p.i Hint: run tests with command line option -v to see more interesting output. B-) h4 Example files p.c source file p.c listing