Skip to content
Branch: master
Find file History
Pull request Compare This branch is 2 commits behind kervinck:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
LICENSE
Makefile
README.md
gtemu.c
gtemu.h
gtloader.c
gtmain.c
gtsdl.c
gtsdl.h
luagt.c

README.md

libgtemu -- David Kolf's Library for Gigatron Emulation

Version 0.4.0

About

libgtemu provides functions for emulating the Gigatron TTL microcomputer and rendering its graphics through the SDL library.

It can be used as a lightweight stand-alone emulator as well as being controlled by a scripting language such as Lua.

License

Copyright (C) 2019 David Heiko Kolf.

Published under the BSD 2-Clause License.

Compiling

You will probably have to modify the provided Makefile to fit to your system, but there are only a few files and few dependencies. As an example for compiling Lua bindings you might look at LPEG.

Files

  • Main library: gtemu.c, gtloader.c and gtsdl.c
  • Example application: gtmain.c
  • Lua bindings: luagt.c

The main library could also be compiled without gtsdl.c if you want to use a different library for graphics and sound.

Dependencies

  • Core SDL at least version 2.0.4.
  • Lua version 5.3 (for the Lua bindings).

Future

The following features might be nice to have:

  • Python bindings: Python is used for most other scripts related to the Gigatron, so it would be nice to be able to use this emulator from Python as well.
  • Proper Makefile and precompiled binaries for Windows.
  • More RAM aliases in the script bindings.
  • IIR audio filters to replicate the sound output of the Gigatron more precisely. According to the Gigatron schematics (page 8/8) there are two single order filters in series, a low pass at 700 Hz and a high pass at 160 Hz that overlap to reduce the peak volume by a factor of 5.

Stand-alone emulator

The stand-alone emulator offers the following basic features on the command line:

usage: ./gtrun [-h] [options]

Arguments:
 -h               Display this help.
 -l filename.gt1  GT1 program to be loaded at the start.
 -t filename.gtb  Text file to be sent with Ctrl-F3.
 -r filename.rom  ROM file (default name: gigatron.rom).
 -64              Expand RAM to 64k.

Special keys:
    Ctrl-F2       Send designated GT1 file.
    Ctrl-F3       Send designated text file.
    Alt-L         Perform hard reset and select loader.
    Alt-X         Perform hard reset and send GT1 file.
    ESC           Close the emulation.

Structure and C-API

This library is split into two parts: pure emulation functions without dependencies and SDL rendering functions. You can use the emulation functions directly and use other means to output graphics and sound.

Emulation and peripherals

The core emulation is written without external dependencies and just performs the raw computations. Its functions are defined in the header file gtemu.h.

gtemu.h defines two major structures: GTState and GTPeriph. GTState containes the state of the CPU and its fields are open for inspection and manipulation:

struct GTRomEntry {
	unsigned char i;
	unsigned char d;
};

struct GTState {
	int pc;
	unsigned char ir, d, ac, x, y, out, in;
	struct GTRomEntry *rom;
	size_t romcount;
	unsigned char *ram;
	unsigned int rammask;
};

GTPeriph on the other hand contains the state of the peripherals; the board, the video output, the audio output, the GT1 loader and the serial output. The variables are implementation details and should not be accessed directly.

struct GTPeriph;

gtemu_init

extern void gtemu_init (struct GTState *gt,
	struct GTRomEntry *rom, size_t romsize,
	unsigned char *ram, size_t ramsize);

Initializes the contents of the GTState structure and sets pointers to the given RAM and ROM arrays. You can (and should) initialize the content of those arrays. romsize is the size in bytes, not the number of entries.

The RAM size has to be at least 256 bytes.

gtemu_initperiph

extern void gtemu_initperiph (struct GTPeriph *ph, int audiofreq,
	unsigned long randseed);

Initializes the state of the simulated peripherals. audiofreq should be a valid value even if you do not want to output the sound (just set it to 48000). Otherwise you can get it from the SDL functions. randseed is a seed value for the random number generator.

gtemu_randomizemem

unsigned long gtemu_randomizemem (unsigned long seed,
	void *mem, size_t size);

Randomizes a designated area of memory (like the RAM or the ROM). The return value is the new state of the xorshift32-random number generator.

gtemu_getclock

extern unsigned long long gtemu_getclock (struct GTPeriph *ph);

Returns the total number of instructions executed so far.

gtemu_getxout

extern unsigned char gtemu_getxout (struct GTPeriph *ph);

Returns the current state of the XOUT register (the "blinkenlights" and the sound output).

gtemu_processtick

extern int gtemu_processtick (struct GTState *gt, struct GTPeriph *ph);

Advances the emulation one tick further. This does not output any graphics or sound but still keeps track of the position of the video beam.

It returns the value of rising edges on the synchronization signals in OUT.

gtemu_processscreen

extern int gtemu_processscreen (struct GTState *gt, struct GTPeriph *ph,
	void *pixels, int pitch,
	unsigned short *samples, size_t maxsamples, size_t *nsamples);

Advances the simulation until either the next rising edge of the VSync signal was detected or 110000 steps were processed.

This function is called by internally by gtsdl_render (and gtsdl_runuiframe), you only need to call it when not using one of those functions.

It will output the pixels in a 16-bit RGB444 format, the pitch variable determines the length of one row in bytes. The pixel and pitch arguments are chosen to be compatible with the SDL_LockTexture function from SDL.

The sound samples are expected to be 16-bit in native byte order. This function will output only positive 15-bit numbers to be compatible with both unsigned and signed formats.

The pointers pixels and samples may be NULL if you are not interested in the output.

nsamples points to a variable with the current position inside the samples array and must not be NULL, even if no sound is being played. Just let it point to a dummy variable on the stack.

The return value is the number of instructions that were executed.

gtemu_placelights

void gtemu_placelights (struct GTPeriph *ph, void *pixels, int pitch,
	int power);

Places LEDs as pixels on the bottom of the screen. The positions are chosen to not obscure any pixel of the emulation completely and the colors are chosen from outside the palette available to Gigatron applications.

This function is called internally by gtsdl_render (and gtsdl_runuiframe), you only need to use it when not using one of those functions.

power indicates the state of the power LED. The default rendering function uses it to indicate that the frame rate is at the expected 60 FPS.

gtserialout_setbuffer

void gtserialout_setbuffer (struct GTPeriph *ph, char *buffer,
	size_t buffersize, size_t *bufferpos);

Sets the buffer into which emulated programs can write data by manipulating the synchronization signals. bufferpos points to a variable containing the current index into the buffer. You can change that variable at any time to make place for new data.

TinyBasic on the Gigatron sends an empty newline (\n) when it wants to clear the buffer and start a new output. In this emulation the empty line is just placed into the buffer as well without having any built-in special effects.

gtloader_sendgt1

extern int gtloader_sendgt1 (struct GTPeriph *ph,
	const char *data, size_t datasize);

Instructs the emulation to send the contents of a GT1 file to the emulated Gigatron that can be evaluated by the Loader program. This function does not check whether the GT1 is valid and expects the Loader application to be running already.

The memory pointed to by data must stay valid until everything is sent.

It returns 1 on success and 0 on failure when there is still previous data that was not completely sent.

gtloader_isactive

extern int gtloader_isactive (struct GTPeriph *ph);

Returns whether the loader is active at the moment.

gtloader_validategt1

extern int gtloader_validategt1 (const char *data, size_t datasize);

Validates a GT1 file.

The return value is 1 if the contents were valid, 0 otherwise.

gtloader_sendtext

extern int gtloader_sendtext (struct GTPeriph *ph,
	const char *data, size_t datasize);

Sends data as keystrokes to the emulated Gigatron that can be received by TinyBASIC and other programs accepting text input.

The memory pointed to by data must stay valid until everything is sent.

It returns 1 on success and 0 on failure when there is still previous data that was not completely sent.

gtloader_sendkey

extern int gtloader_sendkey (struct GTState *gt, struct GTPeriph *ph,
	char key);

Sends a single keypress to the emulated Gigatron. When two different keys are sent quickly after each other, the second key will directly replace the input value without first sending a null input.

It returns 1 on success and 0 on failure when there is still previous data that was not completely sent.

SDL

The necessary resources for SDL and some state are kept in the GTSDLState structure. You should access its content only through the provided functions.

struct GTSDLState;

gtsdl_openwindow

extern int gtsdl_openwindow (struct GTSDLState *s, const char *title);

Initializes the necessary SDL subsystems (you should call SDL_Init before) and creates a standalone window.

It returns 1 in case of success and 0 in case of errors. The error can be requested using SDL_GetError.

gtsdl_getaudiocallback

extern SDL_AudioCallback gtsdl_getaudiocallback();

Returns a pointer to the audio callback. Can be used if you want to set up audio on your own (see also gtsdl_setup).

gtsdl_setup

extern int gtsdl_setup (struct GTSDLState *s, SDL_Renderer *renderer,
	SDL_AudioDeviceID audiodev, SDL_AudioSpec *audiospec);

This function is an alternative to gtsdl_openwindow in case your application initialized SDL and created an window on its own.

The audiospec has to be 16-bit integer and you can get the callback function through gtsdl_getaudiocallback.

In case you want to mix the audio you could avoid the provided SDL functions of this library and just call gtemu_processscreen directly in your own sound and video rendering function.

It returns 1 in case of success and 0 in case of errors. The error can be requested using SDL_GetError.

gtsdl_close

extern void gtsdl_close (struct GTSDLState *s);

Closes all SDL resources that were requested by this library. In case of the gtsdl_openwindow function it will free all resources and you just need to call SDL_Close. In case of the gtsdl_setup function it will not free resources allocated outside of the library.

gtsdl_getaudiofreq

extern int gtsdl_getaudiofreq (struct GTSDLState *s);

Returns the frequency of the used audio device.

gtsdl_render

extern SDL_Texture *gtsdl_render (struct GTSDLState *s, struct GTState *gt,
	struct GTPeriph *ph);

Emulates and renders a single frame, queues the audio and returns the finished texture.

This function should be used when the library was initialized with gtsdl_setup. You need to keep control of the frame rate yourself, otherwise the emulation might get ahead of the audio output.

gtsdl_runuiframe

extern int gtsdl_runuiframe (struct GTSDLState *s, struct GTState *gt,
	struct GTPeriph *ph, SDL_Event *ev);

Renders one frame in the standalone window created by gtsdl_openwindow and checks for SDL_Events using SDL_PollEvent.

In case there is a event this function returns immediately without any further actions with the return value 1. The SDL_Event structure contains the current event.

When there is no event it will emulate and render one frame and will call SDL_Delay to limit the frame rate to 60 FPS. In this case the return value is 0;

gtsdl_handleevent

extern int gtsdl_handleevent (struct GTSDLState *s, struct GTState *gt,
	SDL_Event *ev);

Processes keyboard events and updates the IN register accordingly. It returns 1 if it was a handled event, 0 otherwise.

Lua bindings

About Lua

Lua is a lightweight scripting language that can be embedded into other applications as well as being used as a standalone interpreter with an interactive prompt.

The Lua bindings for the emulation library can be loaded using

gtemu = require "gtemu"

assuming that the DLL is in the packages search path.

API

gtemu.initsdl ()

Calls SDL_Init and registers SDL_Close for atexit.

gtemu.openwindow (title)

Initializes the SDL subsystems and creates a window. A window object is returned.

window:runloop (emulation, eventhandlers)

Runs an event loop for the specified emulation (see below) until either the event SQL_QUIT was received, one of the eventhandlers requested a break or the Escape key was detected.

eventhandlers is an optional table where the callback functions onkeydown (keyname, mods, repeated, scancode, keycode), onkeyup (keyname, mods, repeated, scancode, keycode), ontextinput (text), ondropfile (filename) and onframe () can be defined.

The functions can return either true, false or the string "break". When true is returned the event handling is finished, for false the default event handling is executed, for break the loop is interrupted.

window:close ()

Closes the window and releases the SDL subsystems. This function is called automatically when the window object is garbage collected or at the end of the application.

gtemu.newemulation (window [, ramsize])

Initializes the emulation. ramsize can be either 32 or 64. When omitted it defaults to 32.

This function returns an emulation object.

Example of setting up the application and starting an emulation:

gtemu = require "gtemu"

gtemu.initsdl()
window = gtemu.openwindow("Gigatron in Lua")
emulation = gtemu.newemulation(window, 64)

f = assert(io.open("gigatron.rom", "rb"))
emulation:loadrom(f:read("*a"))
f:close()

window:runloop(emulation)

Properties of emulation

In the emulation the following registers can be both read and modified:

pc, ir, d, ac, x, y, out, inp

For example:

if emulation.pc == 0x0123 then
	emulation.inp = 0xff
end

The contents of the RAM can be accessed using the array notation:

if emulation[0x0123] == 0x10 then
	emulation[0x1234] = 0x20
end

There is a special variable that maps the the vPC memory location in ram:

if emulation.vpc >= 0x5a0c then
	-- An application (probably the Loader) is running inside
	-- screen memory
end

emulation.vpc = 0x200

This is equivalent to manual access to the RAM locations 0x16 and 0x17. Further aliases might be defined in future versions.

The following read-only values are also available:

clock, xout

emulation:loadrom (data)

Loads a string into ROM:

f = assert(io.open("gigatron.rom", "rb"))
emulation:loadrom(f:read("*a"))
f:close()

emulation:processtick ()

Advances the simulation for a single tick without any output.

emulation:processscreen ()

Advances the simulation for an entire screen without any output.

emulation:sendgt1 (data)

Sends data from a GT1 file to the Loader application (assuming it is running):

f = assert(io.open("Overworld.gt1", "rb"))
gt1 = f:read("*a")
f:close()

if emulation.vpc >= 0x5a0c then
	emulation:sendgt1(gt1)
end

The data is automatically verified before sending, an error will be raised for invalid data.

emulation:createbuffer (size)

Creates a buffer the Gigatron can send output to.

emulation:getbuffer ()

Requests the current contents of the buffer.

emulation:resetbuffer ()

Clears the buffer.

You can’t perform that action at this time.