From 5742e1f66c4e70151865de7092547223898bbf6b Mon Sep 17 00:00:00 2001 From: Julian Blake Kongslie Date: Sun, 18 Apr 2021 16:00:54 -0700 Subject: Support a proper PDP-8 assembler. --- Makefile | 18 +++++- asm.rb | 70 ----------------------- mem/fib.pal | 117 ++++++++++++++++++++++++++++++++++++++ mem/hello.pal | 30 ++++++++++ pll.rb | 23 -------- tool/p8bin2hex.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tool/pll.rb | 23 ++++++++ 7 files changed, 356 insertions(+), 95 deletions(-) delete mode 100755 asm.rb create mode 100644 mem/fib.pal create mode 100644 mem/hello.pal delete mode 100755 pll.rb create mode 100644 tool/p8bin2hex.c create mode 100755 tool/pll.rb diff --git a/Makefile b/Makefile index 522a7d5..e180bc1 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,26 @@ SOURCES := $(shell find hdl sim -name \*.sv -o -name \*.cpp) HEADERS := $(shell find hdl -name \*.svh) -MEMORIES := $(shell find mem -name \*.hex) +MEMORIES := $(shell find mem -name \*.hex) $(addprefix build/, $(addsuffix .hex, $(basename $(shell find mem -name \*.pal)))) QUARTUS := $(shell find altera tcl -name \*.cdf -o -name \*.sdc -o -name \*.tcl) OPTS ?= -sim: build/Vtop +sim: build/Vtop $(MEMORIES) $< .PHONY: sim +build/p8bin2hex: tool/p8bin2hex.c + @mkdir -p build + gcc -Wall -Werror -o $@ $< + +build/%.hex: build/%.bin build/p8bin2hex + build/p8bin2hex $< > $@ + +build/%.bin: %.pal + @mkdir -p $(dir $@) + palbart $< + mv -f $*.bin $*.lst $(dir $@) + build/Vtop: $(SOURCES) $(HEADERS) @rm -rf verilator @mkdir -p build verilator @@ -29,3 +41,5 @@ fpga: noncpu.sof clean: git clean -dfX .PHONY: clean + +.SECONDARY: diff --git a/asm.rb b/asm.rb deleted file mode 100755 index 6da40fb..0000000 --- a/asm.rb +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/ruby -w - -OPCODES = { - "i" => 0x080, - "acc=" => 0x100, - "ladd" => 0x200, - "store" => 0x300, - "ifeq" => 0x400, - "jmp" => 0x500, - "ascii" => 0x600, - "cla" => 0x001, - "++acc" => 0x002, - "--acc" => 0x004, - "tx" => 0x040, - "rx" => 0x080, - "halt" => 0x000, - } - -Line = Struct.new(:opcode, :refs, :code) - -$labels = {} -$code = [] -ARGF.each_line() do | line | - line.chomp!() - line.sub!(/^.*\/\/\s*/, "") - next unless line =~ /\S/ - op = 0x000 - refs = [] - line.scan(/\S+/).each() do | word | - break if word =~ /^#/ - if word =~ /^0(\d+)$/ - op |= $1.to_i(8) - elsif word =~ /^-0(\d+)$/ - op |= 0x100 - $1.to_i(8) - elsif word =~ /^(\d+)$/ - op |= $1.to_i(10) - elsif word =~ /^-(\d+)$/ - op |= 0x100 - $1.to_i(10) - elsif word =~ /^0x([0-9a-f]+)$/i - op |= $1.to_i(16) - elsif word =~ /^-0x([0-9a-f]+)$/i - op |= 0x100 - $1.to_i(16) - elsif OPCODES.key?(word) - op |= OPCODES[word] - elsif word =~ /^(.+):$/ - $labels[$1] = $code.size() - else - refs << word - end - end - $code << Line.new(op, refs, line) -end - -$code.each_with_index() do | line, i | - op = line.opcode - line.refs.each() do | ref | - if ref =~ /^@(.+)$/ and $labels.key?($1) - op |= $labels[$1] - elsif $labels.key?(ref) - target = $labels[ref] - (i + 1) - throw "Jump too far forward" if target > 0x7f - target += 0x80 if target < 0 - throw "Jump too far backward" if target < 0 - op |= target - else - throw "I don't understand #{ref.inspect()}" - end - end - $stdout.write("#{op.to_s(16).rjust(3, "0")} // #{line.code}\n") -end diff --git a/mem/fib.pal b/mem/fib.pal new file mode 100644 index 0000000..6f56109 --- /dev/null +++ b/mem/fib.pal @@ -0,0 +1,117 @@ + *200 +MAIN, CLA + TAD (-26) + DCA FIBN + DCA FIB0 + IAC + DCA FIB1 +LOOP, CLA + TAD FIB0 + JMS PUTN + CLA + TAD (054) + JMS PUTC + CLA + TAD FIB0 + TAD FIB1 + DCA FIB2 + TAD FIB1 + DCA FIB0 + TAD FIB2 + DCA FIB1 + ISZ FIBN + JMP LOOP +EXIT, HLT + CLA IAC + DCA FIB1 + DCA FIB0 + JMP MAIN +PUTN, 0 + JMS ITOA + CMA IAC + IAC + CMA IAC + DCA 10 +PUTNL, CLA + TAD I 10 + SPA + JMP I PUTN + TAD (60) + JMS PUTC + JMP PUTNL +PUTC, 0 + TLS + TSF + JMP .-1 + JMP I PUTC +ITOA, 0 + DCA ITOAV + DCA ITOAD + DCA ITOAD+1 + DCA ITOAD+2 + DCA ITOAD+3 + TAD (-14) + DCA ITOAN +ITOAL, CLL / LOOP OVER BINARY INPUT BITS + TAD ITOAD+3 + JMS ITOA2X + DCA ITOAD+3 + TAD ITOAD+2 + JMS ITOA2X + DCA ITOAD+2 + TAD ITOAD+1 + JMS ITOA2X + DCA ITOAD+1 + TAD ITOAD + JMS ITOA2X + DCA ITOAD + CLL + TAD ITOAV / SHIFT MSB OUT OF INPUT VALUE + RAL + DCA ITOAV + RAL + TAD ITOAD+3 / ADD SHIFTED-OUT MSB TO DECIMAL LSD + DCA ITOAD+3 + ISZ ITOAN + JMP ITOAL / NEXT LOOP ITERATION + TAD (ITOAD) + DCA ITOAR +ITOAJ, TAD I ITOAR + SZA + JMP ITOAX + ISZ ITOAR + TAD I ITOAR + SZA + JMP ITOAX + ISZ ITOAR + TAD I ITOAR + SZA + JMP ITOAX + ISZ ITOAR +ITOAX, CLA + TAD ITOAR + JMP I ITOA +ITOA2X, 0 + RAL / PUTS 0 IN LINK + DCA ITOAT + TAD (-12) + TAD ITOAT / PUTS 1 IN LINK IF WRAPPED + SMA + DCA ITOAT + CLA + TAD ITOAT + JMP I ITOA2X +FIB0, 0 +FIB1, 1 +FIB2, 0 +FIBN, -27 +ITOAD, 0 / OUTPUT DIGITS (MSD FIRST) + 0 + 0 + 0 + 4000 / OUTPUT SENTINEL +ITOAN, 0 / INPUT BITS LOOP COUNTER +ITOAR, 0 / POINTER TO FIRST NONZERO DIGIT +ITOAT, 0 / SCRATCHPAD FOR ITOA2X +ITOAV, 0 / VALUE BEING CONVERTED (TEMPORARY) +$ diff --git a/mem/hello.pal b/mem/hello.pal new file mode 100644 index 0000000..437ccf9 --- /dev/null +++ b/mem/hello.pal @@ -0,0 +1,30 @@ + *200 +START, CLA CLL + TAD HELLO + DCA 10 +LOOP, TAD I 10 + SNA + JMP EXIT + TLS + TSF + JMP .-1 + CLA + JMP LOOP +EXIT, HLT + JMP START +HELLO, . + "H + "e + "l + "l + "o + ", + " + "w + "o + "r + "l + "d + "! + 0 +$ diff --git a/pll.rb b/pll.rb deleted file mode 100755 index 49464d4..0000000 --- a/pll.rb +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/ruby -w - -TARGET_FREQ = ARGV.shift.to_f -NATIVE_FREQ = 50.0 - -CLOCK_WIDTH = 5 - -best = nil -best_mult = nil -best_div = nil -1.upto(2**CLOCK_WIDTH) do | mult | - 1.upto(2**CLOCK_WIDTH) do | div | - new = NATIVE_FREQ * mult / div - if not best or (new - TARGET_FREQ).abs < (best - TARGET_FREQ).abs or ((new - TARGET_FREQ).abs == (best - TARGET_FREQ).abs and (mult + div) < (best_mult + best_div)) - best = new - best_mult = mult - best_div = div - end - end -end - -error = (best - TARGET_FREQ).abs / TARGET_FREQ -$stdout.write("Closest I can get is #{best}: *#{best_mult} /#{best_div} (#{(error * 100).round}% error)\n") diff --git a/tool/p8bin2hex.c b/tool/p8bin2hex.c new file mode 100644 index 0000000..8d28d19 --- /dev/null +++ b/tool/p8bin2hex.c @@ -0,0 +1,170 @@ +/* + * PDP-8 format converter. + * + * Usage: p8bin2hex tape.bin > tape.hex + * + * Assumes tape.bin is a SIMH-compatible paper tape image in "BIN" or "RIM" + * formats. + * + * RIM format: + * 10.000.000 leader/trailer (at least an inch of these) + * 01.aaa.aaa address hi (bits 5:0 contain address 11:6) + * 00.aaa.aaa address lo (bits 5:0 contain address 5:0) + * 00.xxx.xxx data hi (bits 5:0 contain data 11:6) + * 00.xxx.xxx data lo (bits 5:0 contain data 5:0) + * + * + * BIN format: + * 10.000.000 leader/trailer (at least an inch of these) + * 11.011.000 "field" (bits 6:4 specify address 14:12) + * 01.000.010 origin hi bits: bits 5:0 contain origin 11:6 + * 00.011.100 origin lo bits: bits 5:0 contain origin 5:0 + * 00.xxx.xxx data hi bits: bits 5:0 contain data word 11:6 + * 00.xxx.xxx data lo bits: bits 5:0 contain data word 5:0 + * + * 00.xxx.xxx chucksum hi - sum of all data and origin frames, but + * 00.xxx.xxx chucksum lo - excluding field and leader/trailer + * + * Some tapes may contain assembler error messages. These are are text + * delimited by 0377 bytes. The BIN loader ignores bytes from starting + * at any 0377 byte, until another 0377 byte is seen. + */ + +#include +#include +#include + +uint16_t mem[32768]; + +int +load(FILE *fp) +{ + enum { Addr, Data, Leader, Fetch, Skip , Start } s = Start; + int c = 0, n, v = 0; + uint16_t a = 0, d = 0, t = 0, u = 0; + for (n = 0; (c = getc(fp)) != EOF; ++n) { + switch (s) { + case Addr: + u += c; + a |= c; + fprintf(stderr, " %04o: address %06o\n", c, a & 077777); + s = Fetch; + break; + case Data: + u += c; + d |= 0x8000 | c; + /* fprintf(stderr, " %04o: data %05o\n", c, d & 07777); */ + fprintf(stderr, " %04o", c); + s = Fetch; + break; + case Fetch: + fetch: + if (c == 0200) { + if ((d & 0x8000) != 0) { + if ((d & 07777) != (t & 07777)) + fprintf(stderr, "\nbad checksum %05o\n", t & 07777); + else + v = 1, fprintf(stderr, "\nvalid checksum %05o\n", t & 07777); + } + fprintf(stderr, " %04o", c); + goto leadout; + } + t += u; + u = 0; + if (d & 0x8000) { + fprintf(stderr, ": mem[%06o]=%05o\n", a & 077777, d & 07777); + mem[a] = d & 07777; + a = (a & ~07777) | ((a + 1) & 07777); + d = 0; + } + fprintf(stderr, " %04o", c); + if ((c & 0300) == 0000) { + u = c; + d = (c & 077) << 6; + s = Data; + break; + } + if ((c & 0300) == 0100) { + u = c; + a = (a & ~07777) | (c & 077) << 6; + s = Addr; + break; + } + if ((c & 0307) == 0300) { + a = ((c & 0070) << 9) | (a & 07777); + fprintf(stderr, ": field %o address %06o\n", (c & 070) >> 3, a & 077777); + break; + } + if (c == 0377) { + fprintf(stderr, ": skipping: "); + s = Skip; + break; + } + fprintf(stderr, ": unknown byte at %d\n", n); + break; + case Leader: + leader: + if (c != 0200) { + fprintf(stderr, ": end leader\n"); + s= Fetch; + goto fetch; + } + fprintf(stderr, " %04o", c); + break; + case Skip: + if (c == 0377) { + s = Fetch; + putchar('\n'); + } else { + putchar(c); + } + break; + case Start: + if (c == 0200) { + s = Leader; + goto leader; + } + fprintf(stderr, " %04o", c); + break; + } + } + leadout: + while ((c = getc(fp)) != EOF) + fprintf(stderr, " %04o", c); + putchar('\n'); + return v; +} + +void +dump() +{ + printf("// Generated by p8bin2hex"); + unsigned int nextaddr = -1; + for (uint_fast32_t a = 0; a < 32768; ++a) { + if (mem[a] == 0) continue; + if (nextaddr != a) + printf("\n@%x", (unsigned int)a); + nextaddr = a + 1; + printf(" %03x", mem[a]); + } + printf("\n"); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "usage: p8 filename.bin\n"); + exit(EXIT_FAILURE); + } + FILE *fp = fopen(argv[1], "rb"); + if (!fp) { + perror(argv[1]); + exit(EXIT_FAILURE); + } + if (!load(fp)) + exit(EXIT_FAILURE); + fclose(fp); + dump(); + exit(EXIT_SUCCESS); +} diff --git a/tool/pll.rb b/tool/pll.rb new file mode 100755 index 0000000..49464d4 --- /dev/null +++ b/tool/pll.rb @@ -0,0 +1,23 @@ +#!/usr/bin/ruby -w + +TARGET_FREQ = ARGV.shift.to_f +NATIVE_FREQ = 50.0 + +CLOCK_WIDTH = 5 + +best = nil +best_mult = nil +best_div = nil +1.upto(2**CLOCK_WIDTH) do | mult | + 1.upto(2**CLOCK_WIDTH) do | div | + new = NATIVE_FREQ * mult / div + if not best or (new - TARGET_FREQ).abs < (best - TARGET_FREQ).abs or ((new - TARGET_FREQ).abs == (best - TARGET_FREQ).abs and (mult + div) < (best_mult + best_div)) + best = new + best_mult = mult + best_div = div + end + end +end + +error = (best - TARGET_FREQ).abs / TARGET_FREQ +$stdout.write("Closest I can get is #{best}: *#{best_mult} /#{best_div} (#{(error * 100).round}% error)\n") -- cgit v1.2.3