6

I've found a few 8080 CPU test suites available, but they all assume CP/M or a similar OS is already present and running - they call BDOS for I/O, etc. I'm working towards being able to run CP/M on my 8080 emulator, but for the moment, I'm looking for something that's just going to put the emulated CPU through its paces and output the results.

6
  • 1
    This queststion is way to be broad to be answered in a consitent fashion. You may want to specify what exactly you're looking for. Never the less, go ahead like with any other software project. So setup a test case for each function in detail and combined cases - here this may mean for each instruction, which seams like an acceptable effort. Also keep in mind, that test suites for real hardware may look for different issues here.
    – Raffzahn
    Mar 15, 2019 at 20:50
  • 1
    How will you output without calling an output routine? You could probably write a small "can output only" CP/M subset rather quickly. Mar 16, 2019 at 0:49
  • 2
    @Raffzahn I don't think it is way to broad. As somebody who has written a Z80 emulator, I know exactly what he wants.
    – JeremyP
    Mar 16, 2019 at 10:33
  • 2
    Well, @JeremyP, I've writen more than just one emulator (4004, /360, 2650, 8085, Z80, 6502, 9900, 8086 pick your favourite:)), and to me this question is way to broad, as it fails to tell anything about the test setup the suite is needed, not what goals are to be reached, or at least what tests are required. Just testing isn't a value in itself.
    – Raffzahn
    Mar 16, 2019 at 19:46
  • 3
    @Raffzahn Well just because you can't grok the intent of the question doesn't mean it can't be grokked. It was obvious to me (and Tommy) that he wanted something like ZEXALL that would run on a bare machine. I mean, you've written enough emulators, isn't that what you always want.
    – JeremyP
    Mar 18, 2019 at 9:42

3 Answers 3

23

I'm actually not aware of any major 8080 test suites; everything I've ever found has been for the Z80 rather than its parent.

That aside, I'd heavily suggest you don't discard the CP/M solutions you've found as they're usually pretty trivial to set up as test cases without any of the main substance of a CP/M environment.

For example, to run the CP/M version of Zexall all you need to do is this:

  • give your z80 or 8080 64kb of RAM;
  • load the raw bytes included in zexall.com to address 0x0100;
  • put a RET (opcode 0xc9) at address 0x0005;
  • whenever the CPU hits 0x0005, check what's in the C register, and:
    • if it's 9 then output a string starting from the address in DE until you find a $ character;
    • if it's 2 then output the single character in E;
  • set the PC to 0x0100 and let it run until the stack fully unwinds.

Then check your accumulated text output against that you accumulated during a passing run for a pass or fail; use your human judgment until you get to your first passing run.

So what you've effectively done is implement exactly two of the calls exposed via CP/M's CALL 5 interface, both of which just to append text to a buffer. And that's everything that zexall requires.

Otherwise: FUSE, the Spectrum emulator, provides tests for everything the Z80 does, in an ad hoc text format. I've transcribed those to JSON so that you don't have to write a parser. The full set of files including the original licence is here on Github.

Parse tests.in.json, and you'll get an array of initial CPU states plus bytes to put into memory. Parse tests.expected.json and you'll see both expected bus activity and the expected final state. Within the output there's a value, "tStates" that tells you how many cycles to run for to verify that the initial state turns into the final state.

The counts are in Z80 cycles so there may be some differences there, but most of the tests are very simple — they're just the one or two opcodes long. So you should be able to ignore the tests for opcodes the 8080 doesn't have, and the values for registers that the 8080 doesn't have, and just run until either the PC exits the data you loaded or you hit the expected final state.

4
  • 1
    This is pretty much exactly what I did to make ZEXALL run on my Z80 emulator.
    – JeremyP
    Mar 16, 2019 at 10:38
  • I did pretty much this solution: I replaced the call to 0005 (BDOS entry) with a simple OUT, so I could see the character output for the error code. I was able to fix one bug that way, and already passed all the tests in that test suite. Mar 17, 2019 at 14:16
  • 1
    @CharlesMangin: One could use a hybrid of the two approaches, using an 8080 machine-code routine to either output the value in E, or fetch bytes from (DE) and output them to e.g. I/O address zero until it finds a "$".
    – supercat
    Mar 20, 2019 at 18:08
  • This is almost exactly what I did as well, except that I throw an exception on an "unkown" (other than $02 or $09) BDOS call. Also, I don't see how running to a stack can work: most CP/M programs exit by jumping to $0000. ¶ Anybody interested can find my Python code (at least for the moment) here, either for direct use or to serve as pseudo-code for one's own implementation.
    – cjs
    16 hours ago
6

You can find some Intel 8080 CPU tests on this website. These can be run with no OS, just a minimal support framework described below:

These binaries should be loaded to memory starting at 0x0100, and at startup, your CPU should jump to 0x0100. You should also put two handlers at addresses 0x0000 and 0x0005:

  • The handler at 0x0000 signals the end of the test run.

  • The handler at 0x0005 is used to communicate the values of the registers C, D and E with the outside world. Register C selects from multiple functions:

    • C = 0x02 signals printing the value of register E as an ASCII value
    • C = 0x09 signals printing the value of memory pointed to by DE until a '$' character is encountered

The easiest is to put an OUT instruction at 0x0000 onwards, and an IN followed by a RET at 0x0005 onwards, and then set your emulator to do the right thing on port writes / reads.

I have implemented this protocol in a testbench for an FPGA Intel 8080 implementation inspired by a software emulator test suite; both of these could be useful references for you in implementing the scaffolding necessary to run these CPU tests without any complicated OS, or even any computer, around your CPU.

3
  • 1
    It probably can’t just be IN followed by RET at address 5, as CP/M ABI requires the top of program memory to reside at address 6. MS-DOS took some pains to replicate this ABI: os2museum.com/wp/who-needs-the-address-wraparound-anyway Nov 6, 2020 at 15:58
  • 1
    @user3840170 - if the test programs never use this fact, then this won't be a problem. The idea isn't to support arbitrary CP/M programs, but rather to specifically allow one particular set of programs to run in as simple a fashion as possible. I'm not sure why specifically an IN instruction is most useful here (I'd personally prefer to use HLT at address 0 and OUT at address 5, assuming the tests don't use the former as that would require specific hardware support) but I see no reason it wouldn't work.
    – occipita
    Nov 7, 2020 at 6:50
  • The only reason I initially used an IN instead of a HLT was because that's what the code at github.com/superzazu/8080 used. I have since moved on to HLT and OUT.
    – Cactus
    Nov 7, 2020 at 7:22
1

In case it's useful to anyone to see some more exact details of how to do the limited CP/M emulation suggested in other answers, the following is some Python code to do this from my testmc simulation and test suite. (The code below is currently on a development branch, but will be moved to the main branch soon.)

Below, the m parameter is a Machine object with attributes for the registers and functions to deposit data into memory, run code, etc.

def emu_cpm(m, outstream=None):
    ''' Emulate, within the test framework, a CPM call. Returns:
        - `True`: the call was successfully handled, and the test should
          continue running.
        - `False`: the call was not handled, and the test should examine
          the machine state and take appropriate action.

        Output is always written to `stdout`. The output will be duplicated
        on `outstream` if given; this is useful for collecting the output
        to examine in a test or write to a file.

        This handles calls to the following addresses, which must be set
        in the `stopat` list for `Machine.stepto()`:
        - ``CALL $0000``: Writes a message indicating warm start, but
          returns `False`. (The caller should normally exit the loop and
          then decide whether it should pass or fail the test.)
        - ``CALL $0005``: CP/M BDOS function call. This currently handles
          only printing (which is done to stdout), otherwise an exception
          is raised.
    '''
    def pr(*args, **kwargs):
        print(*args, **kwargs)
        if outstream:
            print(*args, file=outstream, flush=True, **kwargs)

    if m.pc == 0x0000:
        pr('■CP/M WARM START via $0000')
        return False

    if m.pc == 0x0005:      # BDOS call
        if m.c == 0x02:     # write character in E to console
            pr(f"{chr(m.e)}", end='')
        elif m.c == 0x09:   # write $-terminated string at DE to console
            terminator = m.mem.find(b'$', m.de)
            pr(m.bytes(m.de, terminator-m.de).decode('ASCII'), end='')
        else:
            assert 0, 'Unknown BDOS call ${:02X} at {}'.format(m.c, m.regs)
        #   Ensure we return from the successful CP/M BDOS call.
        if m.byte(0x0005) != I.RET:
            m.deposit(0x0005, I.RET)
        return True

    return False

The following is a simple test function that uses this, written for the pytest framework.

def test_8080exer(m):
    load_com(m, 'programs/8080EX1.COM')
    while True:
        m.stepto(stopat=[0x0000, 0x0005], maxsteps=1e11, trace=0)
        if emu_cpm(m): continue
        raise RuntimeError(f'Uknown exit at ${m.pc:04X}')
9
  • Suggestion: "Unknown BDOS call" might better be "Unsupported BDOS call". 3 hours ago
  • It seems strange to write a test suite that checks the correct working of a CPU AND to assume that an underlying OS works correctly. Either I test the CPU, which I expect to have potential errors, or I trust the CPU to run the OS. I cannot have both the same time. (At least this is my approach when writing emulators.) 3 hours ago
  • @busybee I would find that more confusing. C=1, for example is a well known BDOS call: read console, just one that I don't support in this code. ¶ I don't understand your second comment. I'm not doing any OS testing at all here; I'm just emulating enough of the OS the CPU test program expects that I can see its results. And this emulation is done outside the CPU simulator: on a call to $0005 or $0000 the simulator code exits and I then externally query the simulator state to find the character or string and print it.
    – cjs
    3 hours ago
  • I look from the view point of the author of that test suite. Either I suspect the CPU to be erroneous, then I cannot use OS calls. Or I trust the CPU to run correctly to use the OS, then I don't need to test it. -- I know such attempts, I felt into that trap myself, when I was young. Still it is not logical. 3 hours ago
  • It's perfectly possible for a CPU to be erroneous and OS calls still to work. CP/M doesn't use every last capability of the CPU. And in particular, when you're only using a very limited subset of the OS (here, just character output, once you've managed somehow to get the program into memory), you need very little functionality at all. And in this case, even better, my "OS" works even if the simulated CPU is massively failing. But I think your queries are more about those programs than my emulation framework here.
    – cjs
    3 hours ago

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .