[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