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;
|
|
}
|
|
}
|
|
}
|
|
|