Im Magazin „Microcomputing" wurde im September 1977 der folgende Artikel abgedruckt.
Ein Programm, um den Ablauf einer 8080 CPU zu simulieren1.
Lee Stork
605 Harriet Ave.
Shoreview MN 55112

Try an
8080 Simulator
... valuable debugging technique

A simulator is undoubtedly a valuable tool in software troubleshooting, and Lee's article describes one for debugging 8080 code. Now ... who's going to write one for the 6800 or 6502? Better yet, who's going to get busy and write a simulator for running (not debugging) 6800 code on an 8080 (and vice versa)? I'm really surprised nobody has done it before. If you stop and think about it, the idea makes a lot of sense. Essentially you would have two machines! The software running under the simulator would, of course, run slower, but in most cases this wouldn't be such a big deal ("most cases" being game programs, right?). – John.
So that new program doesn't work. I have found that debugging it usually takes longer than writing it. If only I could see what is going on inside the computer, correcting a program would be a lot easier. The simulator presented here does away with having to guess what is happening. It allows me to single step through a program, and after each instruction, shows me all the internal registers in the microprocessor. In addition, the program being executed can be run until a breakpoint address is reached.
A =1234    BC=5678
DE=9ABC    HL=DEFO
PC=0123    SP=4567
  01    1234
  23    5678
  45    9ABC
  67    DEF0
  89    1234
  AB    5678
 *CD    9ABC
  EF    DEF0
  01    1234
  23    5678
  45    9ABC
  67    DEF0
Fig 1. Sample output of the simulator.
 
                  BIT 
            7 6 5 4 3 2 1 0
LXI B       0 0 0 0 0 0 0 1
LXI D       0 0 0 1 0 0 0 1
LXI H       0 0 1 0 0 0 0 1
LXI SP      0 0 1 1 0 0 0 1
Table 1.

The Display

Written for use on an 8080, the display produced by the simulator is shown in Fig. 1. The first three lines show the contents of all the registers. The first two hexadecimal digits after A = is the accumulator, and the next two is the flag register. The first column of numbers is sequential memory bytes starting at six less than the current address of the program's PC register and continuing to PC + 5. The asterisk marks the byte pointed to by PC and is the first byte of the next instruction to be executed. The second column is stack locations, the first number being the last value pushed onto the stack.

Flowchart

A flowchart of the simulator is shown in Fig. 3. First the length of the instruction pointed to by the PC of the program must be determined. If the instruction is sequential (not a jump, call, or return), it is loaded into the simulator. The registers, with the exception of the PC, are loaded into the microprocessor, and the instruction executed. Non-sequential instructions are not directly executed but are carried out by various software routines. After completion of one instruction the registers are stored. If in breakpoint mode, and the program count does not equal the breakpoint address, the simulator jumps back to the beginning and executes the next instruction. Otherwise, the register contents are displayed and the simulator waits for a keyboard input before continuing.
Fig. 3. Flowchart of simulator program.

The Program

A complete listing of the simulator is given in Fig. 2. It should be noted during this discussion that the simulator retains control by never loading the PC register with the actual PC value of the program under execution. The program begins by calling the subroutine LOC, which inputs a four digit hexadecimal starting address from the keyboard. The ASCII digits are converted to binary, and upon return the number is stored in HL. Next a zero is stored in the breakpoint indicator, putting the simulator into single-step mode. The data stored at the stack locations are shown in Fig. 4. Zeroes are also stored at INST and INST + 1, the reason for which will be explained later. Steps 11 through 55 determine the length of the instruction to be executed. As an example consider steps 19 through 20, which determine if the instruction is an LXI. All four LXI instructions are shown in binary in Table 1.
01E3        Stack    0,1          Breakpoint mode
01E5        Stack    2,3          Breakpoint address
01E7        Stack    4,5          Return address storage
01E9        Stack    6,7
01EB        Stack    8,9          ASCII address (during DSPY)
01ED        Stack   10,11         Register address (during DSPY)
01EF        Stack   12,13         SP
01F1        Stack    14,15        PC
01F3        Stack   16,17         L,H
01F5        Stack   18,19         E,D
01F7        Stack   20,21         C,B
01F9        Stack   22,23         Flags,A
01FB        Stack   24,2
Fig. 4. Listing of the data stored at the stack locations.
Note that with the exception of bits five and four all the instructions are the same and all combinations of bits five and four are used. The ANI CF at 1D forces bits four and five to zero, causing all the LXI instructions to look like a LXI B. The CPI 01 at 1F checks for an LXI B and a jump is taken if the compare is equal. All the rest of the instructions in this group utilize this same method except the JC at 2F. Prior to this step all the two and three-byte instructions less than C0 have been detected so at this point the instruction is one byte in length if it is less than C0.
Let's follow a two-byte instruction. After detection a jump is made to TWOB. TWOB retrieves the two bytes from the program, stores them in the simulator at INST + 1, and INST + 2 and updates HL which contains the program's PC value. The program's new value of PC is stored and the rest of the register values are loaded into the microprocessor. The simulator then falls through to the instruction retrieved from memory and executes it. It also executes a NOP at INST. Recall that zeroes were stored at INST and INST+ 1 earlier. A three-byte instruction would use all three locations, and a one-byte would have two NOPs and the one-byte instruction.
Now that the instruction has been executed the internal registers must be stored. But a quandry develops; the only way to obtain the flags is to do a PUSH PSW which would dictate that the value of the stack pointer be stored and then changed first. However, the only way to get this register value is to load HL with zero and do a DAD SP, which clears the carry flag causing its original state to be lost. To get around this problem the state of the carry flag is checked by a JC at 125. Either the flag is left cleared after the DAD SP if the JC is not executed, or it is reset after the double add.
Let's now go back and look at those non-sequential instructions. All these instructions must be simulated since the PC cannot be loaded into the microprocessor. The unconditional instructions are singled out first RST, CALL, JMP, and RET. If the instruction is conditional, the simulator must determine whether to execute or skip it. Bits three, four, and five of these instructions are the same for each condition, zero for JNZ, CNZ, RNZ, 001 for JZ, CZ, and RZ etc. All but these three bits are zeroed out by the ANI 38 (hex) at 68. Two RRCs are performed, and the results of the two shifts are added, leaving values of 0, 6, 12, ... 48 (decimal). This value is added to a base address and the flags are loaded into the microprocessor. A PCHL transfers control to the appropriate pair of jumps. A "no-zero" would go to 78 where, if the condition is met, the JNZ is taken and the instruction executed. Otherwise the following unconditional jump transfers control to the skip location. Similar pairs of jumps check all the other possible conditions.
SKIP updates the PC, one for return and three for jumps and calls, and then proceeds to the breakpoint test. At EXEC returns are first sorted out and handled separately; for a jump or call the next value for PC is retrieved from the program and stored in the location containing the program's PC value. A jump instruction proceeds to the storage routine while a call simulates stack action to store the return address. The returns sorted out earlier also simulate the stack to obtain the next PC value. A PCHL loads the value of HL and stores it in the location for PC. An RST puts the call address in DE and then goes to the call routine to be handled like a call instruction.
At 138 a check is made to determine if the simulator is in breakpoint mode. If it is, the previously stored breakpoint address is compared with the current PC value, and if not equal, the next instruction is executed. If equal, or not in breakpoint mode, the DSPY routine is entered. Here three ASCII characters are output to indicate register type; then the CONV routine is called twice to output the register value in hex. To output a CR after every second register pair the loop count in C is checked. Each time it is an odd number the output character count is increased by one, causing a CR to be issued after the spaces.
After all the registers have been displayed the PC and stack values are output. The series of POPs at 182 thru 184 load SP into DE and PC into HL. These values are returned to the stack for storage. At 187 the value of SP is pushed a second time and will be used during the display routine. The PC value is decreased by six and C is loaded with the loop count.
At 18F the output process begins. C is checked and when equal to six an asterisk is output to indicate the instruction to be executed next. One line of output consists of two spaces or an asterisk and a space, the PC value, three spaces, the stack value, and a CR.
Finally the simulator waits for an input before continuing. If the input character is a B, the simulator calls LOC to get the breakpoint address, and in either case jumps to NEXT to begin processing the next instruction.

Using the Simulator

To use the program start at location and input a four digit hex starting address. The simulator then executes the first instruction and displays the result. The display contains 15 lines, and since my TV typewriter has 16 lines (most systems will need a line feed) a CR returns the cursor to the top of the next page. The simulator interprets the keyboard input as a signal to go ahead and execute the next instruction and output the results. To go into breakpoint mode type a B followed by a four digit hex address. The simulator will continue to execute instructions without display until it comes to the breakpoint address and then revert to single-step mode to display the registers and wait for an input.
Nearly every program must be tailored to the individual system on which it runs. Most users will probably need a CR followed by a line feed to return the cursor or printhead to the beginning of the next line. If this is the case with your system, increment the size of the constants in the MVI B instructions at 172, 1B8 and 1D8. All the input and output routines are at the end of the program so you can easily incorporate other routines such as those used in a monitor, or change the I/O to octal format.
When using the simulator even in breakpoint mode remember the program is being executed at a much slower speed than if it were running alone. Certain real-time applications such as routines with high speed peripherals may not operate properly. Several times I've had a program work using the simulator but fail when run by itself. Every time the problem has been traced to a defective memory chip. When run at full speed as during a stack operation, the memory picked a bit; but when the time between certain memory accesses was changed under the simulator, it worked.
I have found the simulator to be an invaluable troubleshooting tool and believe you will too.

1. Da das Programm nicht aus der TPA verschoben wird, kann das zu untersuchende Programm auch nicht in die TPA geladen werden. Hier ist also ein CP/M Debugger (DDT, SID oder ZSID) besser geeignet.

Eingescanned von Werner Cirsovius
Februar 2014
© Microcomputing (kilobaud)