aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAugustin Fabre <augustin@augfab.fr>2019-12-30 12:49:24 +0100
committerAugustin Fabre <augustin@augfab.fr>2019-12-30 13:06:24 +0100
commit6301692a1b571ce6b61795916f3cebf8f1d0bfb6 (patch)
treec5c8bd2e9aabfad70e8235f563d42bf201465a5e
parent653c12e1bb314f07b6a1a38b15b5ab2f2a5c9a32 (diff)
downloadminimal-cpu-6301692a1b571ce6b61795916f3cebf8f1d0bfb6.tar.gz
minimal-cpu-6301692a1b571ce6b61795916f3cebf8f1d0bfb6.tar.bz2
minimal-cpu-6301692a1b571ce6b61795916f3cebf8f1d0bfb6.tar.xz
Add assembler
Vastly inspired by Jean-Claude Wippler’s “TFoC - A minimal computer”. https://jeelabs.org/2017/11/tfoc---a-minimal-computer/
-rw-r--r--tools/as.py204
1 files changed, 204 insertions, 0 deletions
diff --git a/tools/as.py b/tools/as.py
new file mode 100644
index 0000000..fca2e64
--- /dev/null
+++ b/tools/as.py
@@ -0,0 +1,204 @@
+import sys
+from functools import wraps
+from struct import pack
+from argparse import ArgumentParser, FileType
+
+def valof(fn):
+ "Decorator to get the value of `arg` in memory before applying `fn`."
+ @wraps(fn)
+ def wrapper(self, arg):
+ if not isinstance(arg, int):
+ arg = self._valof(arg)
+ return fn(self, arg)
+ return wrapper
+
+
+class MCPU(object):
+ def __init__(self, emulator=False):
+ self._emulator = emulator
+ self._pc = 0
+ self._mem = [0 for _ in range(1 << 6)]
+
+ # These constants are needed for combined instructions.
+ self._labels = {"zero": 0x3d,
+ "one": 0x3e,
+ "allone": 0x3f}
+ self._mem[self._labels["one"]] = 1
+ self._mem[self._labels["allone"]] = 0xff
+
+ self._fmap = {"org": self.org,
+ "dcb": self.dcb,
+
+ "nor": self.nor,
+ "add": self.add,
+ "sta": self.sta,
+ "jcc": self.jcc,
+
+ "clr": self.clr,
+ "lda": self.lda,
+ "not": self.not_,
+ "jmp": self.jmp,
+ "jcs": self.jcs,
+ "sub": self.sub,
+
+ "out": self.out,
+ }
+
+ def parse(self, fh):
+ stripped = []
+ for i in fh:
+ i = i.rstrip()
+ if not i:
+ continue
+ comment = i.find(";")
+ if comment == -1:
+ stripped.append(i)
+ else:
+ i = i[:comment].rstrip()
+ if i.lstrip():
+ stripped.append(i)
+
+ for _ in range(2):
+ # First pass: put labels in table.
+ # Second pass: use labels.
+ self._pc = 0
+ for i in stripped:
+ if i.endswith(":"):
+ label = i[:-1]
+ self._labels[label] = self._pc
+ else:
+ mnemonic, arg = i.split()
+ self._fmap[mnemonic](self._valof(arg))
+
+ def disass(self, fh):
+ fh.write("Labels:\tlabel \t @\tmem[@]\n")
+ for k, v in self._labels.items():
+ fh.write(f"\t{k:8s}\t{v:2x}\t{self._mem[v]:2x}\n")
+
+ fh.write("\nMemory:\t @\t\tmem[@]\tmnemo\t@arg\t(@label)\n")
+ for i, b in enumerate(self._mem):
+ val = b & 0x3f
+ label = next((k for k, v in self._labels.items() if v == val), "")
+
+ instr = (b >> 6) & 0x3
+
+ if b == 0xff and self._emulator:
+ val = "out\tacc"
+ elif instr == 0:
+ val = f"nor\t{val:2x}"
+ elif instr == 1:
+ val = f"add\t{val:2x}"
+ elif instr == 2:
+ val = f"sta\t{val:2x}"
+ elif instr == 3:
+ val = f"jcc\t{val:2x}"
+
+ if label:
+ val += f"\t({label})"
+
+ fh.write(f"\t{i:2x}\t\t{b:2x}\t{val}\n")
+
+ def dump(self, fh):
+ for b in self._mem:
+ fh.write(pack("B", b))
+
+ def _valof(self, arg):
+ if isinstance(arg, int):
+ return arg
+
+ if arg in self._labels:
+ return self._labels[arg]
+
+ try:
+ return int(arg, 0)
+ except:
+ return 0xba
+
+ # pseudo instructions
+
+ def org(self, arg):
+ assert 0 <= arg <= 0xff
+ self._pc = arg
+
+ @valof
+ def dcb(self, arg):
+ self._mem[self._pc & 0x3f] = arg
+ self._pc += 1
+
+ # instruction set
+
+ @valof
+ def nor(self, arg):
+ self.dcb(arg | 0x00)
+
+ @valof
+ def add(self, arg):
+ self.dcb(arg | 0x40)
+
+ @valof
+ def sta(self, arg):
+ self.dcb(arg | 0x80)
+
+ @valof
+ def jcc(self, arg):
+ self.dcb(arg | 0xc0)
+
+ # combined instructions
+
+ def clr(self, _):
+ self.nor("allone")
+
+ def not_(self, _):
+ self.nor("zero")
+
+ @valof
+ def jcs(self, arg):
+ self.jcc(self._pc + 2)
+ self.jcc(arg)
+
+ @valof
+ def jmp(self, arg):
+ self.jcc(arg)
+ self.jcc(arg)
+
+ @valof
+ def lda(self, arg):
+ self.nor("allone")
+ self.add(arg)
+
+ @valof
+ def sub(self, arg):
+ self.nor("zero")
+ self.add(arg)
+ self.add("one")
+
+ def out(self, _):
+ if self._emulator:
+ self.jcc(0x3f)
+ else:
+ self.add("zero")
+
+def main(argv):
+ parser = ArgumentParser(description="Assemble to memory image")
+ parser.add_argument("ifile", nargs="?", type=FileType("r"),
+ default=sys.stdin)
+ parser.add_argument("ofile", nargs="?", type=FileType("wb"),
+ default=sys.stdout.buffer)
+ parser.add_argument("-d", "--disass", nargs="?", type=FileType("w"),
+ const=sys.stderr,
+ help="Print disass to file (default to stderr)")
+ parser.add_argument("-e", "--emulator", action="store_true",
+ help="Emit instruction `out`. By default replace with `add zero`.")
+
+ args = parser.parse_args(argv)
+
+ cpu = MCPU(emulator=args.emulator)
+ cpu.parse(args.ifile)
+
+ if args.disass:
+ cpu.disass(args.disass)
+
+ cpu.dump(args.ofile)
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))