542 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			542 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
 | 
						|
libgtemu -- David Kolf's Library for Gigatron Emulation
 | 
						|
=======================================================
 | 
						|
 | 
						|
Version 0.4.0
 | 
						|
 | 
						|
About
 | 
						|
-----
 | 
						|
 | 
						|
libgtemu provides functions for emulating the [Gigatron TTL microcomputer][gt]
 | 
						|
and rendering its graphics through the [SDL library][sdl].
 | 
						|
 | 
						|
It can be used as a lightweight stand-alone emulator as well as being
 | 
						|
controlled by a scripting language such as [Lua][lua].
 | 
						|
 | 
						|
[gt]:  https://gigatron.io/
 | 
						|
[sdl]: http://www.libsdl.org/
 | 
						|
[lua]: https://www.lua.org/
 | 
						|
 | 
						|
License
 | 
						|
-------
 | 
						|
 | 
						|
Copyright (C) 2019 [David Heiko Kolf](http://dkolf.de/).
 | 
						|
 | 
						|
Published under the [BSD 2-Clause License][lic].
 | 
						|
 | 
						|
[lic]: https://opensource.org/licenses/BSD-2-Clause
 | 
						|
 | 
						|
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][lpeg].
 | 
						|
 | 
						|
[lpeg]: http://www.inf.puc-rio.br/~roberto/lpeg/#download
 | 
						|
 | 
						|
### 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][sdl] at least version 2.0.4.
 | 
						|
  *  [Lua][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][scm] (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.
 | 
						|
 | 
						|
[scm]: https://cdn.hackaday.io/files/20781889094304/Schematics.pdf
 | 
						|
 | 
						|
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][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][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.
 | 
						|
 |