commit 31f1d1ddfcd8ac9a301f5b39224ad7a8b9d273b5 from: Sven M. Hallberg date: Sun Jan 01 13:42:16 2023 UTC WIP prototype Designed and tested to run on OpenBSD. Translation works, but upload does not. 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 +#include /* uint8_t */ +#include /* exit */ +#include /* strcmp, strdup */ +#include /* isdigit */ +#include /* getopt */ +#include /* tcgetattr, tcsetattr, cfsetspeed */ +#include +#include +#include + +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 +