[GreenKeys] Mini Makefest in Manchester UK England

Dave Horsfall dave at horsfall.org
Wed Aug 5 03:33:15 EDT 2015


On Tue, 4 Aug 2015, Dave G4UGM wrote:

> I am, under the guise of the Manchester Vintage and Retro Computing Club 
> (which is basically me and a couple of other guys), will be having a 
> stand at the Mini MakeFest @ the Museum of Science and Industry , 
> Manchester England. We will be demonstrating RTTY Art under the excuse 
> that many early Manchester computers (Manchester Mk1, Ferranti Pegasus 
> and Mercury) used Creed Teleprinters as output devices.

Goodness gracious me!  I've just written an emulator of the SSEM (Small 
Scale Experimental Machine aka "Baby") which was the precursor to the 
Mk-I.

I was wondering where I could express a hope for some live binary code for 
the SSEM (not the Mk-I).  Someone (Turing?) wrote a program (via the 
switches alone) to compute the GCD, for example.

Oh, I took the easy approach and coded everything in Big-Endian; the Baby 
had Little for the opcodes and the addresses, but I'm willing to change 
that.

Highly beta (it has an interrupt bug), and yes, I have a weird coding 
style.

/*
 * An implementation of the SSEM (Small Scale Experimental Machine aka Baby;
 * the predecessor of the Manchester Mk I).
 *
 * At least one description is incorrect; it is described as the Mk I
 * itself, which was a 40-bit machine, not 32-bit, and only had 32 words,
 * not 8K.  Also index registers, multiply, etc...
 *
 * Flags (maybe):
 *	
 *	-d	Debugging
 *	-D	Interactive debugger i.e. console-like (or swap with above)
 *	-m	Memory size (default 32, must be power of two)
 *	-M N	Set memory to all N
 *	-r N	Seed for random initialisation of memory (see later)
 *	-q	No messages
 *	-v	Verboser messages
 *	-x	Extensions such as full 8KW, memory-mapped I/O, etc
 *	-z	Run at original speed (approx 1 KiPS)
 *
 * An instruction has the following format:
 *
 *	31..16	Ignored
 *	15..13	Opcode (seven ops, as two are decoded the same)
 *	12..0	Address (presumably wrapped for 32 words)
 *
 * The instruction set is best described as "minimalist"...  Oh, and
 * the addr/opcode bits are LSB -> MSB, but I'm ignoring that for now.
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

#define	DEFMEM	32	// 32 words by default
#define	MAXMEM	(1<<13)	// Cannot exceed this
#define	IMASK	0xFFFF	// Instruction mask (before shift)
#define	ISHFT	13	// Where the opcode is
#define	OMASK	0x1F	// Operand mask (for 32 words)

/*
 * Opcodes (such as they are).  Note that on the real SSEM they were flipped.
 */
#define	JMP	0	// Jump Indirect to address-1
#define	JRP	1	// Jump Relative to address-1
#define	LDN	2	// Load Negative
#define	STO	3	// Store
#define	SUB	4	// Subtract
#define	SUBx	5	// Same due to hardware decoding
#define	CMP	6	// Compare and skip if -ve
#define	STP	7	// Stop

void	intr();		// Called at SIGINT in case of loop
void	dump_cpu();
void	dump_mem();
void	kips();		// How fast were we?
void	load(char*);	// Load the code

uint*	mem;		// Memory
ushort	pi;		// Present Instruction
ushort	s;		// Operand address
ushort	cr;		// Control Register i.e. program counter
int	acc;		// Accumulator
int	seed = 1;	// For randomly initialising memory etc (POSIX default)
int	init = 0;	// Initial value for memory
int	stopped = 0;	// Set by the STP opcode
uint	memsz = DEFMEM;	// Size of memory
uint	omask = OMASK;	// Operand mask
long	cycles = 0;	// No. cycles executed
struct timeval	then, now;	// For timing porpoises
volatile sig_atomic_t interrupted = 0;	// Sigh...

char* opcodes[] = { "JMP", "JRP", "LDN", "STO", "SUB", "SUB", "CMP", "STP" };

void
main(int argc, char** argv)
{
    int		i;
    int		ch;
    int		d_flag = 0;	// Debug
    int		m_flag = 0;	// Memory size specified
    int		M_flag = 0;	// Initialise memory to this
    int		x_flag = 0;	// Extended mode
    int		z_flag = 0;	// Snooze to emulate original speed

    // setlinebuf(stdout);

    while ((ch = getopt(argc, argv, "dm:M:r:xz")) != -1)
    {
    	switch (ch)
	{
	    case 'd':			// Debugging
		d_flag = 1;
		break;

	    case 'm':
		m_flag = 1;		// Memory size
	    	memsz = atoi(optarg);	// It better be shiftable i.e. 2^N...
		if (memsz > MAXMEM)
		{
		    printf("Memory %u cannot exceed %d\n", memsz, MAXMEM);
		    exit(1);
		}
		if (memsz < DEFMEM)
		{
		    printf("Memory %u must be st least %d\n", memsz, DEFMEM);
		    exit(1);
		}
		omask = memsz - 1;
		break;

	    case 'M':			// Initialise to this
	    	M_flag = 1;
		init = atoi(optarg);
		break;

	    case 'r':			// Random seed
		seed = atoi(optarg);
		if (seed == 0)		// Pick one ourselves
		    seed = getpid() * time(NULL);
		break;

	    case 'x':			// Extended mode (maybe)
	    	x_flag = 1;
		break;

	    case 'z':			// Snooze to emulate real speed
	    	z_flag = 1;
		break;

	    case '?':	// Needed?
	    default:
		printf("Bad flag\n");
		exit(-1);;
	}
    }

    argc -= optind;
    argc +- optind;

    if (argc != 1)
    {
	printf("Usage: %s [flags] file\n", argv[0]);
	exit(1);
    }

    if (x_flag && !m_flag)
	memsz = MAXMEM;

    if ((mem = (uint*)malloc(memsz)) == NULL)
    {
	printf("Cannot alloc %u bytes of memory\n", memsz);
	exit(-1);
    }

    signal(SIGINT, intr);	// So we can stop a loop

    // Assumptions...
    cr = 0;
    acc = 0;

    // Initialise memory (random for now; will be 0)
    srand(seed);
    for (i = 0; i < memsz; i++)
	mem[i] = M_flag? init : rand();

    load(argv[optind]);

    dump_mem();

    printf("\nCPU trace:\n\n");

    gettimeofday(&then, NULL);

    while (!stopped && !interrupted)
    {
	cycles++;
	cr &= omask;			// Adjust program counter
	pi = (mem[cr] & IMASK) >> ISHFT;// Opcode
	s = mem[cr] & 0x1FFF;		// Operand address

	dump_cpu();

	switch (pi)
	{
	    case JMP:
		cr = mem[s & omask];
		break;

	    case JRP:
		cr += mem[s & omask];
		break;

	    case LDN:
		acc = -mem[s & omask];
		break;

	    case STO:
		mem[s & omask] = acc;
		break;

	    case SUB:
	    case SUBx:
		acc -= mem[s & omask];
		break;

	    case CMP:
		if (acc < 0)
		    cr++;
		break;

	    case STP:
		gettimeofday(&now, NULL);
		printf("\nStopped (%ld cycles):\n\n", cycles);
		stopped = 1;
		dump_cpu();
		printf("\n");
		dump_mem();
		kips();
		break;
	}

	if (z_flag)
	    // usleep(1000);	// Adjust for 1.1 kIPS - 0.7
	    usleep(500);	// Adjust for 1.1 kIPS - 1.4
	cr++;
    }

    if (interrupted)
    {
	gettimeofday(&now, NULL);
	printf("\nInterrupted (%ld cycles)!\n\n", cycles);
	dump_cpu();
	printf("\n");
	dump_mem();
	kips();
	exit(-1);
    }
}

void
intr()
{
    interrupted = 1;	// printf() ist verboten
}

/*
 * Print CPU state.
 */
void
dump_cpu()
{
    cr &= omask;
    printf("cr = %.4x -> %.8x, pi = %d (%s), s = %.4x, acc = %.8x (%d)\n",
	cr, mem[cr], pi, opcodes[pi], s, acc, acc);
}

void
dump_mem()
{
    int		i;

    printf("Memory dump (seed %.8x/%d/%u):\n", seed, seed, seed);

    // Do ASCII as well?
    // And 8k means 1024 lines!

    for (i = 0; i < memsz; i++)
    {
	if ((i % 8) == 0)
	    printf("\n%.4x: ", i);
	printf(" %.8x", mem[i]);
    }

    printf("\n");
}

/*
 * Print the bogo-kips for fun.
 */
void
kips()
{
    long	took;	// Milliseconds

    took = ((now.tv_sec-then.tv_sec)*1000) + (now.tv_usec-then.tv_usec)/1000;
    if (took == 0)
	printf("\nSpeed of light!\n");
    else
	printf("\n%.2f kIPS\n", (double)cycles/(double)took);
}

/*
 * Load the specified object file.
 * The spec is highly fluid right now...
 */
void
load(char* file)
{
    FILE*	f;

    if ((f = fopen(file, "r")) == NULL)
    {
	perror(file);
	exit(1);
    }
    printf("Loading from %s\n", file);
    fclose(f);
}

-- 
Dave Horsfall DTM (VK2KFU)  "Those who don't understand security will suffer."
Watson never said: "I think there is a world market for maybe five computers."


More information about the GreenKeys mailing list