483 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			483 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 | 
						|
    This file is part of libgtemu, a library for Gigatron emulation.
 | 
						|
    Copyright (C) 2019 David Heiko Kolf
 | 
						|
 | 
						|
    Published under the BSD-2-Clause license.
 | 
						|
    https://opensource.org/licenses/BSD-2-Clause
 | 
						|
 | 
						|
*/
 | 
						|
 | 
						|
#include "gtemu.h"
 | 
						|
 | 
						|
extern void gtloader_onfallingvsync(struct GTState *gt, struct GTPeriph *ph);
 | 
						|
extern void gtloader_onrisinghsync (struct GTState *gt, struct GTPeriph *ph);
 | 
						|
 | 
						|
const int hbackporch = 48 / 4;
 | 
						|
const int screenwidth = 640 / 4;
 | 
						|
const int vbackporch = 33;
 | 
						|
const int screenheight = 480;
 | 
						|
 | 
						|
static unsigned long xorshift32 (unsigned long x)
 | 
						|
{
 | 
						|
	x ^= x << 13;
 | 
						|
	x ^= x >> 17;
 | 
						|
	x ^= x << 5;
 | 
						|
	return x;
 | 
						|
}
 | 
						|
 | 
						|
void gtemu_init (struct GTState *gt,
 | 
						|
	struct GTRomEntry *rom, size_t romsize,
 | 
						|
	unsigned char *ram, size_t ramsize)
 | 
						|
{
 | 
						|
	gt->rom = rom;
 | 
						|
	gt->romcount = romsize / sizeof(rom[0]);
 | 
						|
	gt->ram = ram;
 | 
						|
	gt->rammask = ramsize - 1;
 | 
						|
	gt->pc = 0;
 | 
						|
	gt->in = 0xff;
 | 
						|
	gt->out = 0x80;
 | 
						|
}
 | 
						|
 | 
						|
unsigned long gtemu_randomizemem (unsigned long seed,
 | 
						|
	void *mem, size_t size)
 | 
						|
{
 | 
						|
	int *imem = (int *) mem;
 | 
						|
	int i;
 | 
						|
	if (seed == 0) {
 | 
						|
		/* 0 is an invalid value for xorshift */
 | 
						|
		seed = 0x74007400;
 | 
						|
	}
 | 
						|
	for (i = 0; i < size / sizeof(int); i++) {
 | 
						|
		imem[i] = seed = xorshift32(seed);
 | 
						|
	}
 | 
						|
	return seed;
 | 
						|
}
 | 
						|
 | 
						|
void gtemu_initperiph (struct GTPeriph *ph, int audiofreq,
 | 
						|
	unsigned long randseed)
 | 
						|
{
 | 
						|
	ph->board.booted = 0;
 | 
						|
	if (randseed == 0) {
 | 
						|
		/* 0 is an invalid value for xorshift */
 | 
						|
		randseed = 0x74007400;
 | 
						|
	}
 | 
						|
	ph->board.undef = xorshift32(randseed);
 | 
						|
	ph->board.clock = 0;
 | 
						|
	ph->board.xout = 0;
 | 
						|
	ph->video.x = ph->video.y = 1000;
 | 
						|
	ph->audio.sampleticks1k = 6251940000ull / audiofreq;
 | 
						|
	ph->audio.sampletickscount1k = 0;
 | 
						|
	ph->loader.state = 0;
 | 
						|
	ph->serialout.invpulse = 0;
 | 
						|
	ph->serialout.bitcount = 0;
 | 
						|
	ph->serialout.buffer = NULL;
 | 
						|
	ph->serialout.buffersize = 0;
 | 
						|
	ph->serialout.bufferpos = NULL;
 | 
						|
	ph->serialout.sentfull = 0;
 | 
						|
}
 | 
						|
 | 
						|
unsigned long long gtemu_getclock (struct GTPeriph *ph)
 | 
						|
{
 | 
						|
	return ph->board.clock;
 | 
						|
}
 | 
						|
 | 
						|
unsigned char gtemu_getxout (struct GTPeriph *ph)
 | 
						|
{
 | 
						|
	return ph->board.xout;
 | 
						|
}
 | 
						|
 | 
						|
static void cputick (struct GTState *gt, unsigned char undef)
 | 
						|
{
 | 
						|
	unsigned char ir, d, databus, ac;
 | 
						|
	int ins, mod, bus, pc;
 | 
						|
	int iswrite, isjump;
 | 
						|
	unsigned char lo, hi = 0;
 | 
						|
	unsigned char *to = NULL;
 | 
						|
	size_t addr;
 | 
						|
 | 
						|
	ir = gt->ir;
 | 
						|
	d = gt->d;
 | 
						|
 | 
						|
	pc = gt->pc;
 | 
						|
 | 
						|
	if (pc >= gt->romcount) {
 | 
						|
		pc = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	gt->ir = gt->rom[pc].i;
 | 
						|
	gt->d = gt->rom[pc].d;
 | 
						|
 | 
						|
#if 1 /* optional optimization */
 | 
						|
	/* shortcuts for the three most frequent instructions */
 | 
						|
	switch (ir) {
 | 
						|
	case 0x5d: /* ora [y,x++],out -- the video output instruction */
 | 
						|
		addr = (gt->x | (gt->y << 8)) & gt->rammask;
 | 
						|
		gt->out = gt->ram[addr] | gt->ac;
 | 
						|
		gt->x++;
 | 
						|
		gt->pc = pc + 1;
 | 
						|
		return;
 | 
						|
	case 0xc2: /* st [D] */
 | 
						|
		/* this assumes that the RAM has at least 256 bytes */
 | 
						|
		gt->ram[d] = gt->ac;
 | 
						|
		gt->pc = pc + 1;
 | 
						|
		return;
 | 
						|
	case 0x01: /* ld [D] */
 | 
						|
		/* this assumes that the RAM has at least 256 bytes */
 | 
						|
		gt->ac = gt->ram[d];
 | 
						|
		gt->pc = pc + 1;
 | 
						|
		return;
 | 
						|
	default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
	ins = ir >> 5;
 | 
						|
	mod = (ir >> 2) & 7;
 | 
						|
	bus = ir & 3;
 | 
						|
 | 
						|
	iswrite = ins == 6;
 | 
						|
	isjump = ins == 7;
 | 
						|
 | 
						|
	lo = d;
 | 
						|
 | 
						|
	if (!isjump) {
 | 
						|
		switch (mod) {
 | 
						|
		case 0:
 | 
						|
		case 1:
 | 
						|
		case 2:
 | 
						|
		case 3:
 | 
						|
			if (!iswrite) {
 | 
						|
				to = >->ac;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		case 4:
 | 
						|
			to = >->x;
 | 
						|
			break;
 | 
						|
		case 5:
 | 
						|
			to = >->y;
 | 
						|
			break;
 | 
						|
		case 6:
 | 
						|
		case 7:
 | 
						|
			if (!iswrite) {
 | 
						|
				to = >->out;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		switch (mod) {
 | 
						|
		case 1:
 | 
						|
			lo = gt->x;
 | 
						|
			break;
 | 
						|
		case 2:
 | 
						|
			hi = gt->y;
 | 
						|
			break;
 | 
						|
		case 3:
 | 
						|
			lo = gt->x;
 | 
						|
			hi = gt->y;
 | 
						|
			break;
 | 
						|
		case 7:
 | 
						|
			lo = gt->x;
 | 
						|
			hi = gt->y;
 | 
						|
			gt->x++;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	addr = ((hi << 8) | lo) & gt->rammask;
 | 
						|
 | 
						|
	switch (bus) {
 | 
						|
	case 0:
 | 
						|
		databus = d;
 | 
						|
		break;
 | 
						|
	case 1:
 | 
						|
		if (iswrite) {
 | 
						|
			databus = undef;
 | 
						|
		} else {
 | 
						|
			databus = gt->ram[addr];
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	case 2:
 | 
						|
		databus = gt->ac;
 | 
						|
		break;
 | 
						|
	case 3:
 | 
						|
		databus = gt->in;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (iswrite) {
 | 
						|
		gt->ram[addr] = databus;
 | 
						|
	}
 | 
						|
 | 
						|
	ac = gt->ac;
 | 
						|
 | 
						|
	if (to != NULL) {
 | 
						|
		switch (ins) {
 | 
						|
		case 0:
 | 
						|
			*to = databus;
 | 
						|
			break;
 | 
						|
		case 1:
 | 
						|
			*to = ac & databus;
 | 
						|
			break;
 | 
						|
		case 2:
 | 
						|
			*to = ac | databus;
 | 
						|
			break;
 | 
						|
		case 3:
 | 
						|
			*to = ac ^ databus;
 | 
						|
			break;
 | 
						|
		case 4:
 | 
						|
			*to = ac + databus;
 | 
						|
			break;
 | 
						|
		case 5:
 | 
						|
			*to = ac - databus;
 | 
						|
			break;
 | 
						|
		case 6:
 | 
						|
			*to = ac;
 | 
						|
			break;
 | 
						|
		case 7:
 | 
						|
			*to = -ac;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (isjump) {
 | 
						|
		if (mod != 0) {
 | 
						|
			int cond = ac == 0 ? 2 : ac >> 7;
 | 
						|
			if (mod & (1 << cond)) {
 | 
						|
				gt->pc = (pc & 0xff00) | databus;
 | 
						|
			} else {
 | 
						|
				gt->pc = pc + 1;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			gt->pc = (gt->y << 8) | databus;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		gt->pc = pc + 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void gtserialout_onfallingvsync(struct GTPeriph *ph)
 | 
						|
{
 | 
						|
	ph->serialout.invpulse = 1;
 | 
						|
	ph->serialout.count = 0;
 | 
						|
}
 | 
						|
 | 
						|
static void gtserialout_onrisinghsync (struct GTPeriph *ph)
 | 
						|
{
 | 
						|
	if (ph->serialout.invpulse) {
 | 
						|
		ph->serialout.count++;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void writechar (struct GTPeriph *ph, char c)
 | 
						|
{
 | 
						|
	if (ph->serialout.buffer != NULL &&
 | 
						|
		ph->serialout.bufferpos != NULL) {
 | 
						|
 | 
						|
		size_t pos = *ph->serialout.bufferpos;
 | 
						|
		if (pos < ph->serialout.buffersize) {
 | 
						|
			ph->serialout.buffer[pos] = ph->serialout.outchar;
 | 
						|
			pos++;
 | 
						|
			*ph->serialout.bufferpos = pos;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void gtserialout_onrisingvsync (struct GTState *gt,
 | 
						|
	struct GTPeriph *ph)
 | 
						|
{
 | 
						|
	ph->serialout.invpulse = 0;
 | 
						|
	if (ph->serialout.count == 9) {
 | 
						|
		ph->serialout.outchar =
 | 
						|
			0x80 | (ph->serialout.outchar >> 1);
 | 
						|
		ph->serialout.bitcount++;
 | 
						|
	} else if (ph->serialout.count == 7) {
 | 
						|
		ph->serialout.outchar =
 | 
						|
			0 | (ph->serialout.outchar >> 1);
 | 
						|
		ph->serialout.bitcount++;
 | 
						|
	} else {
 | 
						|
		ph->serialout.bitcount = 0;
 | 
						|
	}
 | 
						|
	if (ph->serialout.bitcount > 0 && (ph->serialout.buffer == NULL ||
 | 
						|
		ph->serialout.bufferpos == NULL ||
 | 
						|
		*ph->serialout.bufferpos >= ph->serialout.buffersize)) {
 | 
						|
 | 
						|
		gt->in = 0x03;
 | 
						|
		ph->serialout.sentfull = 1;
 | 
						|
	} else if (ph->serialout.sentfull) {
 | 
						|
		gt->in = 0xff;
 | 
						|
		ph->serialout.sentfull = 0;
 | 
						|
	}
 | 
						|
	if (ph->serialout.bitcount == 8) {
 | 
						|
		ph->serialout.bitcount = 0;
 | 
						|
		writechar(ph, (char) ph->serialout.outchar);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void gtserialout_setbuffer (struct GTPeriph *ph, char *buffer,
 | 
						|
	size_t buffersize, size_t *bufferpos)
 | 
						|
{
 | 
						|
	ph->serialout.buffer = buffer;
 | 
						|
	ph->serialout.buffersize = buffersize;
 | 
						|
	ph->serialout.bufferpos = bufferpos;
 | 
						|
}
 | 
						|
 | 
						|
int gtemu_processtick (struct GTState *gt, struct GTPeriph *ph)
 | 
						|
{
 | 
						|
	unsigned char prevout, risingout, fallingout;
 | 
						|
 | 
						|
	prevout = gt->out;
 | 
						|
 | 
						|
	cputick(gt, ph->board.undef);
 | 
						|
 | 
						|
	risingout = ~prevout & gt->out;
 | 
						|
	fallingout = prevout & ~gt->out;
 | 
						|
 | 
						|
	if (!ph->board.booted) {
 | 
						|
		gt->pc = 0;
 | 
						|
		ph->board.booted = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	ph->board.clock++;
 | 
						|
	ph->video.x++;
 | 
						|
	ph->audio.sampletickscount1k += 1000;
 | 
						|
 | 
						|
	if (risingout & 0x40) {
 | 
						|
		/* hsync */
 | 
						|
		unsigned char ac = gt->ac;
 | 
						|
		unsigned short sample8 = (ac & 0xf0) | (ac >> 4);
 | 
						|
		/* only use 15 bit, sometimes SDL needs signed sound */
 | 
						|
		ph->audio.sample = sample8 << 7 | sample8 >> 1;
 | 
						|
 | 
						|
		ph->board.xout = ac;
 | 
						|
 | 
						|
		ph->video.x = -hbackporch;
 | 
						|
		ph->video.y++;
 | 
						|
 | 
						|
		gtloader_onrisinghsync(gt, ph);
 | 
						|
		gtserialout_onrisinghsync(ph);
 | 
						|
	}
 | 
						|
	if (risingout & 0x80) {
 | 
						|
		/* vsync */
 | 
						|
		ph->video.y = -vbackporch + 5;
 | 
						|
		ph->board.undef = xorshift32(ph->board.undef);
 | 
						|
 | 
						|
		gtserialout_onrisingvsync(gt, ph);
 | 
						|
	}
 | 
						|
	if (fallingout & 0x80) {
 | 
						|
		gtloader_onfallingvsync(gt, ph);
 | 
						|
		gtserialout_onfallingvsync(ph);
 | 
						|
	}
 | 
						|
	return risingout & 0xc0;
 | 
						|
}
 | 
						|
 | 
						|
static const unsigned long long colors[] = {
 | 
						|
/* a single value spans four pixels, each in SDL_PIXELFORMAT_RGB444 */
 | 
						|
0x0000000000000000, 0x0500050005000500, 0x0a000a000a000a00, 0x0f000f000f000f00,
 | 
						|
0x0050005000500050, 0x0550055005500550, 0x0a500a500a500a50, 0x0f500f500f500f50,
 | 
						|
0x00a000a000a000a0, 0x05a005a005a005a0, 0x0aa00aa00aa00aa0, 0x0fa00fa00fa00fa0,
 | 
						|
0x00f000f000f000f0, 0x05f005f005f005f0, 0x0af00af00af00af0, 0x0ff00ff00ff00ff0,
 | 
						|
0x0005000500050005, 0x0505050505050505, 0x0a050a050a050a05, 0x0f050f050f050f05,
 | 
						|
0x0055005500550055, 0x0555055505550555, 0x0a550a550a550a55, 0x0f550f550f550f55,
 | 
						|
0x00a500a500a500a5, 0x05a505a505a505a5, 0x0aa50aa50aa50aa5, 0x0fa50fa50fa50fa5,
 | 
						|
0x00f500f500f500f5, 0x05f505f505f505f5, 0x0af50af50af50af5, 0x0ff50ff50ff50ff5,
 | 
						|
0x000a000a000a000a, 0x050a050a050a050a, 0x0a0a0a0a0a0a0a0a, 0x0f0a0f0a0f0a0f0a,
 | 
						|
0x005a005a005a005a, 0x055a055a055a055a, 0x0a5a0a5a0a5a0a5a, 0x0f5a0f5a0f5a0f5a,
 | 
						|
0x00aa00aa00aa00aa, 0x05aa05aa05aa05aa, 0x0aaa0aaa0aaa0aaa, 0x0faa0faa0faa0faa,
 | 
						|
0x00fa00fa00fa00fa, 0x05fa05fa05fa05fa, 0x0afa0afa0afa0afa, 0x0ffa0ffa0ffa0ffa,
 | 
						|
0x000f000f000f000f, 0x050f050f050f050f, 0x0a0f0a0f0a0f0a0f, 0x0f0f0f0f0f0f0f0f,
 | 
						|
0x005f005f005f005f, 0x055f055f055f055f, 0x0a5f0a5f0a5f0a5f, 0x0f5f0f5f0f5f0f5f,
 | 
						|
0x00af00af00af00af, 0x05af05af05af05af, 0x0aaf0aaf0aaf0aaf, 0x0faf0faf0faf0faf,
 | 
						|
0x00ff00ff00ff00ff, 0x05ff05ff05ff05ff, 0x0aff0aff0aff0aff, 0x0fff0fff0fff0fff
 | 
						|
};
 | 
						|
 | 
						|
const unsigned long long undefcolor = 0x0333066604440777;
 | 
						|
 | 
						|
int gtemu_processscreen (struct GTState *gt, struct GTPeriph *ph,
 | 
						|
	void *pixels, int pitch, unsigned short *samples, size_t maxsamples,
 | 
						|
	size_t *nsamples)
 | 
						|
{
 | 
						|
	unsigned long long *line = NULL;
 | 
						|
	int x = ph->video.x, y = ph->video.y;
 | 
						|
	int screenticks;
 | 
						|
	int s = *nsamples;
 | 
						|
 | 
						|
	for (screenticks = 0; screenticks < 110000; screenticks++) {
 | 
						|
		unsigned char risingout;
 | 
						|
 | 
						|
		risingout = gtemu_processtick(gt, ph);
 | 
						|
 | 
						|
		if (risingout & 0x80) {
 | 
						|
			/* vsync */
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		x++;
 | 
						|
		if (risingout & 0x40) {
 | 
						|
			/* hsync */
 | 
						|
			if (line != NULL) {
 | 
						|
				if (x < 0) x = 0;
 | 
						|
				for (; x < screenwidth; x++) {
 | 
						|
					line[x] = undefcolor;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			x = ph->video.x;
 | 
						|
			y = ph->video.y;
 | 
						|
			if (y >= 0 && y < screenheight && pixels != NULL) {
 | 
						|
				line = (unsigned long long *)
 | 
						|
					(pixels + y * pitch);
 | 
						|
			} else {
 | 
						|
				line = NULL;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (line != NULL && x >= 0 && x < screenwidth) {
 | 
						|
			line[x] = colors[gt->out & 0x3f];
 | 
						|
		}
 | 
						|
		if (ph->audio.sampletickscount1k >= ph->audio.sampleticks1k) {
 | 
						|
			ph->audio.sampletickscount1k -= ph->audio.sampleticks1k;
 | 
						|
			if (s < maxsamples && samples != NULL) {
 | 
						|
				samples[s] = ph->audio.sample;
 | 
						|
				s++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	*nsamples = s;
 | 
						|
 | 
						|
	if (pixels != NULL) {
 | 
						|
		if (y < 0) y = 0;
 | 
						|
		for (; y < screenheight; y++) {
 | 
						|
			line = (unsigned long long *) (pixels + y * pitch);
 | 
						|
			for (x = 0; x < 160; x++) {
 | 
						|
				line[x] = undefcolor;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return screenticks;
 | 
						|
}
 | 
						|
 | 
						|
void gtemu_placelights (struct GTPeriph *ph, void *pixels, int pitch,
 | 
						|
	int power)
 | 
						|
{
 | 
						|
	int y;
 | 
						|
	for (y = 474; y < 478; y++) {
 | 
						|
		int i, j, x0;
 | 
						|
		unsigned short c;
 | 
						|
		unsigned short *line = (unsigned short *)
 | 
						|
			(pixels + y * pitch);
 | 
						|
		int xout = gtemu_getxout(ph);
 | 
						|
		for (i = 0; i < 4; i++) {
 | 
						|
			c = xout&1 ? 0x0f42 : 0x222;
 | 
						|
			xout >>= 1;
 | 
						|
			x0 = i * 8 + 6;
 | 
						|
			for (j = x0; j < x0 + 4; j++) {
 | 
						|
				line[j] = c;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		c = power ? 0x0f42 : 0x222;
 | 
						|
		x0 = 632;
 | 
						|
		for (j = x0; j < x0 + 4; j++) {
 | 
						|
			line[j] = c;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 |