~swisschili/toolchain-6502

c6b4f7e34ea1bad432d59c7cff283dfb08e398d1 — swissChili 3 months ago b04a402
Multithread debugger and emulator with message queues
14 files changed, 256 insertions(+), 106 deletions(-)

M CMakeLists.txt
A LICENSE
M README.md
M common.c
M common.h
M cpu.c
M cpu.h
M dbg.c
M dbg.h
M gui.c
M gui.h
M main.c
M screen.c
M screen.h
M CMakeLists.txt => CMakeLists.txt +1 -8
@@ 3,7 3,6 @@ cmake_minimum_required(VERSION 3.0)
project(6502 VERSION 0.1.0 LANGUAGES C)

option(GEN_INSTRUCTIONS_HEADER ON)
option(NO_PTHREAD OFF)

include_directories(nuklear)



@@ 15,12 14,6 @@ if (${GEN_INSTRUCTIONS_HEADER})
		WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
endif()

if (${NO_PTHREAD})
	add_definitions(-DNO_PTHREAD)
else()
	set(THREAD pthread)
endif()

add_executable(6502 main.c cpu.c cpu.h dbg.c dbg.h 
	instructions.h gui.h gui.c screen.h screen.c common.h common.c)
target_link_libraries(6502 readline SDL2 GL GLU GLEW m ${THREAD})
target_link_libraries(6502 readline SDL2 GL GLU GLEW m rt pthread)

A LICENSE => LICENSE +23 -0
@@ 0,0 1,23 @@
Copyright 2020 swissChili

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and 
the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
and the following disclaimer in the documentation and/or other materials provided with the 
distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse 
or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file

M README.md => README.md +39 -0
@@ 11,6 11,45 @@ The `instructions.h` header is generated from `6502.csv` and contains
definitions of every 6502 opcode, its mnemonic and addressing mode.
It is built automatically by cmake. 

## Dependencies

- POSIX Threads
- POSIX Messsage Queues
- SDL2
- OpenGL 3
- GLEW
- GNU Readline
- Nuklear (included)

## License

```
Copyright 2020 swissChili

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and 
the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
and the following disclaimer in the documentation and/or other materials provided with the 
distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse 
or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

---

```
 ____________________________________

M common.c => common.c +1 -1
@@ 9,7 9,7 @@ unsigned g_catch_len;

void throw_(const char *msg, const char *file, unsigned int line)
{
	printf("\033[31mException thrown:\033[33m %s:%d\033[0m %s\n", file, line, msg);
	fprintf(stderr, "\033[31mException thrown:\033[33m %s:%d\033[0m %s\n", file, line, msg);

	for (int i = g_catch_len - 1; i >= 0; i--)
	{

M common.h => common.h +1 -1
@@ 10,7 10,7 @@

#define ASSERT(message, body) \
	{ \
		if (!body) \
		if (!(body)) \
			THROW("Assert failed: " message " [" #body "]"); \
	}


M cpu.c => cpu.c +33 -13
@@ 4,6 4,8 @@
#define SCREEN_ONLY_SDL
#include "screen.h"

#include "dbg.h"
#include <errno.h>
#include <endian.h>
#include <stdio.h>
#include <stdlib.h>


@@ 539,8 541,6 @@ arg_t fetch_addr(cpu_t *cpu, uint8_t am, uint f)

void step(cpu_t *cpu)
{
	static int steps;
	steps++;
	cpu->screen_dirty = false;
	uint8_t pc = cpu->pc;
	uint8_t op = cpu->mem[cpu->pc++];


@@ 559,17 559,6 @@ void step(cpu_t *cpu)
			warn("Undefined opcode %x near %x [%x]", op, pc, cpu->mem[pc]);
			THROW("Undefined opcode");
	}

	if (steps % 100 == 0)
		printf("%d\n", steps);

// If can't run screen in seperate thread, just run it here (bad)
#ifdef NO_PTHREAD
	if (g_scr)
	{
		sdl_screen(g_scr, cpu->mem + CPU_FB_ADDR, cpu->screen_dirty);
	}
#endif
}

int dump_inst(cpu_t *cpu, char *buf, const char *mn, uint16_t addr, uint8_t am)


@@ 677,3 666,34 @@ void run(cpu_t *cpu)

	printf("CPU Halted\n");
}

void run_mq(cpu_t *cpu, mqd_t mq)
{
	char buf[MQ_BUF_LEN];
	bool running;

	while (true)
	{
		if (running)
		{
			if (cpu->running)
				step(cpu);
			else
				running = false;
		}

		ssize_t recvd = mq_receive(mq, buf, MQ_BUF_LEN * 2, NULL);

		if (recvd == -1 && errno != EAGAIN)
		{
			printf("errno = %d\n", errno);
			THROW("mq_receive returned -1");
		}

		if (recvd > 0)
		{
			if (debug_stmt(cpu, buf, &running))
				break;
		}
	}
}

M cpu.h => cpu.h +5 -0
@@ 2,12 2,16 @@

#include <stdint.h>
#include <stdbool.h>
#include <mqueue.h>

#define REGISTERS R(A) R(X) R(Y) R(SP)
#define CPU_FB_ADDR 0x200
#define CPU_FB_W 32
#define CPU_FB_H 32

#define MQ_BUF_LEN 512
#define MQ_NAME "/6502-to-cpu"

enum // Registers
{
	A, X, Y, SP


@@ 148,3 152,4 @@ void disas_num(cpu_t *cpu, uint16_t num);
// Buffer must be freed by user
char *disas_step(cpu_t *cpu);
void run(cpu_t *cpu);
void run_mq(cpu_t *cpu, mqd_t mq);

M dbg.c => dbg.c +81 -42
@@ 4,64 4,103 @@
#include <readline/readline.h>
#include <readline/history.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

void debug(cpu_t *cpu)
bool debug_stmt(cpu_t *cpu, char *input, bool *running)
{
	while (true)
	{
		char *input = readline("\033[33m> \033[0m");
		if (!input || !*input)
			continue;

		char *tok = strtok(input, " \t\r\n\v");
	char *tok = strtok(input, " \t\r\n\v");

		if (!tok || !*tok)
			continue;
		
		if (!strcmp(tok, "step") || !strcmp(tok, "s"))
		{
			step(cpu);
		}
		else if (!strcmp(tok, "show") || !strcmp(tok, "print"))
	if (!tok || !*tok)
		return false;
	
	if (!strcmp(tok, "step") || !strcmp(tok, "s"))
	{
		step(cpu);
	}
	else if (!strcmp(tok, "show") || !strcmp(tok, "print"))
	{
		if ((tok = strtok(NULL, " ")))
		{
			if ((tok = strtok(NULL, " ")))
			char *ok = 0;
			if (tok[0] == '$')
			{
				char *ok = 0;
				if (tok[0] == '$')
				{
					uint16_t addr = strtol(tok + 1, &ok, 16);
				uint16_t addr = strtol(tok + 1, &ok, 16);

					printf("Memory:\n");
					printf("\t$%x	%x\n", addr, cpu->mem[addr]);
					if (addr < 0xFFFF)
						printf("\t$%x	%x\n", addr + 1, cpu->mem[addr + 1]);
				}
				else
				{
					printf("Expected an address as an argument in the form "
						"$1234, not %s\n", tok);
				}
				printf("Memory:\n");
				printf("\t$%x	%x\n", addr, cpu->mem[addr]);
				if (addr < 0xFFFF)
					printf("\t$%x	%x\n", addr + 1, cpu->mem[addr + 1]);
			}
			else
			{
				printf("Registers:\n");

				#define R(r) printf("\t" #r ":\t%x\n", cpu->regs[r]);
					REGISTERS
				#undef R
				printf("Expected an address as an argument in the form "
					"$1234, not %s\n", tok);
			}
		}
		else if (!strcmp(tok, "quit") || !strcmp(tok, "exit"))
		{
			printf("Bye\n");
			return;
		}
		else
		{
			printf("Unknown command %s\n", tok);
			printf("Registers:\n");

			printf("\tPC:\t$%x\n", cpu->pc);
			#define R(r) printf("\t" #r ":\t$%x\n", cpu->regs[r]);
				REGISTERS
			#undef R
		}
	}
	else if (!strcmp(tok, "run"))
	{
		*running = true;
	}
	else if (!strcmp(tok, "quit") || !strcmp(tok, "exit"))
	{
		printf("Bye\n");
		return true;
	}
	else
	{
		printf("Unknown command %s\n", tok);
	}
	
	return false;
}

typedef struct
{
	mqd_t mq;
	cpu_t *cpu;
} debug_prompt_arg_t;

void debug_prompt(debug_prompt_arg_t *arg)
{
	mqd_t mq = arg->mq;
	cpu_t *cpu = arg->cpu;
	free(arg);

	bool running = true;
	while (running)
	{
		char *input = readline("\033[33m> \033[0m");
		if (!input || !*input)
			continue;

		if (!strcmp(input, "quit") || !strcmp(input, "exit"))
			running = false;

		mq_send(mq, input, strlen(input) + 1, 2);

		add_history(input);
		free(input);
	}
}

pthread_t start_debug_prompt(mqd_t mq, cpu_t *cpu)
{
	debug_prompt_arg_t *arg = malloc(sizeof(debug_prompt_arg_t));
	arg->mq = mq;
	arg->cpu = cpu;

	pthread_t thread;
	pthread_create(&thread, NULL, (void *(*)(void *))&debug_prompt, arg);
	return thread;
}

M dbg.h => dbg.h +6 -1
@@ 2,4 2,9 @@

#include "cpu.h"

void debug(cpu_t *cpu);
#include <stdbool.h>
#include <mqueue.h>

bool debug_stmt(cpu_t *cpu, char *input, bool *running);
// void debug_prompt(mqd_t mq, cpu_t *cpu);
pthread_t start_debug_prompt(mqd_t mq, cpu_t *cpu);
\ No newline at end of file

M gui.c => gui.c +22 -1
@@ 23,8 23,19 @@
#define MAX_VERTEX_MEMORY 512 * 1024
#define MAX_ELEMENT_MEMORY 128 * 1024

void gui(cpu_t *cpu)
typedef struct
{
	cpu_t *cpu;
	mqd_t mq;
} gui_arg_t;

void gui(gui_arg_t *arg)
{
	cpu_t *cpu = arg->cpu;
	mqd_t mq = arg->mq;

	free(arg);

	SDL_Window *win;
	SDL_GLContext glContext;
	int win_width, win_height;


@@ 223,3 234,13 @@ cleanup:
	SDL_DestroyWindow(win);
	SDL_Quit();
}


void start_gui(mqd_t mq, cpu_t *cpu)
{
	pthread_t gui_thread;
	gui_arg_t *arg = malloc(sizeof(gui_arg_t));
	arg->cpu = cpu;
	arg->mq = mq;
	pthread_create(&gui_thread, NULL, (void *(*)(void *))&gui, arg);
}

M gui.h => gui.h +2 -1
@@ 2,4 2,5 @@

#include "cpu.h"

void gui(cpu_t *cpu);
// void gui(cpu_t *cpu);
void start_gui(mqd_t mq_to_cpu, cpu_t *cpu);

M main.c => main.c +35 -20
@@ 6,23 6,26 @@

#include <bits/getopt_core.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern sdl_screen_t *g_scr;

#ifndef NO_PTHREAD
#include <pthread.h>
#include <mqueue.h>
#include <sys/stat.h>


void cleanup_screen_thread(pthread_t thread)
{
	g_screen_thread_halt = true;
	puts("Cleaning up screen...");
	pthread_join(thread, NULL);
	pthread_cancel(thread);
}

void cleanup_debug_prompt_thread(pthread_t thread)
{
	puts("Cleaning up debug prompt...");
	pthread_cancel(thread);
}
#endif

int main(int argc, char **argv)
{


@@ 97,11 100,28 @@ int main(int argc, char **argv)
	}

	cpu_t cpu;
	mqd_t mq_to_cpu;

	struct mq_attr attrs;
	attrs.mq_maxmsg = 10;
	attrs.mq_msgsize = MQ_BUF_LEN;

	if (should_read)
	{
		cpu = new_cpu();
		fread(cpu.mem + 0x600, 0xFFFF - 0x600, 1, input);
		
		int unlink = mq_unlink(MQ_NAME);
		if (unlink < 0 && errno != ENOENT)
		{
			printf("Warning: mq_unlink() error: %d %s\n", errno, strerror(errno));
		}
		
		mq_to_cpu = mq_open(MQ_NAME, O_RDWR | O_CREAT | O_NONBLOCK, S_IWUSR|S_IRUSR, &attrs);
		printf("error after mq_open (%ld) = %d %s\n", attrs.mq_msgsize, errno, strerror(errno));
		ASSERT("Open message queue for emulator", mq_to_cpu > 0)

		mq_send(mq_to_cpu, "init", 5, 2);
	}
	else
	{


@@ 111,12 131,7 @@ int main(int argc, char **argv)

	if (scrflag)
	{
#ifndef NO_PTHREAD
		CATCH(&cleanup_screen_thread, start_screen_thread(cpu.mem + CPU_FB_ADDR));
#else
		sdl_screen_t scr = new_sdl_screen(8);
		g_scr = &scr;
#endif
	}

	if (guiflag && scrflag)


@@ 126,11 141,12 @@ int main(int argc, char **argv)

	if (guiflag)
	{
		gui(&cpu);
		start_gui(mq_to_cpu, &cpu);
		run_mq(&cpu, mq_to_cpu);
	}
	else if (disflag)
	{
		disas_num(&cpu, 12);
		disas_num(&cpu, 64);
	}
	else if (runflag)
	{


@@ 143,14 159,13 @@ int main(int argc, char **argv)
	}
	else if (debugflag)
	{
		debug(&cpu);
		CATCH(&cleanup_debug_prompt_thread, start_debug_prompt(mq_to_cpu, &cpu));
		run_mq(&cpu, mq_to_cpu);
	}
	
#ifdef NO_PTHREAD
	if (scrflag)
		free_sdl_screen(g_scr);
#endif

	if (should_read)
	{
		free_cpu(&cpu);
		mq_close(mq_to_cpu);
	}
}

M screen.c => screen.c +6 -12
@@ 3,10 3,8 @@
#include "common.h"

#include <SDL2/SDL.h>

#ifndef NO_PTHREAD
#include <pthread.h>
#endif


struct nk_color byte_to_color(uint8_t b)
{


@@ 101,25 99,23 @@ bool sdl_screen(sdl_screen_t *scr, uint8_t *mem, bool dirty)
	return false;
}


#ifndef NO_PTHREAD

bool g_screen_thread_halt = false;


void *screen_thread(uint8_t *mem)
{
	sdl_screen_t scr = new_sdl_screen(8);
	
	pthread_cleanup_push(((void (*)(void *))&free_sdl_screen), ((void *)&scr));

	while (true)
	{
		if (sdl_screen(&scr, mem, true) || g_screen_thread_halt)
			break;
	}
	free_sdl_screen(&scr);

	exit(0);
	pthread_cleanup_pop(true);

	return NULL;
	pthread_exit(NULL);
}

pthread_t start_screen_thread(uint8_t *mem)


@@ 128,5 124,3 @@ pthread_t start_screen_thread(uint8_t *mem)
	pthread_create(&thread, NULL, (void *(*)(void *))&screen_thread, mem);
	return thread;
}

#endif

M screen.h => screen.h +1 -6
@@ 2,6 2,7 @@

#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>

#ifndef SCREEN_ONLY_SDL



@@ 35,13 36,7 @@ sdl_screen_t new_sdl_screen(uint8_t size);
void free_sdl_screen(sdl_screen_t *scr);
bool sdl_screen(sdl_screen_t *scr, uint8_t *mem, bool dirty);

#ifndef NO_PTHREAD

#include <pthread.h>

extern bool g_screen_thread_halt;

void *screen_thread(uint8_t *mem);
pthread_t start_screen_thread(uint8_t *mem);

#endif