Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit b56116f9248434ac0608b1ab526b482fa10cbeeb 0 parents
@Textmode authored
9 .gitignore
@@ -0,0 +1,9 @@
+*~
+*.crap
+*.bin
+*.o
+*.tmp
+*.page
+*.seg
+*.luac
+
28 COPYING
@@ -0,0 +1,28 @@
+TMVM is licenced under the Zlib licence, a copy of which is provided below.
+
+-----
+
+Copyright (c) 2011 Textmode (D. M. Brownrigg)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not be
+misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+
+(End of Licence)
+-----
+
97 README
@@ -0,0 +1,97 @@
+Textmode Virtual Macaroni, or TMVM, is a simple, poorly-designed,
+simulation of a register-based system.
+It currently has an addressable memory space of 256 bytes (00..ff)
+(Segments are planned, but not yet implemented). It runs at a nominal
+speed of 1MHz.
+
+Its not presently useful for much of anything, it also likely to be
+unstable and unreliable, so have fun! :D
+
+Note: TMVM is still under development, expect fundamental design and API
+changes.
+
+TMVM is cop
+
+== Requirements
+ Lua 5.1 or better.
+ A sense of humor.
+
+
+
+
+== Usage
+
+=== Machine
+
+machine.lua can be required. asm.lua can be required, or run as script
+(eg. 'lua asm.lua file.asm').
+
+machine exposes the following functions:
+ module:new(opt_name)
+ returns: machine
+ creates a new machine with the given name.
+ if no name is given, a default name will be provided.
+
+
+ (VM):signal(opt_signal)
+ returns: signal, description
+ if given no parms, returns the current signal, and a (short) textual
+ description.
+ otherwise it triggers the given signal.
+ if another signal has yet to be cleared, it will trigger a double
+ fault.
+ if a double-fault is uncleared, a triple-fault.
+ triggering a signal NONE will clear the signal.
+
+
+ (VM):load(opt_start, data_string_or_table)
+ returns: (nothing)
+ attempts to load the given data into the machine's memory.
+ if start is omitted, it will load at address 0, segment 0.
+
+
+ (VM):cycle(opt_num)
+ returns: machine_state
+ completes num instruction cycles. if num is omitted, it will assume
+ 1 cycle.
+
+
+ (VM):run(bool_show_status_dumps) -- Broken!
+ returns: (nothing)
+ attempts to run the machine at its base speed.
+ if passed true, it will call VM:dump() roughly 1/s
+
+
+ (VM):dump()
+ returns: (nothing)
+ prints state information on the current machine
+
+=== ASM
+
+asm.lua can be required normally. additionally, it is a valid script and
+ can be directly run.
+ as a program, asm.lua takes the name of a source file, and optionally the
+ name of an output file. if no output file is given, it outputs to the
+ same filename with the ".crap" replacing its original extension: thus
+ 'test.asm' outputs to 'test.crap'. regardless, it also prints a
+ lua-formatted table of hex digits representing the assembled binary.
+
+asm.lua exposes the following functions:
+
+ asm.load(filename)
+ returns: preparsed_chunk
+ loads the given file and returns a normalised chunk, suitable for
+ further parsing.
+
+
+ asm.scrub(string)
+ returns: preparsed_chunk
+ processes the given string into a pre-parsed chunk, suitable for further
+ parsing.
+
+ asm.parse(chunk)
+ returns: final_bytestring
+ takes a pre-parsed chunk, and returns a fully-assembled chunk as a
+ string, suitable for saving or loading into a machine.
+
+
316 asm.lua
@@ -0,0 +1,316 @@
+local _M = {_NAME="asm", number=0}
+
+local _MT = {__index=_M}
+
+local symbols, len -- needed for parsing
+
+local regnum = {PRM=0x0, A =0x1, B =0x2, ACC=0x3, RET=0x4,
+ SIG=0xd, SEG=0xe, IP =0xf}
+local regs = {'A', 'B', 'ACC', 'RET', 'SEG', 'IP'}
+
+local function reg_encode(a, b)
+ a = a or 'PRM'
+ b = b or 'PRM'
+
+ assert(regnum[a] and regnum[b], "Invalid Register passed to reg_encode")
+ return (regnum[a]*16) + regnum[b]
+end
+
+local function parm(p)
+ assert(p, "Can't check nil parms!")
+ p = string.match(p, "%S+")
+ local form, abs, value = 'unk', not (p:sub(1, 1) == '&'), p
+ p = p:match("&?(.*)")
+
+ for i=1,#regs do
+ form = regs[i] == p and 'register' or form
+ end
+
+ if form ~= 'register' then
+ value = tonumber(p)
+ form = 'literal'
+
+ if value == nil then
+ value = symbols[p]
+ if value then
+ return form, abs, value
+ else
+ --assert(value, string.format("Okay, I give up. whats '%s'?", tostring(p)))
+ form = 'symbol'
+ end
+
+
+ end
+ end
+
+ return form, abs, value
+end
+
+local encoders = {
+ NOP = function(a, b)
+ assert(not (a or b), "NOP has no parms")
+ return string.char(0x00)
+ end;
+ MOV = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+
+ if af == 'register' and bf == 'register' then -- free-register form
+ if aa == true and aa == true then -- pure register form
+ return string.char(0x0f, reg_encode(av, bv))
+ end
+ end
+
+ if af == 'literal' and bf=='register' then
+ assert(bv=="A", "MOV litteral to (not A) not implemented")
+ return string.char(0x05, tonumber(av))
+ end
+ end;
+ ADD = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+ assert(af == 'register' and bf == 'register', "ADD only works with registers")
+ assert(aa and ba, "ADD only works with absolute parms")
+
+ if (av == "A" or av == "B") and (bv == "A" or bv == "B") then
+ return string.char(0x09)
+ else -- free-register form
+ return string.char(0x06, reg_encode(av, bv))
+ end
+ error("unhandled ADD form!")
+ end;
+ DIV = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+ assert(af == 'register' and bf == 'register', "DIV only works with registers")
+ assert(aa and ba, "DIV only works with absolute parms")
+
+ return string.char(0xa1, reg_encode(av, bv))
+ end;
+ SHW = function(a, b)
+ local af, aa, av = parm(a)
+ assert(af=='register', "SHW only presently works for registers")
+
+ if av=='A' then
+ return string.char(0x0a)
+ else
+ return string.char(0xa1, reg_encode(av))
+ end
+
+ end;
+ HLT = function(a, b)
+-- assert(not (a or b), "HLT has no parms")
+ return string.char(0xff)
+ end;
+ SWP = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+ assert(af == 'register' and bf == 'register', "SWP only works with registers")
+
+ if (av == "A" or av == "B") and (bv == "A" or bv == "B") then
+ return string.char(0x08)
+ else -- free-register form
+ return string.char(0x07, reg_encode(av, bv))
+ end
+ error("unhandled SWP form!")
+ end;
+ LES = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+ assert(af == 'register' and bf == 'register', "LES only works with registers")
+
+ if (av == "A") and (bv == "B") then
+ return string.char(0x0d)
+ else -- free-register form
+
+ --return string.char(0x0d)
+ error("LES is only currently defined in the form LES .A,.B")
+ end
+ error("unhandled LES form!")
+ end;
+ EQL = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+ assert(af == 'register' and bf == 'register', "EQL only works with registers")
+
+ if (av == "A") and (bv == "B") then
+ return string.char(0xa0)
+ else -- free-register form
+
+ error("EQL is only currently defined in the form EQL .A,.B")
+ end
+ error("unhandled LES form!")
+ end;
+ GTE = function(a, b)
+ local af, aa, av = parm(a)
+ local bf, ba, bv = parm(b)
+ assert(af == 'register' and bf == 'register', "GRT only works with registers")
+
+ if (av == "A") and (bv == "B") then
+ return string.char(0x08,0x0d,0x08) -- swap, lessthan, swap
+ else -- free-register form
+ error("GRT is only currently defined in the form GRT .A,.B")
+ end
+ error("unhandled GRT form!")
+ end;
+ LBL = function(a, b)
+ assert((a or b), "LBL requires parms")
+
+ if a then symbols[a] = len%256 end
+ if b then symbols[b] = math.floor(len/256) end
+ return ""
+ end;
+ JNZ = function(a, b)
+ assert(a or b, "JNZ requires parms.")
+ assert(not b, "JNZ only accepts one parm.")
+ local af, aa, av = parm(a)
+ assert(af=='literal' or af == 'symbol', "Only constant jump targets supported at this time.")
+
+ if af == 'literal' then
+ return string.char(0x0c, av)
+ elseif af == 'symbol' then
+ return string.char(0x0c, 0x00), true
+ end
+ error("unhandled JNZ form!")
+ end;
+ JMP = function(a, b)
+ assert(a or b, "JMP requires parms.")
+ assert(not b, "JMP only accepts one parm.")
+ local af, aa, av = parm(a)
+ assert(af=='literal' or af == 'symbol', "Only constant jump targets supported at this time.")
+
+ if af == 'literal' then
+ return string.char(0x0b, av)
+ elseif af == 'symbol' then
+ return string.char(0x0b, 0x00), true
+ end
+ error("unhandled JNZ form!")
+ end;
+ LET = function(a, b)
+ assert((a or b), "LBL requires parms")
+
+ symbols[a] = tonumber(b) or 0
+ return ""
+ end;
+}
+
+local function scrub(s)
+ local t = {}
+ s = s..'\n'
+-- s = string.gsub(s, "\n\n", "\n") --in retrospect, keep lines
+
+ for l in string.gmatch(s, "[^\n]*\n") do
+ s = string.match(l, "[^#;]*")
+ if s ~= "\n" and s ~= "" then
+ t[#t+1]= s
+ end
+ end
+
+ return t
+end
+
+_M = scrub
+
+function _M.load(fname)
+ local f, err = io.open(fname)
+ if not f then return nil, err end
+
+ local t = scrub(f:read('*a'))
+ f:close()
+
+ return t
+end
+
+function _M.parse(t)
+ local tos = tostring
+ local c,p = {}, {}
+
+ symbols = {}
+
+ local op, a, b
+ for i=1,#t do
+ op, a = string.match(t[i], "(%u*) *(.*)")
+ --print(a)
+ a, b = string.match(a, "([^%p%s]*)[,%s]?[,%s]?([^%p%s]*)")
+ --print(a, b)
+ if op == "" then op = nil end
+ if a == "" then a = nil end
+ if b == "" then b = nil end
+ c[i]={op=op, a=a, b=b}
+ end
+
+ local bin = {}
+ local op, a, b, r
+ len = 0
+ for i=1,#c do
+ op, a, b = c[i].op,c[i].a,c[i].b
+ if op then
+ assert(encoders[op], ("[line %d: %s %s,%s # Unknown instruction.]"):format(i, op, tos(a), tos(b)))
+ print(string.format("%s %s, %s", tos(op), tos(a), tos(b)))
+ r, patch = encoders[op](a, b)
+ assert(r, ("[line %d: %s %s,%s # No valid reduction.]"):format(i, op, tos(a), tos(b)))
+
+ bin[i] = r or ""
+ len = len + #r
+ --print("YEHAW!", patch)
+ if patch then p[#p+1] = i end
+ end
+ end
+
+ local i
+ for j=1,#p do
+ i = p[j]
+ op, a, b = c[i].op,c[i].a,c[i].b
+ print(string.format("Patching %s %s, %s", tos(op), tos(a), tos(b)))
+ r, patch = encoders[op](a, b)
+ if r then
+ bin[i] = r
+ len = len + #r
+ else
+ bin[i]=""
+ end
+
+ assert(not patch, ("[line %d: %s %s,%s # Failed to patch symbol.]"):format(i, op, tos(a), tos(b)))
+
+ end
+
+ return table.concat(bin)
+end
+
+
+-- possibly clever shit
+if arg and arg[0] then
+ print("Begining")
+
+ math.randomseed(os.time())
+ local function tmpnam() return string.format("x%04x", math.random(0xffff+1)-1) end
+
+ local inf, outf, err = arg[1], arg[2], ""
+ assert(inf, "must specify a file to load")
+ print(string.format("Loading '%s'", inf))
+ local chk = _M.load(inf)
+
+ if not outf then outf = (string.match(inf, "[^%.]*") or tmpnam())..".crap" end
+
+ print(string.format("Writting '%s'", tostring(outf)))
+
+ outf, err = io.open(outf, "w")
+ assert(outf, err)
+ chk = _M.parse(chk)
+ outf:write(chk)
+ outf:close()
+
+ io.write('{')
+ for i=1,#chk do io.write(string.format("0x%02x;", chk:byte(i))) end
+ print('}')
+end
+
+
+-- MODULE TAIL
+-------------------------------------------------------------------------
+
+-- in 5.1.x the custom is to set a global equal to the module name, in all others this ill-behaved.
+if _VERSION == "Lua 5.1" then _G[_M._NAME]=_M end
+
+return _M
+
5 examples/COPYING
@@ -0,0 +1,5 @@
+The Code samples in the examples/ directory (which should be the directory
+ this file is in) are considered minor.
+
+They are hereby provided under CC0 (public domain).
+
35 examples/count10.asm
@@ -0,0 +1,35 @@
+;####################################################
+;# Program to count to ten, using a loop. ##
+;####################################################
+
+; (Re)set the accumulator
+MOV 0,A
+MOV A,ACC
+
+; put 1 in B
+; (since I don't have an increment instruction yet,
+; or a free-register nn mov, we take the scenic route)
+MOV 1,A
+SWP A,B
+
+; put the accumulated value in A, and put A+B in ACC
+MOV ACC,A
+ADD A,B ; result in ACC
+
+; put 10 in b, and acc in A. again, scenic route
+MOV 10,A
+SWP A,B
+MOV ACC,A
+
+; Set RET to 1 if A < B
+LES A,B
+
+; show the result so far with our handy Debugging freind: SHoW
+SHW A
+
+; if the test above passed, Set IP to 4 (MOV 1,A)
+JNZ 4
+
+; Halt the machine, becuase we are selfish bastards who think of no one but ourselves
+HLT
+
26 examples/jump.asm
@@ -0,0 +1,26 @@
+;###################################################
+;## A program to demonstrate jumping to a label ##
+;###################################################
+MOV 42, A
+JMP dest ; Even though the label hasn't been seen yet, it may be referenced
+
+; if the jump fails or misaims, we'll halt here.
+HLT
+HLT
+HLT
+HLT
+HLT
+HLT
+HLT
+HLT
+
+; and heres our label, it sets both the position
+; and segment in seperate symbols. we can ommit the latter if we don't care.
+LBL dest, destseg
+
+; show 42, to prove this worked.
+SHW A
+
+; finally, halt like the selfish bastards we are.
+HLT
+
7 examples/show.asm
@@ -0,0 +1,7 @@
+## Program to show the number 42.
+MOV 42,A ## 052a
+SHW A ## 0a
+HLT ## ff
+
+##0x05, 0x2a, 0x0a, 0xff
+
8 examples/test.asm
@@ -0,0 +1,8 @@
+MOV 0, A
+SWP A, B
+MOV 1, A
+EQL A, B
+MOV RET, A
+SHW A
+HLT
+
333 machine.lua
@@ -0,0 +1,333 @@
+
+local _M = {_NAME="machine", number=0}
+
+local _MT = {__index=_M}
+
+
+-------------------------------------------------------------------------
+-- KEY VALUES
+local maxmem = 256
+
+local HERTZ = 1
+local KILOHERTZ = 1000*HERTZ
+local MEGAHERTZ = 1000*KILOHERTZ
+
+-------------------------------------------------------------------------
+-- SIGNALS
+local SIG_NONE = 0x00 -- Nothing doing.
+local SIG_DIV0 = 0x01 -- Divide by zero
+local SIG_ILLEGAL_INSTRUCTION = 0x02 -- Illegal Instruction (aka, unknown opcode)
+local SIG_DOUBLE_FAULT = 0x0f -- Double-fault (eg, fault before clearing a fault)
+local SIG_TRIPLE_FAULT = 0xff -- Triple (halting) fault.
+
+signals = {
+ [SIG_NONE] = "Signal Normal";
+ [SIG_DIV0] = "Divide by zero";
+ [SIG_DOUBLE_FAULT] = "Double Fault";
+ [SIG_TRIPLE_FAULT] = "Triple Fault (halting fault)";
+ }
+
+-------------------------------------------------------------------------
+-- MISC
+local cr_tbl = {[0x0]='PRM', [0x1]='A', [0x2]='B', [0x3]='ACC', [0x4]='RET',
+ [0xd]='SIG', [0xe]='SEG', [0xf]='IP' }
+
+
+-------------------------------------------------------------------------
+-- private helper functions
+
+-- printf, as per C
+local function printf(...)
+ print(string.format(...))
+end
+
+-- a variant of io.write that include printf-like formating.
+local function writef(...)
+ io.write(string.format(...))
+end
+
+-- takes a formatted free-register byte,and returns a pair of register names
+local function convreg(n)
+ local mf=math.floor
+ local a, b = mf(n/16), mf(n%16)
+ a, b =cr_tbl[a], cr_tbl[b]
+ assert(a, ("Illegal High Register in byte: %02x"):format(n))
+ assert(b, ("Illegal Low Register in byte: %02x"):format(n))
+
+ return a, b
+end
+
+-- a probably flawed rounding function.
+local function round(n)
+ if x > 0 then return math.floor(n)
+ else return math.ciel(n) end
+end
+
+-- converts a string of bytes into the standard bytetables used elsewhere
+local function stringtodata(str)
+ local t = {}
+ for i=1,#str do
+ t[i] = str:sub(i, i):byte()
+ end
+
+ return t
+end
+
+-- a very coarse and generally poor wait/"sleep" function.
+local function wait(n)
+ local t, nt, dt = os.time(), nil, 0
+ repeat
+ nt = os.time()
+ dt = os.difftime(t, nt)
+ until dt >= n
+ return dt
+end
+
+-------------------------------------------------------------------------
+-- Instruction set
+--
+-- the long table of opcode-function pairs that drive this malformed beast.
+_M.iset = {
+ [0x00]=function(self) -- NOP # No OPeration, do nothing, etc.
+ return 1;
+ end;
+ [0x02]=function(self) -- MOV &R:&R # indirect free-register move
+ local a, b = convreg(self.memory[self.IP+1])
+ self.memory[self[b]] = self.memory[self[a]]
+ return 2;
+ end;
+ [0x03]=function(self) -- MOV .A:&nn # Move A to addresss.
+ local point = self.memory[self.IP+1]
+ self.memory[point] = self.A
+ return 2;
+ end;
+ [0x04]=function(self) -- MOV &nn:.A # Put address contents into A
+ local point = self.memory[self.IP+1]
+ self.A = self.memory[point]
+ return 2;
+ end;
+ [0x05]=function(self) -- MOV nn:.A # put literal into A
+ local point = self.memory[self.IP+1]
+ self.A = point
+ return 2;
+ end;
+ [0x06]=function(self) -- ADD R:R -> ACC # free-register ADD, results in ACC
+ local a, b = convreg(self.memory[self.IP+1])
+ self.ACC = self[a] + self[b]
+ return 2;
+ end;
+ [0x07]=function(self) -- SWP R:R # Free-register swap
+ local a, b = convreg(self.memory[self.IP+1])
+ self[a], self[b] = self[b], self[a]
+ return 2;
+ end;
+ [0x08]=function(self) -- SWP .A:.B (or SWP .B:.A ...) # fixed register AB swap
+ self.A, self.B = self.B, self.A
+ return 1;
+ end;
+ [0x09]=function(self) -- ADD .A:.B -> ACC # fixed-register AB ADD, results in ACC
+ self.ACC = self.A + self.B
+ return 1;
+ end;
+ [0x0a]=function(self) -- SHW .A # fixed register Show A
+ print("A: "..self.A)
+ return 1;
+ end;
+ [0x0b]=function(self) -- JMP nn (or if you prefer, MOV nn:.IP) unconditional jump with literal address
+ self.IP = self.memory[self.memory[self.IP+1]]
+ return 0;
+ end;
+ [0x0c]=function(self) -- JNZ RET, nn (or MNZ RET, nn:.IP) # fixed-register RET conditional jump with literal address
+ if self.RET ~= 0 then
+ self.IP = self.memory[self.IP+1]
+ return 0
+ end
+ return 2;
+ end;
+ [0x0d]=function(self) -- LES .A:.B -> RET # Fixed-register AB less-than, results in RET
+ self.RET = ({[true]=1,[false]=0})[self.A < self.B]
+ return 1;
+ end;
+ [0x0e]=function(self) -- MNZ RET, R, &nn # free-register conditional move to literal address
+ local a, b = convreg(self.memory[self.IP+1])
+
+ if a then
+ self.memory[self.memory[self.IP+1]] = self[b]
+ end
+ return 3;
+ end;
+ [0x0f]=function(self) -- MOV R:R # free-register move
+ local a, b = convreg(self.memory[self.IP+1])
+ self[b] = self[a]
+ return 2;
+ end;
+ [0xa0]=function(self) -- EQL .A:.B -> RET # fixed-register AB equals, results in RET
+ self.RET = ({[true]=1,[false]=0})[self.A == self.B]
+ return 1;
+ end;
+ [0xa1]=function(self) -- SHW R # Free-register show
+ local a, b = convreg(self.memory[self.IP+1])
+ print(a..": "..self[a])
+ return 2;
+ end;
+ [0xa2]=function(self) -- DIV R, R # Free-register divide
+ local a, b = convreg(self.memory[self.IP+1])
+ if self.b == 0 then self:signal(SIG_DIV0) end
+ self.RET = round((self[a] / self[b])%256)
+ return 2;
+ end;
+
+ [0xff]=function(self) -- HLT # halts the machine
+ self.state = 'halt';
+ return 0;
+ end;
+
+ }
+
+_M.speed = 1*MEGAHERTZ -- set he default speed.
+
+-----
+-- With parm it returns the current SIGnal
+-- given SIG_NONE it clears the current signal
+-- given any other it acts according to the signal rules as follows:
+-- if the current signal is NONE, set the given signal,
+-- if the given signal is NONE, set the signal to NONE (ie, clear the signal)
+-- if the current signal is nither NONE, nor DOUBLE_FAULT, signal DOUBLE_FAULT and LJMP 0,0 (aka, reset)
+-- if the current signal is DOUBLE_FAULT, signal a TRIPLE_FAULT, and halt the machine.
+function _M:signal(sig)
+ if sig == nil then return self.SIG, signals[self.SIG] end
+
+ if sig == SIG_NONE then
+ self.SIG = SIG_NONE
+ elseif self.SIG == SIG_DOUBLE then
+ self.SIG = SIG_TRIPLE_FAULT
+ self.state = 'halt'
+ elseif self.SIG ~= SIG_NONE then
+ self.SIG = SIG_DOUBLE_FAULT
+ self.IP = 0
+ self.SEG = 0
+ else
+ self.SIG = sig
+ end
+
+ return self.SIG
+end
+
+-----
+-- Creates a new machine with the given name.
+-- if no name is given, a default name is chosen
+function _M:new(name)
+
+ _M.number = _M.number+1
+
+ local m = {name=name or ("Machine_%02x"):format(_M.number), time = 0,speed=_M.speed, memory={};
+ IP=0, -- Instruction Pointer
+ SIG=0, -- SIGnal register
+ SEG=0, -- SEGment Pointer
+ A=0, -- Register A
+ B=0, -- Register B
+ ACC=0, -- ACCumulator
+ RET=0 -- RETurn, or result
+ }
+ setmetatable(m, _MT)
+
+ for i=0,maxmem do
+ m.memory[i] = 0--math.random(256)-1
+ end
+
+ return m
+end
+
+-----
+-- Loads the given chunk or bytestring into memory.
+-- as one parm (data) it load the data into 0:0
+-- as two parms (start, data) it loads the data into the given address
+function _M:load(start, data)
+ if data == nil then data, start = start, 0 end
+-- assert(type(data)~="string", "Support for loading strings is not yet implemented")
+ assert(type(data)=='table' or type(data)=='string', "Invalid loadable data parm")
+
+ if type(data)=='string' then data=stringtodata(data) end
+
+ for i=0, #data-1 do
+ assert(type(data[i+1]) == 'number' and data[i+1] == data[i+1]%256,
+ "data can only contain numerical data in the range 0..255")
+ self.memory[i+start] = data[i+1]
+ end
+end
+
+-----
+-- Executes the given number of cycles.
+-- if no number if given, it executes a single cycle.
+function _M:cycle(n)
+ local n = n or 1
+
+ local ins, adv
+ for i=1,n do
+ ins = self.memory[self.IP]
+ assert(self.iset[ins], ("Invalid instruction at address 0x%02x : %02x"):format(self.IP, ins))
+
+ self.IP = self.iset[ins](self) + self.IP -- order is important here.
+ self.IP = self.IP % 256
+ self.time = self.time+1
+
+ if self.state == 'halt' then return 'halt' end
+ end
+ return self.state
+end
+
+-----
+-- BROKEN!
+-- causes the machine to run at its basic speed until halted
+-- if given a parm that evaluates as true, it will display a status-dump
+-- approximately every second.
+function _M:run(stats)
+ stats = stats or false
+ local time, ntime, dt = os.time(), nil, nil
+
+ while not self.state == 'halt' do
+ self:cycle(self.speed)
+
+ if stats then self:dump() end
+
+ ntime = os.time()
+ dt = os.difftime(time, ntime)
+ if dt < 1 then wait(1)
+ elseif dt > 1 then print("woops, too slow!")
+ end
+
+ print(self.time)
+ time = ntime
+ end
+end
+
+-----
+-- Dumps the status of the machine in a (hopefully) readable format.
+-- intended for debugging.
+function _M:dump()
+ printf("%s, %d ticks", self.name, self.time)
+ printf("IP:%02x\tACC:%02x\tRET:%02x\tA:%02x\tB:%02x", self.IP, self.ACC, self.RET, self.A, self.B)
+ print()
+
+ local ram = self.memory
+ for i=0,255, 16 do
+ for j=0,15 do
+ writef("%02x ", ram[i+j])
+ end
+ print()
+ end
+
+end
+
+-- set the default call for a machine to be Cycle
+_MT.__call = _M.run
+
+
+-------------------------------------------------------------------------
+-- MODULE TAIL
+
+-- in 5.1.x the custom is to set a global equal to the module name, in all others this ill-behaved.
+if _VERSION == "Lua 5.1" then _G[_M._NAME]=_M end
+
+return _M
+
Please sign in to comment.
Something went wrong with that request. Please try again.