When I was at school and worked / played with the Soviet clones Sinclair 48K, I dreamed of a neighbor's 8086.
When the 486DX66 appeared, I dreamed again of the Z80. So carried my love for retrocomputers into the present. And although now I am trying to embody myself in hardware as a “PC designer”, and even possessing some collection of rare and not very CPU, I always wanted to make a virtual version myself. But that knowledge was not enough, something else; most often - time. In the end, decided to try. The dream was to launch a SVM for an EU computer, and Elite would see again on something made by ourselves. But since the house is being built from the foundation, I decided to start from the beginning.
I also programmed at school, on Agatha, at home on Mikroshe, then on Java. But then abandoned. More than a year ago, it took you to automate one process, tried something and rushed. I try to write in C, I work on Linux, and use GTK + (3.0) (although I’m writing win on it, I’m used to it. And yes, I know that this is a perversion). Examples of the implementation of exactly what I wanted on GTK + did not find, so maybe this post will come in handy the same as I am starting with GTK and emulation.
There are no articles on the principles of emulation, and specifically Chip-8 - a car and a small truck, so I will not repost what is so well described, for example,
here, and
here and here .
')
I did not look at the source code of a single emulator, before trying to write my own. In addition to the pleasure of the result, the goal of self-study was pursued. Peeping into the answers always led to a lack of memorization. Therefore, I wanted to "suffer the torment" myself first. I use Glade. Therefore, the entire interface was drawn in it. Since this is a test attempt and no practical use was planned, some things have been simplified. I decided to do something in the emulator of the next system. I apologize in advance for the code style.
So, we draw our window for the emulator. The resolution of the Chip-8 base version is 64 * 32, I took the pixel size as 8 * 8. Therefore, we set the appropriate properties of GtkDrawingArea, where we will draw.


All the inside of the virtual CPU lies in the structure
typedef struct { uint64_t last_cycle; uint64_t vsync; gboolean pressed; uint8_t last_key; gboolean run; uint8_t delay_timer; uint8_t sound_timer; uint8_t cycle; uint8_t keypad[16]; uint8_t V[16]; uint16_t opcode; uint16_t stack[16]; uint16_t sp; uint16_t I; uint16_t pc; uint8_t video[SCREEN_X][SCREEN_Y]; uint8_t video_mirrored[SCREEN_X][SCREEN_Y]; uint8_t memory[RAM_SIZE]; }_CHIP8; extern _CHIP8 SYS;
Perhaps the video memory "looks" is not very natural, but then I wanted to transfer to the microcontroller with the display 128 * 64, and I wanted to get rid of all the extra multiplications / divisions, if possible. And then it remained.
Disassembling ROM is implemented simply and primitively.
SYS.opcode = SYS.memory[SYS.pc] << 8 | SYS.memory[SYS.pc + 1];
After that, "binary magic" goes in a relatively large function with switch / case.
With microcontrollers, I have been driving a little longer, but still binary arithmetic was more a black box than a clear subject. Working with an emulator for an hour or two I instilled in me and burned “in the subcortex” everything that I needed to know.
There are few opcodes, so this solution is quite justified. The machine codes themselves are very conveniently composed, so this function is written very quickly. The main thing is to understand AND and OR, as well as remember that Chip-8 is a big endian machine.
The main loop is spinning in a separate stream, with a frequency of 24 Hz, I planned to update the screen.
The problem is that GTK requires all manipulations to it from the main loop. Therefore, once every 1/24 sec the video memory is mirrored and with the help of g_idle_add we inform the main loop that we want to call refresh_screen. The function will be called as soon as resources are freed. If you do not do this and call drawing functions from another thread, it will almost certainly work. It may even work for a long time, until either it is painted, or funny and not very artifacts / special effects arise.
void *chip8_vcpu_pipeline(void *data) { […...........] g_idle_add((GSourceFunc) refresh_screen, NULL); […............] return (0); }
First you need to make the appropriate callback for GtkDrawingArea. All drawing will occur in this function.
gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data) { cr = gdk_cairo_create( gtk_widget_get_window (widget)); cairo_set_source_rgb(cr, 0, 0, 0); cairo_paint(cr); for ( int x = 0; x < SCREEN_X; x++ ) { for ( int y = 0; y < SCREEN_Y; y++ ) { SYS.video_mirrored[x][y] ? set_dot(cr, x, y) : clear_dot(cr, x, y); } } cairo_destroy(cr); return FALSE; }
Well, the functions of the pixel: put a dot / erase it
void set_dot(cairo_t *cr, int32_t cx, int32_t cy) { cairo_set_source_rgb(cr, 255, 255, 255); cairo_set_line_width(cr, 2); cairo_rectangle(cr, cx * 8, cy * 8, 8, 8); cairo_fill(cr); cairo_stroke(cr); } void clear_dot(cairo_t *cr, int32_t cx, int32_t cy) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 2); cairo_rectangle(cr, cx * 8, cy * 8, 8, 8); cairo_fill(cr); cairo_stroke(cr); }
The function draw_cb is connected to the draw event GtkDrawingArea. One frame now we draw, but how to update the screen? This is done in refresh_screen, where GUI.screen is GtkDrawingArea.
gboolean refresh_screen(void) { gtk_widget_queue_draw_area(GTK_WIDGET(GUI.screen), 0, 0, 512, 256); return FALSE; }
Since we called drawing via g_idle_add, we return FALSE so that the drawing is one-time.
Now the keyboard. We write two functions
gboolean on_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { switch(event->keyval) { case GDK_KEY_1: SYS.keypad[1]=1; SYS.last_key = 1; break; case GDK_KEY_2: SYS.keypad[2]=1; SYS.last_key = 2; break; …........
And the same for on_key_release and connect them to the key-press-event and key-release-event respectively.
I could not find a clear specification - what is the speed of the processor of the Chip-8 virtual machine, as a result I chose the cycle length by eye. In any case, the moving picture on the screen, and even the opportunity to play pinpong very well motivated to move on.
