-
Notifications
You must be signed in to change notification settings - Fork 2
Home
Welcome to the M68K wiki!
How to make a M68K Phoenix-core testcase.
1. What is a testcase ?
In simple words, a testcase is a program that is specifically written to verify that another program works as expected. In our context, the purpose is to check any software or hardware re-implementation of the Motorola MC680x0 CPU (can be FPGA cores or Emulators). It is intended to catch and reveal deeply hidden bugs in the re-implemented CPU instructions set. Indeed, a re-implemented instruction might behave differently than the real 680x0 instruction. A well written testcase can help to reveal those dysfunctions.
Basically, a testcase is a simple M68K ASM source file, compiled with M68K GNU/AS. The difference between a 'normal' ASM code versus a 'testcase' ASM code mainly lies in the fact that the code must verify itself that what we are waiting for is really what it is computed.
2. Why make a testcase ?
The Motorola 680x0 CPU is, by design, known to be hard to implement. It is a CISC (Complex Instruction Set Computer) which means it has many instructions (more than 100) and complex addressing modes (about 20). As a consequence, in some FPGA cores or emulators some programs (system-apps, games, demos, ...) might not works accurately. It is always possible to disassemble faulty programs in order to find the faulty part. But most of the time it is real pain, if not quite impossible, even for an experimented ASM coder. A testcase can help to focus on a specific instruction or a range of instructions. This way is much more efficient to find bug(s), and more important, to understand what is not correctly working. More, a bunch of testcases can be automatically executed in a row ; unlike debugging programs by hand. Making a large bunch of tescases helps to provides a solid regression tool. Also, a testcase should work for all M68K re-implementation projects. So, making and publishing a testcase surely help the community.
3. How to make a testcase
A testcase is by definition a self-verifying program. That said, how to do this ? Basically, we write a function (a sub-routine) that outputs some results. Then we compare the results with some precalculated values stored in a data section. If a value differs from a precalculated one, the program stops with an error (STOP #-1), else it exit normally (RTS).
3.1 GNU/AS specifics
Unlike some compilers, GNU/AS doesn't support Local Labels (prefixed with a '.')
So just use Global Labels. Here it is not a big deal because testcases are short programs.
3.2 Some rules
Only use M68K instructions.
This means no OS-Calls nor Chipset-Calls.
3.3 Write the body
Focus on one instruction, ADDQ.B for example.
Could be any among the M68K instructions set.
Write a small program around the instruction you decided to test ; we will see some samples.
A testcase often use a Loop to compute several values.
Keep in mind to test enough values, that covers the domain that is tested.
Specially strategics and extrems values (minimal, maximal, overflow cases, ...)
But not too much! because a testcase executed in a debugger might takes long times.
One good idea is to make a program that do not exceed 30 secondes.
Optimisations are absolutely not necessary as we focus on the behaviour
of the program, not on the speed.
3.4 Verify the body
Check all computed data. If possible it is better to test one computed value at each iteration,
than to test all of them at end of program ; because it is much easier to locate an error
when one is detected.
One trivial method to test data is to substract a computed value with a precalculated one.
In fact, Calculated value - Precalculated value should always be equal to 0.
If result is not equal to 0 then, here we are, there was an error that needs to be raised.
Specifically to the FPGA Phoenix-core, it is better to use the dedicated 'ASSERT ZERO Register'.
Address of this register is $00D0000C. If a non-zero value is moved to this address, then
program stops immediately with an error.
You can define it as a constant, at start of program, like this :
ASSERT_ZERO EQU $00D0000C ; ASSERT ZERO Register
And use it like this, for example :
MOVE.L Dn,ASSERT_ZERO ; Data register content must be 0
MOVE.L (An)+,ASSERT_ZERO ; Address register content must be 0
3.5 Exit program
Program generally end with a RTS instruction ; this is used when no error is detected.
To raise an error and stop the program (sort of exception) we can use STOP #<error> instruction.
Specifically to the FPGA Phoenix-core, the 'ASSERT ZERO Register' can also stop the program.
;====================================================================== ; Minimal Testcase ; ADDQ #immediate,Dn ;======================================================================
START: LEA PRECALC,A0 ; Init Precalculated values MOVEQ #4,D0 ; Init Counter (5 values - 1) MOVEQ #0,D1 ; Init Result (starts at 0) REPEAT: ADDQ.B #2,D1 ; Result = Result + 2 SUB.B D1,(A0)+ ; (A0) = (A0) - D1 ==> Should be 0 BNE ERROR ; If (A0) <> 0 Then branch to ERROR DBF D0,REPEAT ; If D0 < 0 Then decrement D0 and branch to REPEAT EXIT: RTS ; Exit program normally ERROR: STOP #-1 ; Stop program with error PRECALC: DC.B 2,4,6,8,10 ; Precalculated values
;====================================================================== ; Example 1 ; Minimal Testcase ; ADDQ #immediate,Dn ;======================================================================
ASSERT_ZERO EQU 0D0000C
START: MOVEQ #3,D0 ; D0 = 3 REPEAT: ADDQ.B #2,D0 ; D0 = D0 + 2 SUBQ.B #5,D0 ; D0 = D0 - 5 ==> Should be 0 MOVE.L D0,ASSERT_ZERO ; Raise error if D0 <> 0 EXIT: RTS ; Stop program
;====================================================================== ; Example 2 ; Minimal Testcase ; ADDQ #immediate,Dn ;======================================================================
NUM_VALUES EQU 5 ASSERT_ZERO EQU 0D0000C
START: LEA PRECALC,A0 ; Init Precalculated values MOVEQ #NUM_VALUES-1,D0 ; Init Counter (5 values - 1) MOVEQ #0,D1 ; Init Result (starts at 0) REPEAT: ADDQ.B #2,D1 ; Result = Result + 2 SUBQ.B D1,(A0) ; (A0) = (A0) - D1 ==> Should be 0 MOVE.L (A0)+,ASSERT_ZERO ; Raise error if (A0) <> 0 DBF D0,REPEAT ; If D0 > 0 Then D0 - 1 and branch to REPEAT EXIT: RTS ; Stop program
PRECALC: DC.B 2,4,6,8,10 ; Precalculated values
;======================================================================