Heath8080A — Product Design : 8080A Emulation |
Page last updated |
26-October-2002 |
|
On this page
dispatch Related links |
8080A Emulation 8080A emulation is a mostly-generic implementation of an 8080A microprocessor. The only H8-specific functionality implemented in this module is the write protection of the lower 8K of the memory address space. The common areas of this module are very much on the critical execution path when it comes to raw emulation performance. Every effort has been made to increase speed, even to the extent of fudging a little on the memory write protection. The implementation has been extensively tested and is believed to be correct in all areas but one: the calculation of the Auxiliary Carry flag may not be the same as you would get from a real 8080A chip in the case of the logical operators (AND, OR, XOR), subtract and compare. It is correct for the increment and add operators, which are the only ones that really count, and the DAA instruction gives correct results following increment and add. Instruction Dispatch If the processor is running or there is an interrupt pending, processInstruction is called by H8 Processing to execute a single instruction. If there is an interrupt pending, a restart instruction is passed as a parameter. In this case, if interrupts are enabled, the PC register pair is pushed and then set to the value in that passed vector before the one instruction is executed. There is a separate function for each 8080A instruction. A 256-entry table of pointers to functions is used as the dispatch table. It's defined as follows: void (*instruction[256]) (void) = { NOP, LXI_B, STAX_B, ... }; Instruction dispatch is a call through that table: (*instruction[systemMemory[PC.pair++]]) ( ); (This is not an exact copy from the code; example simplified. See below for a description of memory and the registers.) Each instruction is entered with (PC) pointing to its operand, if any, or the next instruction. On return, RUN and Interrupt Enabled status are returned to H8 Processing. IN/OUT Handling All I/O is handled by the I/O Package, which is responsible for decoding the port address and handling the I/O request. The interface to I/O is simple: void OUT ( void ) { ioOUT ( systemMemory[PC.pair++], regA ); } void IN ( void ) { regA = ioIN ( systemMemory[PC.pair++] ); } OUT passes the port address and the accumulator contents; IN passes the port address and puts the returned data into the accumulator. Memory Write Protection systemMemory is defined as a pointer to a 64K block of unsigned character. The block is allocated when 8080A emulation is initialized. The memory is not cleared. The lower 8K of this block is initialized by H8 Processing at system startup and whenever the processor is reset. (A utility routine in 8080A emulation, aptly named DMA, returns the memory array pointer so that H8 Processing can write directly to memory.) In the H8 under HDOS, the only operating system supported by this emulator, the lower 8K of the address space consists of ROM, 2K of write-protected RAM on the H-17 controller card, and empty space. 8080A emulation must prevent writing to the low 8K in general, and to the write-protected RAM when write is not enabled. Write access is controlled by the high-order bit in the disk control port (177q) (ref. H-17 Disk System). This is a time-critical process as we have to check every write by every instruction. To minimize address compares, I decided to use the write-protect bit to enable write to the entire low 8K of the address space: void writeMem ( unsigned short ndx, unsigned char data ) { if ( ndx & 0xe000 ) // if non-zero, this write in RAM { systemMemory[ndx] = data; return; } if ( h17WriteRam ( ) ) // otherwise, check protection flag systemMemory[ndx] = data; } The implication of this compromise is that a misbehaving program could overwrite the ROMs while this space is unprotected. For that reason, the low 8K of memory (except for the 2K of write-protected RAM) is reinitialized whenever the H8 is reset, making this a non-fatal problem. Instructions that write memory call writeMem as in this example: void STA ( void ) { address.byte.L = systemMemory[PC.pair++]; address.byte.H = systemMemory[PC.pair++]; writeMem( address.value, regA ); } Register Design We need the ability to access registers individually and in pairs, and we need to do so without overhead. For that reason, I decided to use unions to construct the registers. To use unions safely, you have to know the exact size and order of storage of multibyte values. I verified that the compiler generates 16-bit shorts. Testing confirmed that the Motorola 68K and PPC processors store multibyte values high-order byte first (they are so-called "big-endian" processors; Intel processors are "little-endian"). Here's a typical register pair definition. union { unsigned short pair; struct { unsigned char H, L; } reg; } HL; Usage: HL.reg.H -- reference to register H HL.reg.L -- reference to register L HL.pair -- reference to the (HL) pair All the other registers, except the (AF) pair, were constructed in the same way. A multi-purpose accumulator was built along the same principles to support 8-bit and 16-bit arithmetic while allowing very quick access to the overflow bit to check for carry. All arithmetic is done in this special accumulator; regA is an unsigned char storage location. Flag Handling For rapid access to flag values, all the flags are kept in individual int fields. This allows flag testing without masking, as in: void RZ ( void ) { if ( vFlagZ ) { PC.reg.L = systemMemory[SP.pair++]; PC.reg.H = systemMemory[SP.pair++]; } } (This is not an exact copy from the code; example simplified.) The values stored in the individual flag bytes were chosen to ease calculation of a PSW byte for PUSH/POP PSW: #define flagS 0x80 #define flagZ 0x40 #define flagAc 0x10 #define flagP 0x04 #define flagCy 0x01 ... /********************************************** * * Flag Register = SZ0A0P1C * **********************************************/ void POP_PS ( void ) { PSW = systemMemory[SP.pair++]; regA = systemMemory[SP.pair++]; vFlagS = PSW & flagS; vFlagZ = PSW & flagZ; vFlagAc = PSW & flagAc; vFlagP = PSW & flagP; vFlagCy = PSW & flagCy; } void PUSH_PS ( void ) { PSW = vFlagS | vFlagZ | vFlagAc | vFlagP | vFlagCy | 0x02; writeMem( --SP.pair, regA ); writeMem( --SP.pair, PSW ); } |