I have started porting the i8080-core project to glsl. It's originally a C program, but I have previously modified to run on the TRS-80 Color Computer using a compiler named CMOC, which is extremely similar to C. I would consider it a C compiler that is just missing a few corner cases, that many programs could live without... in this case, an entire emulator! The previous glsl program that ran simple function and returned has been expanded out to run a single 8080 instruction and return. There are two SSBOs allocated for this computer shader: one is a small struct that stores the processor state, and the other is a 64K block of memory. Opcode handler in glsl
The current implementation is extremely ineffecient. The main loop in the C side of the emulator sets up and runs the shader, then does a memory barrier synchronization, reading back the 8080 processor state, prints out some logging, and then repeats. Eventually, the shader will run many instructions at a time, and will only return when a large chunk (1000? 10000?) has completed, or there is some kind of I/O or fault. For now, one instruction at a time is completely appropriate for debugging. Each iteration it prints the program counter, stack pointer, flags, and the contents of the registers. This is printed on to stderr, so it is easy to redirect to a file.
I also took my fork of i8080-core (which supports linux and CoCo) and added identical instrumentation to the linux version.. This allows me to diff the output of the glsl version to the i8080 version... if the registers or program counters diverge, then that's probably where the problem is. I started by implementing a single instrucion... the first instruction of 8080PRE, which is a simple 8080 test program. It was used in the original i8080 repo before I copied it as part of its test suite. I have found that as I implement the instructions one-by-one, they are getting easier. The emulator uses many macros and helper functions, and I've tried to make the translation to glsl one-to-one. In some cases, I just change the definition of the macro, and the actual switch case handler between the two versions are the same. Even a relatively complicated instruction, like jnz (jump if not zero) have identical implementations: glsl vs C. The macros for TST and RD_WORD are a bit different, but since that are building blocks used by other instructions, this gets easier as the macros are figured out.
In the listings below, on the left we have the processor state of running the original i8080-core on a linux computer. On the right, is the processor state of the glsl version. Only a few instructions are implemented, but this serves as a proof of concept. The listings start the same, but diverges. The "fault displayed is a status code coming back from the emulator. I've defined 0x1000 to be unimplemented instruction, with the opcode in the least significant byte. In this case, C5 is not implemented. Looking at the original emulator, this appears to be the push b instruction. Looking at the code for it in i8080-core, it is the same as the already implemented push h instruction, but on a different register. A quick copy/paste, rerun the emulator, and it will advance another instruction before there is a difference. SOmetime it will run a few instructions before they diverge. At least there's a maximum of 256 times I would have to do this! I'm sure glad I didn't choose to emulate something like an x86!
| i8080-core | 8080.glsl | |
|---|---|---|
0100 a:00 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:00000 0102 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:00000 0104 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:11001 0107 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:11001 0109 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:01110 010c a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:01110 0111 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:01110 0117 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:7fe flags:01110 0118 a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0119 a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 011b a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0121 a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0122 a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0124 a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 012a a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 012d a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:399 flags:01110 012e a:04 b:00 c:00 d:00 e:00 h:01 l:14 sp:39b flags:00000 012f a:04 b:08 c:06 d:00 e:00 h:01 l:14 sp:39d flags:00000 0130 a:04 b:08 c:06 d:0c e:0a h:01 l:14 sp:39f flags:00000 0131 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a1 flags:00000 0134 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a9 flags:00000 0135 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a7 flags:00000 0136 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a5 flags:00000 0137 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a3 flags:00000 0138 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a1 flags:00000 013b a:02 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a1 flags:00000 |
0100 a:00 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:00000 0102 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:00000 0104 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:11001 0107 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:11001 0109 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:01110 010c a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:01110 0111 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:800 flags:01110 0117 a:01 b:00 c:00 d:00 e:00 h:00 l:00 sp:7fe flags:01110 0118 a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0119 a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 011b a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0121 a:01 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0122 a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 0124 a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 012a a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:800 flags:01110 012d a:14 b:00 c:00 d:00 e:00 h:01 l:14 sp:399 flags:01110 012e a:04 b:00 c:00 d:00 e:00 h:01 l:14 sp:39b flags:00000 012f a:04 b:08 c:06 d:00 e:00 h:01 l:14 sp:39d flags:00000 0130 a:04 b:08 c:06 d:0c e:0a h:01 l:14 sp:39f flags:00000 0131 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a1 flags:00000 0134 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a9 flags:00000 0135 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a7 flags:00000 0136 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a5 flags:00000 0137 fault 10c5 0137 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a5 flags:00000 0138 fault 10f5 0138 a:04 b:08 c:06 d:0c e:0a h:10 l:0e sp:3a5 flags:00000 0139 fault 103a |
There are some differences in how I have organized the memory and state of the emulator. The original emulator has a byte array. GLSL doesn't actually support bytes... its integer data type is the 32-bit signed integer. There is some limited support for 8 and 16 bit values in later versions or as an extension, but I wanted to keep this project simple. I also don't want to deal with packing and unpacking bytes at every memory operation. Fortunately... the address space of the 8080 is so small, I store memory as an array of 65536 integers. The same is true of registers: A,B, etc are all 32-bit ints. Register pairs like HL are split into two integers! After arithmetic operations that can overflow 8-bits, I mask to 0xFF. This effectively gives me an array of unsigned bytes... which is what I want to work with in an emulator.
For now, I am going to continue working on this, and hope to get 8080PRE running. That app does eventually perform some output using CP/M BDOS calls. Very basic character I/O is implemented in the original i8080-core, and I will adapt that here. BDOS is called by jumping to a specific location. I want to trap that location with a fault: there will be an invalid instruction that the emulator in glsl can't handle, and will return with a fault code. The C host will perform I/O, manipulate the state of the emulator, and then jump back into glsl. Now, I don't want to step on any possible 8-bit opcodes, but because of the way the byte memory is implemented, I'm not actually limited to 8-bit 'opcodes'. The real memory array has 32-bit spaces for each location... so it is completely possible to put an impossible value like 0x8000 there. So, opcode 0x8000 will be like the VMCALL instruction in modern virtual machines.