Commit Diff


commit - /dev/null
commit + 31f1d1ddfcd8ac9a301f5b39224ad7a8b9d273b5
blob - /dev/null
blob + 3e29b1bb8447cc90143c5d3a12cd3b6a0b00725d (mode 644)
--- /dev/null
+++ .gitignore
@@ -0,0 +1,2 @@
+mc5000
+*.bin
blob - /dev/null
blob + e8c3fe930207fcb1b582c4c6a2658653fc7ca645 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,6 @@
+.PHONY: all test
+
+all: mc5000
+
+test: all
+	./mc5000 -o test.bin test.s
blob - /dev/null
blob + fe0fbb1581647a49810f31de2adb96b47cc5b58f (mode 644)
--- /dev/null
+++ README
@@ -0,0 +1,14 @@
+/* assembler and programmer for rickp's MC5000 dev kit
+ * pesco 2022, ISC license
+ *
+ * Reads MC5000 assembly code from stdin or a file. Translates the program and
+ * writes it to the given MCU via serial port.
+ *
+ * See also: https://github.com/rickp/MC5000_DevKit
+ */
+
+run 'make' to build.
+
+run 'mc5000 -h' for usage info.
+
+see 'notes' file for encoding and protocol details.
blob - /dev/null
blob + aae5b8fea4c35b16cb515b2f173f5dccf2d938c1 (mode 644)
--- /dev/null
+++ mc5000.c
@@ -0,0 +1,463 @@
+/* assembler and programmer for rickp's MC5000 dev kit
+ * pesco 2022, ISC license
+ *
+ * Reads MC5000 assembly code from stdin or a file. Translates the program and
+ * writes it to the given MCU via serial port.
+ *
+ * See also: https://github.com/rickp/MC5000_DevKit
+ */
+
+#include <stdio.h>
+#include <stdint.h>	/* uint8_t */
+#include <stdlib.h>	/* exit */
+#include <string.h>	/* strcmp, strdup */
+#include <ctype.h>	/* isdigit */
+#include <unistd.h>	/* getopt */
+#include <termios.h>	/* tcgetattr, tcsetattr, cfsetspeed */
+#include <regex.h>
+#include <assert.h>
+#include <err.h>
+
+enum argtype {ARG_NONE, ARG_R, ARG_RI, ARG_L, ARG_P};
+	
+struct instr {
+	const char *name;
+	char code;
+	enum argtype arg1, arg2;
+};
+
+struct instr instable[] = {
+	/* basic instructions */
+	{"nop", 1},
+	{"mov", 2, ARG_RI, ARG_R},
+	{"jmp", 3, ARG_L},
+	{"slp", 4, ARG_RI},
+	{"slx", 5, ARG_P},
+
+	/* test instructions */
+	{"teq", 6, ARG_RI, ARG_RI},
+	{"tgt", 7, ARG_RI, ARG_RI},
+	{"tlt", 8, ARG_RI, ARG_RI},
+	{"tcp", 9, ARG_RI, ARG_RI},
+
+	/* arithmetic instructions */
+	{"add", 10, ARG_RI},
+	{"sub", 11, ARG_RI},
+	{"mul", 12, ARG_RI},
+	{"not", 13},
+	{"dgt", 14, ARG_RI},
+	{"dst", 15, ARG_RI, ARG_RI},
+};
+
+/* pseudo-instruction */
+#define OP_LBL 16
+
+const char instr_regex[] =
+    "^[ \t]*(([a-zA-Z][a-zA-Z0-9]*):)?"			/* label */
+    "[ \t]*(([+-])?"					/* condition */
+     "[ \t]*([a-zA-Z][a-zA-Z0-9]*)"			/* operation */
+     "([ \t]+([a-zA-Z][a-zA-Z0-9]*|[+-]?[0-9]+))?"	/* argument 1 */
+     "([ \t]+([a-zA-Z][a-zA-Z0-9]*|[+-]?[0-9]+))?)?"	/* argument 2 */
+    "[ \t]*(#.*)?"					/* comment */
+    "[ \t]*\n?$";					/* rest of line */
+
+#define MATCH_LBL	2
+#define MATCH_COND	4
+#define MATCH_OPER	5
+#define	MATCH_ARG1	7
+#define MATCH_ARG2	9
+#define NMATCH (MATCH_ARG2 + 1)
+
+int
+matchp(const regmatch_t *rm, int i)
+{
+	return (rm[i].rm_so != -1);
+}
+
+char *
+matchstr(char *buf, const regmatch_t *rm, int i)
+{
+	if (!matchp(rm, i))
+		return NULL;
+
+	buf[rm[i].rm_eo] = '\0';	/* split the line up */
+	return buf + rm[i].rm_so;
+}
+
+int
+matchchr(const char *buf, const regmatch_t *rm, int i)
+{
+	if (!matchp(rm, i))
+		return -1;
+
+	return (unsigned char)buf[rm[i].rm_so];
+}
+
+
+char buf[1024];
+FILE *outf = NULL;	/* output file */
+FILE *devf = NULL;	/* serial port */
+FILE *inf = NULL;	/* input file */
+const char *fname;	/* input file name */
+int mcu = 1;		/* programming target */
+int line;		/* current line number */
+int status;		/* exit code */
+uint8_t checksum;
+
+static const char *lbltab[256];
+static const int maxlbl = sizeof lbltab / sizeof *lbltab;
+static int nlabel = 0;
+
+void emit_op(int, const char *, const char *, const char *);
+void emit_op_lbl(const char *);
+void emit_byte(int); // XXX
+
+#define STR(X) matchstr(buf, rm, MATCH_ ## X)
+#define CHR(X) matchchr(buf, rm, MATCH_ ## X)
+
+void
+usage(FILE *f, int x)
+{
+	extern const char *__progname;
+	fprintf(f, "usage: %s [-u num] [-l dev | -o file] [file]\n",
+	    __progname);
+	exit(x);
+}
+
+int
+main(int argc, char *argv[])
+{
+	regex_t re;
+	regmatch_t rm[NMATCH];
+	const char *op;
+	int r, cond, label;
+
+	/* compile the instruction regex */
+	if ((r = regcomp(&re, instr_regex, REG_EXTENDED)) != 0) {
+		regerror(r, &re, buf, sizeof buf);
+		errx(1, "regcomp: %s", buf);
+	}
+
+	/* handle command line options */
+	while ((r = getopt(argc, argv, "hl:o:u:")) != -1) {
+		switch (r) {
+		case 'h':
+			usage(stdout, 0);
+		case 'l':
+			if ((devf = fopen(optarg, "r+")) == NULL)
+				err(1, "%s", optarg);
+			outf = devf;
+			break;
+		case 'o':
+			if ((outf = fopen(optarg, "w")) == NULL)
+				err(1, "%s", optarg);
+			devf = NULL;
+			break;
+		case 'u':
+			if (isdigit(*optarg) && optarg[1] == '\0')
+				mcu = *optarg - '0';
+			else
+				err(1, "invalid argument '%s'. option -%c "
+				    "expects a single digit MCU number",
+				    optarg, r);
+			break;
+		default:
+			usage(stderr, 1);
+		}
+	}
+	argc -= optind;
+	argv += optind;
+	if (argc > 1)
+		usage(stderr, 1);
+
+	/* open input file */
+	if (argc == 1) {
+		fname = argv[0];
+		if ((inf = fopen(fname, "r")) == NULL)
+			err(1, "%s", fname);
+	} else {
+		fname = "(stdin)";
+		inf = stdin;
+	}
+
+	/* start programming */
+	if (devf != NULL) {
+		struct termios t;
+
+		/* set line speed */
+		if (tcgetattr(fileno(devf), &t) != 0)
+			err(1, "tcgetattr");
+		cfsetspeed(&t, B19200);
+		if (tcsetattr(fileno(devf), TCSAFLUSH, &t) != 0)
+			err(1, "tcsetattr");
+
+		emit_byte(0x7F);		/* start code */
+		emit_byte(0x30 + mcu);	/* chip id */
+	}
+
+	/* process input */
+	for (line = 1, label = 0; !feof(inf); line++) {
+		/* read one line of input */
+		buf[sizeof buf - 2] = '\0';
+		if (fgets(buf, sizeof buf, inf) == NULL)
+			break;
+		if (buf[sizeof buf - 2] != '\0' &&
+		    buf[sizeof buf - 2] != '\n') {
+			fprintf(stderr, "%s:%d: overlong line\n", fname, line);
+			status = 1;
+			continue;
+		}
+
+		/* parse the line */
+		r = regexec(&re, buf, NMATCH, rm, 0);
+		if (r == REG_NOMATCH) {
+			fprintf(stderr, "%s:%d: syntax error\n", fname, line);
+			status = 1;
+			continue;
+		}
+		else if (r != 0) {
+			regerror(r, &re, buf, sizeof buf);
+			errx(1, "regexec: %s (line %d)", buf, line);
+		}
+
+		/* emit a pseudo-operation for labels */
+		emit_op_lbl(STR(LBL));
+
+		/* emit the actual instruction, if present */
+		emit_op(CHR(COND), STR(OPER), STR(ARG1), STR(ARG2));
+	}
+	if (ferror(inf))
+		errx(1, "%s:%d: read error", fname, line);
+
+	/* finish programming */
+	if (devf != NULL) {
+		uint8_t resp[3];
+
+		checksum = -checksum >> 2;
+		emit_byte(checksum);
+		emit_byte(0x7E);		/* end code */
+		fflush(devf);
+
+		/* read and validate response */
+		if (fread(resp, 1, 3, devf) < 3)
+			errx(1, "error reading response from board");
+		if (resp[0] != 0x7F)
+			errx(1, "unexpected response received");
+		if (resp[1] != 0x30 + mcu)
+			errx(1, "wrong chip ID (0x%.2x) in response", resp[1]);
+		if (resp[2] == 1)
+			printf("MCU #%d: program accepted\n", mcu);
+		else if (resp[2] == 0)
+			errx(1, "MCU #%d: programming failure", mcu);
+		else
+			errx(1, "MCU #%d: unknown result (%d)", mcu, resp[2]);
+	}
+
+	return status;
+}
+
+void
+emit_byte(int n)
+{
+	assert(n >= 0);
+	assert(n < 256);
+	if (outf != NULL)
+		fputc(n, outf);
+	checksum += n;
+}
+
+void
+emit_dummy(void)
+{
+	emit_byte(0xFF);
+	status = 1;
+}
+
+void
+emit_xbus(const char *arg)
+{
+	if (arg[0] != 'x') {
+		fprintf(stderr, "%s:%d: %s is not an XBus port\n",
+		    fname, line, arg);
+		emit_dummy();
+		return;
+	}
+
+	// XXX ?
+	if (arg[1] == '0')		/* x0 */
+		emit_byte(0x40);	/* = 01000000 */
+	else if (arg[1] == '1')		/* x1 */
+		emit_byte(0x00);	/* = 00000000 */
+	else {
+		fprintf(stderr, "%s:%d: undefined port %s\n", fname, line,
+		    arg);
+		emit_dummy();
+	}
+}
+
+int
+emit_reg(const char *arg)
+{
+	static struct {const char *name; uint8_t val;} regtable[] = {
+		{"acc",	0x70},	/* 01110000 */
+		{"dat",	0x60},	/* 01100000 */
+		{"p0",	0x50},	/* 01010000 */
+		{"p1",	0x58},	/* 01011000 */
+		{"x0",	0x40},	/* 01000000 */
+		{"x1",	0x48},	/* 01001000 */
+	};
+	static const int n = sizeof regtable / sizeof *regtable;
+	int i;
+
+	assert(arg != NULL);
+	if (arg[0] == '+' || arg[0] == '-' || isdigit(arg[0]))
+		return 0;		/* it's a number */
+
+	for (i = 0; i < n; i++)
+		if (strcmp(arg, regtable[i].name) == 0)
+			break;
+	if (i == n) {
+		fprintf(stderr, "%s:%d: undefined register %s\n", fname, line,
+		    arg);
+		emit_dummy();
+	}
+
+	emit_byte(regtable[i].val);
+	return 1;
+}
+
+void
+emit_int(const char *arg)
+{
+	int i;
+
+	assert(arg != NULL);
+	i = atoi(arg);
+	if (i > 999)
+		i = 999;
+	else if (i < -999)
+		i = -999;
+	i += 1000;
+
+	/* 000hhhhh 00llllll */
+	assert(i >= 0);
+	assert(i < 2000);
+	emit_byte(i >> 6);		/* 5 high bits */
+	emit_byte(i & 0x3F);		/* 6 low bits */
+}
+
+int
+find_label(const char *arg)
+{
+	int i;
+
+	/* look for the given label in the table */
+	for (i = 0; i < nlabel; i++)
+		if (strcmp(lbltab[i], arg) == 0)
+			return i;
+
+	/* if not found, assign next free number */
+	assert(nlabel < maxlbl);
+	if ((lbltab[nlabel] = strdup(arg)) == NULL)
+		err(1, "new_label");
+
+	return nlabel++;
+}
+
+void
+emit_label(const char *arg)
+{
+	emit_byte(find_label(arg));
+}
+
+void
+emit_arg(const char *op, enum argtype ty, const char *arg)
+{
+	if (ty == ARG_NONE) {
+		if (arg != NULL) {
+			fprintf(stderr, "%s:%d: extra argument to %s: %s\n",
+			    fname, line, op, arg);
+			status = 1;
+		}
+		return;
+	}
+
+	if (arg == NULL) {
+		fprintf(stderr, "%s:%d: %s missing argument\n", fname, line,
+		    op);
+		emit_dummy();
+		return;
+	}
+
+	switch(ty) {
+	case ARG_R:
+		if (!emit_reg(arg)) {
+			fprintf(stderr, "%s:%d: register expected, not %s\n",
+			    fname, line, arg);
+			emit_dummy();
+		}
+		break;
+	case ARG_RI:
+		if (emit_reg(arg))		/* 1 byte */
+			emit_byte(0x00);	/* 1 byte */
+		else
+			emit_int(arg);		/* 2 bytes */
+		break;
+	case ARG_L:
+		emit_label(arg);
+		break;
+	case ARG_P:
+		emit_xbus(arg);
+		break;
+	default:
+		errx(1, "undefined argtype %d\n", ty);
+	}
+}
+
+void
+emit_op(int cond, const char *op, const char *arg1, const char *arg2)
+{
+	static const int n = sizeof instable / sizeof *instable;
+	int i;
+	uint8_t b;
+
+	if (op == NULL)
+		return;
+
+	/* find the operation in the table */
+	for (i = 0; i < n; i++)
+		if (strcmp(instable[i].name, op) == 0)
+			break;
+	if (i == n) {
+		fprintf(stderr, "%s:%d: undefined instruction %s\n", fname,
+		    line, op);
+		emit_dummy();
+		return;
+	}
+
+	/* encode and emit the operation byte: 0ccccccmp */
+	b = instable[i].code << 2;
+	if (cond != -1) {
+		if (cond == '+')
+			b |= 0x01;
+		else if (cond == '-')
+			b |= 0x02;
+		else
+			errx(1, "undefined flag %c (line %d)", cond, line);
+	}
+	emit_byte(b);
+
+	/* emit the arguments */
+	emit_arg(op, instable[i].arg1, arg1);
+	emit_arg(op, instable[i].arg2, arg2);
+}
+
+void
+emit_op_lbl(const char *arg)
+{
+	if (arg == NULL)
+		return;
+
+	emit_byte(OP_LBL << 2);
+	emit_byte(find_label(arg));
+}
blob - /dev/null
blob + f54683f98483759bf77294342b46070776b417fc (mode 644)
--- /dev/null
+++ notes
@@ -0,0 +1,103 @@
+serial programming:
+
+- both microcontrollers are directly connected to the TX and RX lines of the
+  USB serial chip.
+- a start byte followed by a chip ID indicates which MCU should receive/respond
+- maximum program size: 255
+
+protocol:
+1. send start code (0x7F)
+2. send chip ID (0x31 or 0x32)
+3. send program
+4. send checksum (over program)
+5. send end code (0x7E)
+6. expect confirmation from chip:
+   a. start code (0x7F)
+   b. chip ID
+   c. result (0x01 = success, 0x00 = failure)
+
+instruction encoding:
+
+    xcccccmp [arg1] [arg2]
+    ^\___/^^
+    |  |  |'-plus?
+    |  |  '-minus?
+    |  '-operation
+    '-unused (0)
+
+operations:
+ 1 = nop
+ 2 = mov R/I R
+ 3 = jmp L
+ 4 = slp R/I
+ 5 = slx P
+ 6 = teq R/I R/I
+ 7 = tgt R/I R/I
+ 8 = tlt R/I R/I
+ 9 = tcp R/I R/I
+10 = add R/I
+11 = sub R/I
+12 = mul R/I
+13 = not
+14 = dgt R/I
+15 = dst R/I R/I
+16 = lbl L
+31 = reserved (so 0x7F and 0x7E remain available as start/end codes)
+
+argument encoding R/I:
+
+    x110xxxx xxxxxxxx = dat
+    x111xxxx xxxxxxxx = acc
+    x1000xxx xxxxxxxx = x0
+    x1001xxx xxxxxxxx = x1
+    x1010xxx xxxxxxxx = p0
+    x1011xxx xxxxxxxx = p1
+
+    x00hhhhh xxllllll = hhhhhllllll - 1000
+
+argument encoding R:
+
+    x110xxxx = dat
+    x111xxxx = acc
+    x1000xxx = x0
+    x1001xxx = x1
+    x1010xxx = p0
+    x1011xxx = p1
+
+argument encoding L:
+
+    llllllll = label number (assigned in order, starting at 0)
+
+argument encoding P:
+
+    x1xxxxxx = x0
+    x0xxxxxx = x1
+
+
+checksum calculation:
+
+  chk(bytes) = - sum(bytes) / 4 (mod 256)
+
+  calculate mod 256:
+  1. add up the input bytes
+  2. negate
+  3. divide by 4 (shift right by 2 bits)
+  result is a 6-bit number.
+
+
+firmware states:
+ no_prog
+   -> transmission_start, when START_CHAR received
+ empty_prog (initial state)
+   -> transmission_start, when START_CHAR received
+ transmission_start
+   -> transmission_start, when START_CHAR received
+   -> line_prog, if own serial number received
+   -> previous state otherwise
+ line_prog
+   -> transmission_start, when START_CHAR received
+   -> prog_ready, when END_CHAR received and checksum OK
+   -> empty_prog, when END_CHAR received and checksum fails
+   -> empty_prog, when END_CHAR received while program_buf_pos <= 2
+ prog_ready
+   -> transmission_start, when START_CHAR received
blob - /dev/null
blob + 9e412dd22a24be2f6cf36e3aad0cfba966ad720c (mode 644)
--- /dev/null
+++ test.s
@@ -0,0 +1,13 @@
+start:
+ teq p0 100
++mov 100 p1
+ nop
++slp 5
++mov 0 p1
+ slp 5
+
+-nop #keck wirst
+
+ jmp end
+ end:jmp start
+