/*
* HOME(PROJECTS) || RESUME || LINKS || ABOUT
*/

|Newest| . . . |<<Newer| . . . . . . |Older >>| . . . |Oldest|
(view all as one document)

Retrochallenge 2026/10

October 22

Running a few instructions

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.

|Newest| . . . |<<Newer| . . . . . . |Older >>| . . . |Oldest|
(view all as one document)