Permalink
Browse files

initial commit, v1.0

  • Loading branch information...
1 parent 37f16ff commit 7a75df00d9e83ed684f4b03cf2f0f04342734a86 @dsmvwld committed May 29, 2012
Showing with 5,034 additions and 3 deletions.
  1. +97 −3 README.md
  2. +276 −0 asm
  3. +842 −0 camel16.dasm
  4. +2,659 −0 camel16.lst
  5. BIN camel16.o
  6. +156 −0 camel16d.dasm
  7. +1,004 −0 camel16h.dasm
  8. BIN screenshot.png
View
100 README.md
@@ -1,4 +1,98 @@
-CamelForth-16
-=============
+CamelForth for the DCPU-16
+==========================
-CamelForth port for the DCPU-16
+This is CamelForth-16, an implementation of the Forth programming language
+for the fictional Mojang 16-bit DCPU. It is a fully-featured, standard, ANS-compatible
+16-bit implementation of the language, including a glimpse of required 16/32-bit
+mixed arithmetic.
+
+It is a port of the popular CamelForth direct-threaded code (DTC) model,
+derived directly from the existing Z-80 CP/M code by Brad Rodriguez.
+Like CamelForth in general, it is licensed under the GPL.
+
+For details on DCPU-16, see <http://0x10c.com/doc/dcpu-16.txt>,
+but also see the yet-unpublished, semi-official changes at
+<http://pastebin.com/raw.php?i=Q4JvQvnM>. CamelForth-16 is
+written to the latter spec (v1.7),
+and thus requires a recent, up-to-date simulator.
+It was developed using an unofficial, patched version of the "highnerd rc1" simulator,
+but can be run in any recent simulator that supports the required
+devices (generic keyboard and LEM-1802 display), for example, hellige's simulator
+from <https://github.com/hellige/dcpu> with the -g switch (SDL required).
+
+For background on the Mojang DCPU and the 0x10c game,
+check out <http://0x10c.com>.
+
+For details on CamelForth, see <http://www.camelforth.com>.
+For help and tutorials on Forth itself, see <http://www.forth.org>,
+<http://www.forth.com> and <http://en.wikipedia.org/wiki/Forth_(programming_language)>.
+
+
+Quick Start
+-----------
+
+The CamelForth-16 image is supplied as a big-endian image in the file camel16.o.
+The choice for big-endian stems from it being the native format of Notch's simulator
+it was developed in, and it being supported by popular up-to-date simulators such
+as hellige's. Other formats such as little-endian or DAT source will be supplied shortly.
+
+The CamelForth image, camel16.o, can be run from a SDL-enabled hellige
+DCPU simulator directory as follows:
+
+ ./dcpu -g ../path/to/camel16.o
+
+Notice the need to include the -g option, and thus the SDL requirement.
+CamelForth does not yet run in hellige's pure ncurses console environment.
+
+In a setting with an extra SDL window open, remember to keep the input focus
+on the simulator window to get keyboard input to register.
+Also, CamelForth is case-sensitive, but does not fold case on input. Standard
+Forth words have thus to be entered in uppercase.
+
+Try
+ 3 4 + .
+to check out basic arithmetic, or
+ 355 10000 113 */ .
+to try some mixed 16/32-bit arithmetic, delivering an amusing result.
+
+Try
+ WORDS
+to get a listing of the Forth words supported by CamelForth-16.
+Remember to enter it in upper case.
+The WORDS listing can be interrupted by pressing a key anytime.
+
+Check out the commented source listing (camel16.lst) for details.
+
+
+The Assembler
+-------------
+
+CamelForth-16 comes with its own custom DCPU-16 assembler, which is currently
+required to build a CamelForth-16 executable. The assembler is written in Ruby,
+and implements the macros needed to define Forth headers and keep track of link
+fields. It is not a full macro assembler however.
+
+To recompile CamelForth-16 from its three source files, issue
+
+ ./asm camel16.dasm
+
+This will also produce an updated listing in camel16.lst.
+
+
+Thanks
+------
+
+Thanks go out to Brad Rodriguez for a simple yet comprehensive model
+implementation. Check out <http://www.camelforth.com> for other
+implementations on common, real microprocessors. As the saying goes,
+"If you've seen one Forth, you've -- well, seen one Forth," but
+porting CamelForth was exceptionally easy.
+
+Thanks to Markus Persson (@notch) for 0x10c as such, for providing an interesting
+challenge and a moving target that kept things lively this April (2012).
+Check out <http://0x10c.com> for the upcoming game.
+
+Last but not least, thanks to Charles Moore for Forth in general.
+I hadn't touched Forth in many years now, and it all felt familiar (and "just right")
+again. An amazing language and mindset.
+Check out his site <http://www.colorforth.com/index.html>.
View
276 asm
@@ -0,0 +1,276 @@
+#!/usr/bin/env ruby
+# asm.rb -- assembler for Mojang's fictional 1985 DCPU-16
+# hh 04apr12 started
+# hh 06apr12 generate listing and object file, added DAT
+# hh 28apr12 adapted to DCPU 1.5, added INCLUDE, macros
+# hh 03may12 fix INCLUDE listing darkness
+# hh 06may12 fix handling of negative literals
+
+# TODO:
+# do not list INCLUDEs as line 0 of included file
+# do not list data if ORG bumps $pc forward
+
+WORD = 0xffff
+BASIC_OPCODES = %w(- SET ADD SUB MUL MLI DIV DVI MOD MDI AND BOR XOR SHR ASR SHL IFB IFC IFE IFN IFG IFA IFL IFU - - ADX SBX - - STI STD)
+SPECIAL_OPCODES = %w(- JSR - - - - - HCF INT IAG IAS IAP IAQ - - - HWN HWQ HWI)
+REGS = %w(A B C X Y Z I J)
+EXTRAS = %w(- PEEK - SP PC EX)
+
+REG = /[ABCXYZIJ]/
+LABEL = /[A-Za-z$_][A-Za-z0-9$_]*/
+LITERAL = /-?\d{1,5}|0[xX][0-9a-fA-F]{1,4}/
+
+$mem = Array.new(0x1_0000, 0)
+$labels = Hash.new
+$errors = 0
+$link = 0 # macro variable for CamelForth headers
+
+def err(msg)
+ $stderr.puts "#{File.basename($f.last.path)}:#{$f.last.lineno} #{msg}"
+ $errors += 1
+end
+
+# deposit a word in memory, advancing PC
+def comma(w)
+ $mem[$pc & WORD] = (w & WORD)
+ $pc += 1
+end
+
+SOME_VALUE = 040 # not a compact literal
+
+def lookup(label)
+ if $pass == 1
+ $labels.fetch(label, SOME_VALUE)
+ else
+ $labels.fetch(label) {|k| err("undefined label: #{label}"); SOME_VALUE }
+ end
+end
+
+def word(s)
+ s.to_i(0) & WORD # accept 0x
+end
+
+def asm_operand(op)
+ case op
+ when /^#{REG}$/
+ REGS.index(op)
+ when /^\[(#{REG})\]$/
+ 010+REGS.index($1)
+ when /^\[(#{LITERAL})\+(#{REG})\]$/
+ comma(word($1))
+ 020+REGS.index($2)
+ when /^\[(#{LABEL})\+(#{REG})\]$/
+ comma(lookup($1))
+ 020+REGS.index($2)
+ when /^(PUSH|POP)$/
+ 030 # TODO error checking a/b
+ when /^(PEEK|SP|PC|EX)$/
+ 030+EXTRAS.index(op)
+ when /^\[SP\+(#{LITERAL})\]$/ # TODO allow LABEL
+ comma($1)
+ 032
+ when /^\[(#{LITERAL})\]$/
+ comma(word($1))
+ 036
+ when /^\[(#{LABEL})\]$/
+ comma(lookup($1))
+ 036
+ when /^(#{LITERAL})$/
+ lit = word($1)
+ if lit == 0xffff
+ 040
+ elsif (0..30) === lit
+ 040+(lit+1)
+ else
+ comma(lit)
+ 037
+ end
+ when /^(#{LABEL})$/
+ comma(lookup($1)) # TODO use short form if backref
+ 037
+ else
+ raise "bad operand: #{op}"
+ end
+end
+
+def resolve(opd)
+ return case opd
+ when /^#{LITERAL}$/
+ word(opd)
+ when /^#{LABEL}$/
+ lookup(opd.upcase)
+ else
+ err("bad expression: #{opd}") if $pass == 1
+ 0
+ end
+end
+
+def data(opd)
+ case opd
+ when /^"(.*)"$/, /^'(.*)'$/
+ $1.codepoints.each {|u| comma(u) }
+ else
+ comma(resolve(opd))
+ end
+end
+
+def asm_opcode(opc, opds)
+ case opc
+ when 'DAT', 'DW'
+ opds.each {|opd| data(opd) }
+ when 'INCLUDE'
+ $f << File.open(opds.shift+".dasm", "r")
+ when 'HEAD', 'IMMED'
+ label, name, action = opds
+ comma($link)
+ comma(opc == 'IMMED' ? 1 : 0)
+ $labels['$LINK'] = $link = $pc
+ comma(name.length()-2) # sans quotes
+ data(name)
+ deflab(label.upcase, $pc)
+ unless action.upcase == "DOCODE"
+ comma(0x7c20) # JSR addr
+ comma(lookup(action.upcase))
+ end
+ when 'NEXT'
+ comma(0x3401)
+ comma(0x88a2)
+ comma(0x0381)
+ when 'ORG'
+ $list_pc = $pc = resolve(opds.shift)
+ else
+ a = b = 0
+ pc = $pc
+ comma(0) # allocate instruction early on
+ o = if b = SPECIAL_OPCODES.index(opc)
+ a = asm_operand(opds.shift.upcase)
+ 0
+ elsif o = BASIC_OPCODES.index(opc)
+ # processor handles a first, then b
+ bop = opds.shift.upcase
+ a = asm_operand(opds.shift.upcase)
+ b = asm_operand(bop) & 037 # TODO error checking
+ raise "too many operands" unless opds.empty?
+ o
+ else
+ err "bad opcode #{opc}" if $pass == 1
+ return
+ 0
+ end
+ $mem[pc] = (a << 10) | (b << 5) | o
+ end
+end
+
+def tokenize(line)
+ tokens = line.scan(/:[\w$]+|;.*|,|"[^"]*"|'[^']*'|[-\w$\[\]\+]+/)
+ tokens.delete(',')
+ comment = tokens.find_index {|t| t =~ /^;/ }
+ tokens = tokens[0, comment] unless comment.nil?
+ tokens
+end
+
+def deflab(label, value)
+ if $labels.include?(label) && $labels[label] != value
+ err "duplicate definition of #{label}" if $pass == 1
+ else
+ $labels[label] = value
+ end
+end
+
+def asm_line(line)
+ tokens = tokenize(line)
+ return if tokens.empty?
+ while tokens.first.start_with?(':')
+ label = tokens.shift.sub(/^:/, '')
+ if $pass == 1
+ deflab(label.upcase, $pc)
+ else
+ err "phase error" if $pc != $labels[label.upcase]
+ end
+ return if tokens.empty? # allow labels-only lines
+ end
+ opcode = tokens.shift.upcase
+ asm_opcode(opcode, tokens)
+end
+
+def word_if(addr, used)
+ used ? "%04x" % $mem[addr] : ''
+end
+
+def list_line(line, pc)
+ len = $pc - pc
+ $listing.printf("%04x %-4s %-4s %-4s\t%4d %s\n",
+ pc, word_if(pc, len >= 1), word_if(pc+1, len >= 2), word_if(pc+2, len >= 3),
+ $f.last.lineno, line
+ )
+ if len > 3
+ len -= 3
+ while len > 0
+ pc += 3
+ $listing.printf("%04x %-4s %-4s %-4s\t%4d\n",
+ pc, word_if(pc, len >= 1), word_if(pc+1, len >= 2), word_if(pc+2, len >= 3),
+ $f.last.lineno
+ )
+ len -= 3
+ end
+ end
+end
+
+def asm_file(f) # f rewindable
+ $f = Array.new
+ $f << f
+ while !$f.empty?
+ line = $f.last.gets
+ if line
+ line.chomp!
+ $list_pc = $pc
+ asm_line(line)
+ list_line(line, $list_pc) if $pass == 2
+ else
+ $f.pop
+ end
+ end
+end
+
+def dump_syms(f)
+ f.puts "\nSYMBOL TABLE\n\n"
+ $labels.sort.each {|k,v| f.printf("%-10s %04x\n", k, v) }
+end
+
+def save_obj(fn)
+ File.open(fn, "wb") {|f|
+ f.write $mem[0, $pc].pack("n*")
+ }
+end
+
+def asm_fn(ifn, ofn)
+ File.open(ifn, "r") {|f|
+ $pass = 1
+ $pc = $link = 0
+ asm_file(f)
+ f.rewind
+ $pass = 2
+ $pc = $link = 0
+ $listing = File.open(File.basename(ifn, ".dasm") + ".lst", "w")
+ asm_file(f)
+ #dump_syms($listing)
+ $listing.close
+ save_obj(ofn)
+ }
+end
+
+if $0 == __FILE__
+ unless ARGV.length == 1 || ARGV.length == 3
+ $stderr.puts "usage: #{File.basename($0, '.rb')} [-o output.o] input.dasm"
+ exit(1)
+ end
+ ofn = nil
+ if ARGV.first == '-o'
+ ofn = ARGV.shift(2).last
+ end
+ ifn = ARGV.shift
+ ofn = File.basename(ifn, '.dasm') + '.o' if ofn.nil?
+ asm_fn(ifn, ofn)
+ puts "#{$errors} errors" if $errors > 0
+ exit($errors == 0 ? 0 : 1)
+end
Oops, something went wrong.

0 comments on commit 7a75df0

Please sign in to comment.