├── .gitignore ├── LICENSE ├── device.lua ├── README.md ├── main.lua ├── love-devices.lua ├── uxn.lua └── instruction_tests.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.rom 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Original Uxn code Copyright (c) Devine Lu Linvega 4 | Lua port Copyright 2021 Aldous Rice-Leech 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /device.lua: -------------------------------------------------------------------------------- 1 | local Device = {} 2 | 3 | Device.DEBUG_NUM_CALLS = { read = {}, write = {}} 4 | 5 | Device.__index = function(self, k) 6 | if type(k) == "number" then 7 | Device.DEBUG_NUM_CALLS.read[self.device_num or self] = (Device.DEBUG_NUM_CALLS.read[self.device_num or self] or 0) + 1 8 | 9 | local value = self.portdata[k] or 0 10 | local port = self.ports[k] 11 | if port then 12 | if port.onread then 13 | value = port.onread(self) 14 | if port.byte == "high" then 15 | value = bit.rshift(value, 8) 16 | end 17 | end 18 | end 19 | return bit.band(value, 0xff) 20 | elseif Device[k] then 21 | return Device[k] 22 | end 23 | end 24 | 25 | function Device:readShort(port) 26 | return bit.lshift(self[port], 8) + self[port+1] 27 | end 28 | 29 | function Device:writeShort(port, short) 30 | -- Write the low byte first since onwrite only triggers on the high byte 31 | self[port+1] = bit.band(short, 0xff) 32 | self[port] = bit.band(bit.rshift(short, 8), 0xff) 33 | end 34 | 35 | Device.__newindex = function(self, k, v) 36 | if type(k) == "number" then 37 | --Device.DEBUG_NUM_CALLS.write[self.device_num or self] = (Device.DEBUG_NUM_CALLS.write[self.device_num or self] or 0) + 1 38 | self.portdata[k] = v 39 | local port = self.ports[k] 40 | if port then 41 | -- Ignore write triggers for the low byte of a short 42 | if port.onwrite and port.byte ~= "low" then 43 | return port.onwrite(self, v) 44 | end 45 | end 46 | -- If there's no handlers attached to this port, just store the value 47 | if not (port and (port.onwrite or port.onread)) then 48 | -- Cache the result 49 | rawset(self, k, v) 50 | end 51 | else 52 | return rawset(self, k, v) 53 | end 54 | end 55 | 56 | function Device:new(devicenum) 57 | local device = setmetatable({ 58 | ports = {}, 59 | portdata = {}, 60 | }, self) 61 | 62 | -- Add vector port by default 63 | device:addPort(0, true, false, function(self, value) 64 | self.cpu.vectors[self.device_num] = self:readShort(0) 65 | end) 66 | 67 | return device 68 | end 69 | 70 | function Device:addPort(num, short, read, write) 71 | if short then 72 | self.ports[num] = {byte="high", onread=read, onwrite=write} 73 | self.ports[num+1] = {byte="low", onread=read, onwrite=write} 74 | else 75 | self.ports[num] = {byte="single", onread=read, onwrite=write} 76 | end 77 | end 78 | 79 | function Device:trigger() 80 | self.cpu:triggerDevice(self.device_num) 81 | end 82 | 83 | return Device 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ! This repo is very out of date and I have not kept it up to date with any changes to the spec since January 2022 ! 2 | I have lost interest in this project and am unlikely to update it any time soon. The instruction set itself has changed and I believe there have been lots of changes to various devices in the interim. ROMs compiled since January of 2022 may not run in this emulator 3 | 4 | # Uxn-Lua 5 | 6 | A port of the [Uxn](https://wiki.xxiivv.com/site/uxntal.html) instruction set and [Varvara](https://wiki.xxiivv.com/site/varvara.html) virtual computer to the Lua language and the Love2D game engine. ~~Is compatible with most ROMs, but runs at a much lower speed for cpu-intensive ROMs.~~ 7 | 8 | ## Installation 9 | 10 | Running the unit tests requires [Busted](https://olivinelabs.com/busted/) 11 | 12 | Running this repository as a Love game requires [Love 11+](https://love2d.org/) 13 | 14 | Running uxn.lua as a standalone requires [LuaJIT](https://luajit.org/) or a bitwise operations libray such as the standalone [bitwise module](https://bitop.luajit.org/) 15 | 16 | ## Quickstart 17 | 18 | ### As a Love game 19 | 20 | `love .` 21 | 22 | Will try to load a file called `boot.rom` in the top-level directory. 23 | 24 | `love . somename.rom` 25 | 26 | Will try to load a file called `somename.rom` in the top-level directory. 27 | 28 | ### Standalone use of the cpu 29 | 30 | ```lua 31 | Uxn = require "uxn" 32 | cpu = Uxn.Uxn:new() 33 | 34 | -- Start the instruction pointer at the initial location 35 | cpu.ip = 0x0100 36 | 37 | -- Load some bytes into memory 38 | values = { 39 | 0x80, 0x13, -- LIT 0x13 40 | 0x01, -- INC 41 | 0x80, 0x37, -- LIT 0x37 42 | 43 | 0x18, -- ADD 44 | 45 | 0x00, -- BREAK 46 | } 47 | 48 | -- Copy the values offset by 255 so they start at the location of IP 49 | for i = 1, #values do 50 | cpu.memory[i + 255] = values[i] 51 | end 52 | 53 | cpu:runUntilBreak() 54 | 55 | -- Should print 75 or 0x4b 56 | print(cpu.program_stack:pop()) 57 | 58 | ``` 59 | 60 | ## CPU 61 | 62 | `Uxn:new([memory])` 63 | 64 | Optionally pass in a table that holds the contents of memory. Remember that this table is will be accessed starting at 0 unlike most Lua tables. 65 | 66 | `Uxn:runUntilBreak()` 67 | 68 | Run the CPU until it hits a BRK instruction (0x00). 69 | 70 | `Uxn:addDevice(num, device)` 71 | 72 | Adds a [Device] to the specified device number. See the Varvara docs for standard device numbers. 73 | 74 | ## Devices 75 | 76 | The device object stores 16 bytes of memory as well as providing callbacks to trigger when different ports are written to 77 | 78 | `Device:addPort(port, length, onRead, onWrite)` 79 | 80 | `Device:readShort(addr)` 81 | 82 | `Device:writeShort(addr, value)` 83 | 84 | `Device:trigger` 85 | 86 | Jumps to the vector for this device and runs until it reaches a BRK instruction. 87 | 88 | 89 | ### Examples 90 | 91 | Create a device that prints to the console every time port 0x08 is written to. 92 | 93 | ```lua 94 | local device = Device:new() 95 | 96 | -- Add a byte port at 0x08 97 | device:addPort(0x08, false, nil, function(byte) 98 | print("wrote a byte", byte, "to port 0x08") 99 | end) 100 | ``` 101 | 102 | Attach a device to a cpu 103 | 104 | ```lua 105 | local cpu = Uxn:new() 106 | 107 | cpu:addDevice(num, device) 108 | ``` 109 | 110 | ## TODO 111 | 112 | - [ ] Add file writing 113 | - [ ] Audo 114 | - [ ] Implement debug properties of System device 115 | - [ ] Add datetime device 116 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | local Uxn = (require "uxn").Uxn 2 | bit = require "bit" 3 | 4 | local band, bor, bxor, bnot = bit.band, bit.bor, bit.bxor, bit.bnot 5 | local arshift, rshift, lshift = bit.arshift, bit.rshift, bit.lshift 6 | 7 | local math = require "math" 8 | local devices = require "love-devices" 9 | 10 | Device = require "device" 11 | 12 | -- Debugging 13 | --jit.off() 14 | PROFILE = nil 15 | 16 | -- Screen parameters 17 | WIDTH = 300 18 | HEIGHT = 200 19 | PADDING = 4 20 | SCALING = 2 21 | 22 | function setupCPU(mem) 23 | local cpu = Uxn:new(mem) 24 | cpu.ip = 0x0100 25 | 26 | -- Debugging flags 27 | cpu.PRINT = false 28 | cpu.memory.ERROR_ON_UNINITIALIZED_READ = false 29 | 30 | system = cpu:addDevice(0, devices.system) 31 | console = cpu:addDevice(1, devices.console) 32 | screen = devices.screen(WIDTH, HEIGHT) 33 | cpu:addDevice(2, screen) 34 | 35 | controller = cpu:addDevice(8, devices.controller) 36 | mouse = cpu:addDevice(9, devices.mouse) 37 | file = cpu:addDevice(10, devices.file) 38 | cpu:addDevice(11, devices.datetime) 39 | return cpu 40 | end 41 | 42 | function love.load(arg) 43 | love.mouse.setVisible(false) 44 | love.graphics.setDefaultFilter("nearest", "nearest") 45 | 46 | --love.graphics.setNewFont("mono.ttf", 14) 47 | love.graphics.setBackgroundColor(0,0,0) 48 | love.window.setMode((WIDTH * SCALING) + (PADDING * 2), (HEIGHT * SCALING) + (PADDING * 2)) 49 | 50 | love.keyboard.setKeyRepeat(true) 51 | 52 | -- This is the shader that translates system colours into 53 | -- palette colours 54 | paletteShader = love.graphics.newShader [[ 55 | uniform vec3 palette[4]; 56 | 57 | vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords ) { 58 | vec4 pixel = Texel(tex, texture_coords); 59 | int index = int(pixel.r * 4.0 + 0.5); 60 | pixel.rgb = palette[index]; 61 | 62 | return pixel * color; 63 | } 64 | ]] 65 | 66 | 67 | local memory = {} 68 | 69 | -- Preload the zero-page 70 | for i = 0, 255 do 71 | memory[i] = 0 72 | end 73 | 74 | -- Takes in a filename from the command line, else load boot.rom 75 | local data, size = love.filesystem.read("data", arg[1] or "boot.rom") 76 | 77 | for i = 1, size do 78 | memory[i + 255] = love.data.unpack("B", data, i) 79 | end 80 | 81 | -- Create a new CPU 82 | cpu = setupCPU(memory) 83 | 84 | -- Execute the initial vector 85 | cpu:runUntilBreak() 86 | 87 | if PROFILE then 88 | Device.DEBUG_NUM_CALLS.read = {} 89 | Device.DEBUG_NUM_CALLS.write = {} 90 | cpu.debug_profile = {} 91 | cpu.device_triggers = {} 92 | cpu.device_reads = {} 93 | cpu.device_writes = {} 94 | 95 | love.profiler = require('cigumo_profile') 96 | love.profiler.start() 97 | end 98 | end 99 | 100 | local frame = 0 101 | function love.draw() 102 | frame = frame + 1 103 | 104 | -- Run the screen vector 105 | screen:trigger() 106 | 107 | if frame == PROFILE then 108 | love.profiler.stop() 109 | print(love.profiler.report()) 110 | print("device", "num_triggers") 111 | for k, v in pairs(cpu.device_triggers) do 112 | print(k, v) 113 | end 114 | 115 | print("device", "cpu_reads") 116 | for k, v in pairs(cpu.device_reads) do 117 | print(k, v) 118 | end 119 | 120 | print("device", "cpu_writes") 121 | for k, v in pairs(cpu.device_writes) do 122 | print(k, v) 123 | end 124 | 125 | print("device", "total Device reads") 126 | for k, v in pairs(Device.DEBUG_NUM_CALLS.read) do 127 | print(k, v) 128 | end 129 | 130 | print("device, total Device writes") 131 | for k, v in pairs(Device.DEBUG_NUM_CALLS.write) do 132 | print(k, v) 133 | end 134 | 135 | print(cpu:print_profile()) 136 | end 137 | 138 | love.graphics.push() 139 | 140 | love.graphics.translate(PADDING, PADDING) 141 | love.graphics.scale(SCALING, SCALING) 142 | love.graphics.setColor(1,1,1) 143 | 144 | love.graphics.setBlendMode("replace", "premultiplied") 145 | love.graphics.setShader(paletteShader) 146 | 147 | love.graphics.draw(screen.back) 148 | 149 | love.graphics.setBlendMode("alpha") 150 | love.graphics.draw(screen.front) 151 | 152 | love.graphics.setShader() 153 | 154 | love.graphics.pop() 155 | 156 | local dbg = "x: "..screen:readShort(8).." y: "..screen:readShort(10).." " 157 | dbg = dbg.."PS: "..table.concat(cpu.program_stack, " ").." " 158 | dbg = dbg.."frame: "..frame.." fps: "..love.timer.getFPS() 159 | love.graphics.setColor(1,0,0) 160 | --love.graphics.print(dbg, 10, 300) 161 | end 162 | 163 | -- Take in a love keyconstant and return which bit in the controller byte 164 | keyToBit = { 165 | ["rctrl"] = 0, ["lctrl"] = 0, 166 | ["ralt"] = 2, ["lalt"] = 2, 167 | ["rshift"] = 4, ["lshift"] = 4, 168 | ["escape"] = 8, 169 | ["up"] = 16, 170 | ["down"] = 32, 171 | ["left"] = 64, 172 | ["right"] = 128, 173 | } 174 | 175 | function love.textinput(text) 176 | controller[3] = string.byte(string.sub(text,1,1)) 177 | 178 | controller:trigger() 179 | end 180 | 181 | function love.keypressed(key) 182 | controller[2] = bor(controller[2], keyToBit[key] or 0) 183 | 184 | local ascii = 0 185 | 186 | if key == "backspace" then 187 | ascii = 0x08 188 | elseif key == "return" then 189 | ascii = 0x0d 190 | elseif key == "tab" then 191 | ascii = 0x09 192 | elseif key == "delete" then 193 | ascii = 0x7f 194 | end 195 | 196 | controller[3] = ascii 197 | 198 | controller:trigger() 199 | end 200 | 201 | function love.keyreleased(key) 202 | controller[2] = band(controller[2], bnot(keyToBit[key] or 0)) 203 | controller[3] = 0 204 | 205 | controller:trigger() 206 | end 207 | 208 | --[[ TODO 209 | function love.filedropped(file) 210 | -- TODO: Reset devices 211 | cpu = setupCPU() 212 | 213 | file:open("r") 214 | 215 | local data = file:read("data") 216 | 217 | data = love.data.encode("string", "hex", data) 218 | 219 | cpu.memory.hex_rom = data 220 | 221 | cpu:runUntilBreak() 222 | end 223 | ]]-- 224 | 225 | function normalizeMouse(x, y) 226 | -- Move to the center to offset math.floor 227 | x = x + 0.5 228 | y = y + 0.5 229 | 230 | x = x - PADDING 231 | y = y - PADDING 232 | 233 | x = x / SCALING 234 | y = y / SCALING 235 | 236 | return math.floor(x), math.floor(y) 237 | end 238 | 239 | local old_x, old_y 240 | 241 | function love.mousemoved(x, y) 242 | x, y = normalizeMouse(x, y) 243 | if x ~= old_x or y ~= old_y then 244 | 245 | mouse:writeShort(2, x) 246 | mouse:writeShort(4, y) 247 | 248 | old_x = x 249 | old_y = y 250 | 251 | mouse:trigger() 252 | end 253 | end 254 | 255 | function love.mousepressed(x, y, button) 256 | x, y = normalizeMouse(x, y) 257 | 258 | mouse:writeShort(2, x) 259 | mouse:writeShort(4, y) 260 | 261 | mouse[6] = bor(mouse[6], button == 1 and 0x01 or 0x10) 262 | 263 | mouse:trigger() 264 | end 265 | 266 | function love.mousereleased(x, y, button) 267 | x, y = normalizeMouse(x, y) 268 | 269 | mouse:writeShort(2, x) 270 | mouse:writeShort(4, y) 271 | 272 | mouse[6] = band(mouse[6], button == 1 and 0x10 or 0x01) 273 | 274 | mouse:trigger() 275 | end 276 | 277 | function love.wheelmoved(_, y) 278 | mouse[7] = y > 0 and 1 or -1 279 | 280 | mouse:trigger() 281 | end 282 | 283 | function love.update(dt) 284 | mouse[7] = 0 285 | end 286 | -------------------------------------------------------------------------------- /love-devices.lua: -------------------------------------------------------------------------------- 1 | local bit = require "bit" 2 | 3 | local band, bor, bxor, bnot = bit.band, bit.bor, bit.bxor, bit.bnot 4 | local arshift, rshift, lshift = bit.arshift, bit.rshift, bit.lshift 5 | 6 | local Device = require "device" 7 | 8 | local devices = {} 9 | 10 | local system = Device:new() 11 | 12 | system.name = "system" 13 | 14 | -- Default palette of black, Red, Green, and Blue 15 | system.palette = { 16 | [0]={0,0,0}, 17 | {1,0,0}, 18 | {0,1,0}, 19 | {0,0,1}, 20 | } 21 | 22 | function regeneratePalette(system) 23 | local r,g,b = system:readShort(8), system:readShort(10), system:readShort(12) 24 | r,g,b = bit.tohex(r, 4), bit.tohex(g, 4), bit.tohex(b, 4) 25 | 26 | for i = 0, 3 do 27 | -- Grab the hex digit for each colour channel 28 | colour = { 29 | string.sub(r, i+1, i+1), 30 | string.sub(g, i+1, i+1), 31 | string.sub(b, i+1, i+1), 32 | } 33 | 34 | colour = { 35 | tonumber(colour[1], 16) / 0xf, 36 | tonumber(colour[2], 16) / 0xf, 37 | tonumber(colour[3], 16) / 0xf, 38 | } 39 | 40 | system.palette[i] = colour 41 | 42 | end 43 | 44 | paletteShader:send("palette", 45 | system.palette[0], 46 | system.palette[1], 47 | system.palette[2], 48 | system.palette[3]) 49 | 50 | --love.graphics.setBackgroundColor(system.palette[0]) 51 | end 52 | 53 | system.initColours = false 54 | 55 | system:addPort(0x02, false, function(self) 56 | return self.cpu.program_stack:len() 57 | end) 58 | 59 | system:addPort(0x03, false, function(self) 60 | return self.cpu.return_stack:len() 61 | end) 62 | 63 | system:addPort(0x08, true, nil, regeneratePalette) 64 | system:addPort(0x0a, true, nil, regeneratePalette) 65 | system:addPort(0x0c, true, nil, regeneratePalette) 66 | 67 | system:addPort(0x0f, false, nil, function() 68 | error("halt") 69 | end) 70 | 71 | -- portnum, short, read, write 72 | 73 | local console = Device:new() 74 | console.name = "console" 75 | console.stdout = "" 76 | console.stderr = "" 77 | 78 | -- read char 79 | console:addPort(2, false) 80 | -- write char 81 | console:addPort(8, false, nil, function(self, byte) 82 | io.write(string.char(byte)) 83 | self.stdout = self.stdout..string.char(byte) 84 | end) 85 | -- error char 86 | console:addPort(9, false, nil, function(self, byte) 87 | self.stderr = self.stderr..string.char(byte) 88 | end) 89 | 90 | local screen = function(width, height) 91 | local screen = Device:new() 92 | 93 | screen.name = "screen" 94 | 95 | screen.back = love.graphics.newCanvas(width, height) 96 | screen.front = love.graphics.newCanvas(width, height) 97 | 98 | love.graphics.setCanvas(back) 99 | -- System colour 0 at full alpha 100 | love.graphics.clear(0,0,0,1) 101 | love.graphics.setCanvas(front) 102 | -- System colour 0 (irrelevant) with alpha 103 | love.graphics.clear(0,0,0,0) 104 | love.graphics.setCanvas() 105 | 106 | screen.back:setFilter("nearest", "nearest") 107 | screen.front:setFilter("nearest", "nearest") 108 | 109 | 110 | -- TODO: Resize the screen and canvases 111 | -- For now, don't allow the Uxn cpu to overwrite this valu 112 | 113 | -- Width 114 | screen:addPort(2, true, function(self) return width end) 115 | 116 | -- Height 117 | screen:addPort(4, true, function(self) return height end) 118 | 119 | -- Auto 120 | screen:addPort(6, false, nil, function(self, byte) 121 | self.auto_x = band(byte, 0x01) ~= 0 122 | self.auto_y = band(byte, 0x02) ~= 0 123 | self.auto_addr = band(byte, 0x04) ~= 0 124 | end) 125 | 126 | -- X 127 | screen:addPort(8, true) 128 | 129 | -- Y 130 | screen:addPort(10, true) 131 | 132 | -- Sprite address 133 | screen:addPort(12, true) 134 | 135 | -- Write a single pixel 136 | screen:addPort(14, false, nil, function(self, pixel) 137 | local layer = band(pixel, 0x40) == 0 and self.back or self.front 138 | local index = band(pixel, 0x03) 139 | 140 | local alpha = 1.0 141 | if index == 0 and layer == self.front then 142 | -- Transparency 143 | alpha = 0.0 144 | end 145 | love.graphics.setBlendMode("replace", "premultiplied") 146 | love.graphics.setCanvas(layer) 147 | 148 | love.graphics.setColor(index / 4.0, 0, 0, alpha) 149 | 150 | local x, y = self:readShort(8), self:readShort(10) 151 | -- Offset to account for points grid 152 | love.graphics.points(x + 0.5, y + 0.5) 153 | 154 | love.graphics.setCanvas() 155 | 156 | if self.auto_x then 157 | x = x + 1 158 | self:writeShort(8, x) 159 | end 160 | 161 | if self.auto_y then 162 | y = y + 1 163 | self:writeShort(10, y) 164 | end 165 | end) 166 | 167 | -- Thank you to Sejo @ https://compudanzas.net for writing out these tables! 168 | 169 | ONE_BPP_PALETTE = { 170 | [0] = {0, 0}, 171 | {0, 1}, 172 | {0, 2}, 173 | {0, 3}, 174 | {1, 0}, 175 | {"none", 1}, 176 | {1, 2}, 177 | {1, 3}, 178 | {2, 0}, 179 | {2, 1}, 180 | {"none", 2}, 181 | {2, 3}, 182 | {3, 0}, 183 | {3, 1}, 184 | {3, 2}, 185 | {"none", 3}, 186 | } 187 | 188 | TWO_BPP_PALETTE = { 189 | [0] = {0,0,1,2}, 190 | {0,1,2,3}, 191 | {0,2,3,1}, 192 | {0,3,1,2}, 193 | {1,0,1,2}, 194 | {"none",1,2,3}, 195 | {1,2,3,1}, 196 | {1,3,1,2}, 197 | {2,0,1,2}, 198 | {2,1,2,3}, 199 | {"none",2,3,1}, 200 | {2,3,1,2}, 201 | {3,0,1,2}, 202 | {3,1,2,3}, 203 | {3,2,3,1}, 204 | {"none",3,1,2} 205 | } 206 | 207 | function getBit(val, n) 208 | return rshift(band(val, lshift(1, n)), n) 209 | end 210 | 211 | -- Draw a sprite 212 | screen:addPort(15, false, nil, function(self, spriteByte) 213 | -- 1bpp = 0 / 2bpp = 1 214 | local spriteMode = getBit(spriteByte, 7) 215 | 216 | local layer = band(spriteByte, 0x40) == 0 and self.back or self.front 217 | 218 | local verticalFlip = band(spriteByte, 0x20) ~= 0 219 | local horizontalFlip = band(spriteByte, 0x10) ~= 0 220 | 221 | local x, y = self:readShort(0x08), self:readShort(0x0a) 222 | local spriteAddr = self:readShort(0x0c) 223 | 224 | local system_palette = {[0] = {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}} 225 | 226 | local palette 227 | if spriteMode == 0 then 228 | palette = ONE_BPP_PALETTE[band(spriteByte, 0x0f)] 229 | else 230 | palette = TWO_BPP_PALETTE[band(spriteByte, 0x0f)] 231 | end 232 | 233 | local points = {} 234 | 235 | love.graphics.setCanvas(layer) 236 | 237 | for i = 0, 7 do 238 | local row 239 | local rowAddr = i 240 | if verticalFlip then 241 | rowAddr = 7 - rowAddr 242 | end 243 | local row = self.cpu.memory[spriteAddr + rowAddr] 244 | 245 | -- 2bpp 246 | local row2 247 | if spriteMode == 1 then 248 | row2 = self.cpu.memory[spriteAddr + rowAddr + 8] 249 | end 250 | 251 | for j = 0, 7 do 252 | local bitAddr = j 253 | 254 | if not horizontalFlip then 255 | bitAddr = 7 - bitAddr 256 | end 257 | 258 | local value, colour_index 259 | -- 1 bpp 260 | if spriteMode == 0 then 261 | value = getBit(row, bitAddr) 262 | colour_index = palette[value+1] 263 | else 264 | value = bor( 265 | lshift(getBit(row2, bitAddr), 1), 266 | getBit(row, bitAddr) 267 | ) 268 | 269 | colour_index = palette[value+1] 270 | end 271 | 272 | if colour_index ~= "none" then 273 | local alpha = 1.0 274 | if colour_index == 0 and layer == self.front then 275 | alpha = 0.0 276 | end 277 | 278 | -- Offset by 0.5, 0.5 to account for points grid 279 | points[#points+1] = {x + j + 0.5, y + i + 0.5, colour_index / 4.0, 0, 0, alpha} 280 | 281 | end 282 | end 283 | end 284 | 285 | love.graphics.setColor(1, 1, 1, 1) 286 | love.graphics.setBlendMode("replace", "premultiplied") 287 | love.graphics.setCanvas(layer) 288 | 289 | love.graphics.points(points) 290 | 291 | love.graphics.setCanvas() 292 | 293 | love.graphics.setBlendMode("alpha") 294 | 295 | if self.auto_x then 296 | x = x + 8 297 | self:writeShort(8, x) 298 | end 299 | 300 | if self.auto_y then 301 | y = y + 8 302 | self:writeShort(10, y) 303 | end 304 | 305 | if self.auto_addr then 306 | spriteAddr = spriteAddr + 8 + (8 * spriteMode) 307 | self:writeShort(0x0c, spriteAddr) 308 | end 309 | end) 310 | 311 | return screen 312 | end 313 | 314 | local controller = Device:new() 315 | 316 | controller.name = "controller" 317 | 318 | -- Button 319 | controller:addPort(2, false) 320 | 321 | -- Key 322 | controller:addPort(3, false) 323 | 324 | local mouse = Device:new() 325 | 326 | mouse.name = "mouse" 327 | 328 | -- X 329 | mouse:addPort(2, true) 330 | 331 | -- Y 332 | mouse:addPort(4, true) 333 | 334 | -- Mouse state 335 | mouse:addPort(6, false) 336 | 337 | -- Mouse wheel 338 | mouse:addPort(7, false) 339 | 340 | local function openFile(self, mode) 341 | local cpu = self.cpu 342 | 343 | -- Pointer to the name string 344 | local name_address = self:readShort(8) 345 | 346 | -- Construct a 32-bit seek offset from 2 device shorts 347 | local seek = lshift(self:readShort(4), 16) + self:readShort(6) 348 | 349 | local fileName = "" 350 | local counter = name_address 351 | local char 352 | 353 | -- Assume null-terminated strings 354 | while char ~= 0x00 do 355 | char = cpu.memory[counter] 356 | fileName = fileName .. string.char(char) 357 | counter = counter + 1 358 | end 359 | 360 | print("Trying to open file called ", fileName) 361 | 362 | local file = love.filesystem.newFile(fileName) 363 | 364 | local ok, err = file:open(mode) 365 | 366 | if not ok then return nil end 367 | 368 | file:seek(seek) 369 | 370 | return file 371 | end 372 | 373 | local file = Device:new() 374 | 375 | -- Success 376 | file:addPort(2, true) 377 | 378 | -- File seek ( 32 bits ) 379 | file:addPort(4, true) 380 | file:addPort(6, true) 381 | 382 | -- Pointer to file name 383 | file:addPort(8, true) 384 | 385 | -- Amount of file to read 386 | file:addPort(10, true) 387 | 388 | -- Read 389 | file:addPort(12, true, nil, function(self) 390 | local length = self:readShort(10) 391 | local target_address = self:readShort(12) 392 | 393 | local file = openFile(self, "r") 394 | 395 | if not file then 396 | print("file doesn't exist") 397 | -- Return 0 length for error? 398 | return self:writeShort(2, 0) 399 | end 400 | 401 | local contents, real_size = file:read("data", length) 402 | print("read", real_size, "bytes") 403 | -- Write size to success byte 404 | self:writeShort(2, bit.band(real_size, 0xffff)) 405 | 406 | -- Interpret the file as a list of bytes 407 | local pattern = string.rep("B", real_size) 408 | local dataTable = {love.data.unpack(pattern, contents)} 409 | 410 | -- Copy contents to target_address:target_address+length 411 | for i = 1, #dataTable - 1 do -- Skip the index value that's returned 412 | cpu.memory[target_address + i - 1] = dataTable[i] 413 | end 414 | end) 415 | 416 | -- Write 417 | file:addPort(14, true, function(self) 418 | local length = self:readShort(10) 419 | local target_address = self:readShort(12) 420 | 421 | local file = openFile(self, "w") 422 | 423 | if not file then 424 | print("file can't be opened") 425 | return self:writeShort(2, 0) 426 | end 427 | 428 | -- Encode memory starting at target, running length bytes 429 | 430 | -- file:write(encodedData) 431 | end) 432 | 433 | local datetime = Device:new() 434 | datetime.name = 'datetime' 435 | datetime:addPort(0, true, function(self) 436 | return tonumber(os.date('%Y')) 437 | end) 438 | datetime:addPort(2, false, function(self) 439 | return tonumber(os.date('%m')) - 1 440 | end) 441 | datetime:addPort(3, false, function(self) 442 | return tonumber(os.date('%d')) 443 | end) 444 | datetime:addPort(4, false, function(self) 445 | return tonumber(os.date('%H')) 446 | end) 447 | datetime:addPort(5, false, function(self) 448 | return tonumber(os.date('%M')) 449 | end) 450 | datetime:addPort(6, false, function(self) 451 | return tonumber(os.date('%S')) 452 | end) 453 | datetime:addPort(7, false, function(self) 454 | return tonumber(os.date('%u')) % 7 455 | end) 456 | datetime:addPort(8, true, function(self) 457 | return tonumber(os.date('%j')) - 1 458 | end) 459 | datetime:addPort(10, true, function(self) 460 | return 0 461 | end) 462 | 463 | devices.system = system 464 | devices.console = console 465 | devices.screen = screen 466 | devices.controller = controller 467 | devices.mouse = mouse 468 | devices.file = file 469 | devices.datetime = datetime 470 | 471 | return devices 472 | -------------------------------------------------------------------------------- /uxn.lua: -------------------------------------------------------------------------------- 1 | local bit = require "bit" 2 | local math = require "math" 3 | 4 | local band, bor, bxor, bnot = bit.band, bit.bor, bit.bxor, bit.bnot 5 | local arshift, rshift, lshift = bit.arshift, bit.rshift, bit.lshift 6 | 7 | local function uint8_to_int8(byte) 8 | byte = band(byte, 0xff) 9 | if band(byte, 0x80) ~= 0 then 10 | byte = byte - 0x100 11 | end 12 | 13 | return byte 14 | end 15 | 16 | local Stack = {} 17 | 18 | Stack.__index = Stack 19 | 20 | function Stack:new(limit) 21 | local limit = limit or 256 22 | return setmetatable({ 23 | limit = limit, 24 | head = 0, 25 | }, self) 26 | end 27 | 28 | function Stack:push(byte) 29 | local head = self.head + 1 30 | self[head] = byte 31 | self.head = head 32 | end 33 | 34 | function Stack:pop() 35 | local head = self.head 36 | local byte = self[head] 37 | self.head = head - 1 38 | return byte 39 | end 40 | 41 | function Stack:check(n) 42 | local new = self.head + n 43 | return new >= 0 and new <= self.limit 44 | end 45 | 46 | -- 0-indexed non-destructive get 47 | function Stack:getnth(n) 48 | return self[self.head - n] 49 | end 50 | 51 | function Stack:len() 52 | return self.head 53 | end 54 | 55 | function Stack:debug() 56 | local t = {} 57 | for i = 1, self.head do 58 | t[#t + 1] = bit.tohex(self[i], 2) 59 | end 60 | return table.concat(t, " ") 61 | end 62 | 63 | local Memory = { 64 | __index = function(self, k) 65 | if type(k) == "number" then 66 | -- Uninitialized memory should be randomized for robust testing 67 | if self.ERROR_ON_UNINITIALIZED_READ then 68 | error("READ UNINITIALIZED MEMORY @ "..bit.tohex(k)) 69 | end 70 | return 0 --love.math.random(255) 71 | end 72 | end 73 | } 74 | 75 | local Uxn = {} 76 | 77 | Uxn.__index = Uxn 78 | 79 | function Uxn:new(mem) 80 | return setmetatable({ 81 | ip = 1, 82 | program_stack = Stack:new(), 83 | return_stack = Stack:new(), 84 | memory = setmetatable(mem or {}, Memory), 85 | devices = {}, 86 | vectors = {}, 87 | -- DEBUG TABLES 88 | debug_profile = {}, 89 | device_triggers = {}, -- Debug 90 | device_reads = {}, -- DEBUG 91 | device_writes = {}, -- DEBUG 92 | }, self) 93 | end 94 | 95 | function Uxn:profile(...) 96 | local name = table.concat({...}, "_") 97 | self.debug_profile[name] = (self.debug_profile[name] or 0) + 1 98 | end 99 | 100 | function Uxn:print_profile() 101 | local output = "name\t\tcount\n" 102 | for k,v in pairs(self.debug_profile) do 103 | output = output .. k.."\t\t"..v.."\n" 104 | end 105 | return output 106 | end 107 | 108 | function bytes_to_shorts(bytes) 109 | local shorts = {} 110 | for i = 1, #bytes, 2 do 111 | local high_byte = bytes[i] 112 | local low_byte = bytes[i+1] 113 | shorts[#shorts + 1] = band(lshift(high_byte, 8) + low_byte, 0xffff) 114 | end 115 | return shorts 116 | end 117 | 118 | function bytes_to_short(high, low) 119 | if not low then 120 | low = high[2] 121 | high = high[1] 122 | end 123 | return band(lshift(high, 8) + low, 0xffff) 124 | end 125 | 126 | function shorts_to_bytes(shorts) 127 | local bytes = {} 128 | 129 | for i = 1, #shorts do 130 | local short = shorts[i] 131 | local high_byte = rshift(short, 8) 132 | -- Mask out the low byte 133 | local low_byte = band(short, 0xff) 134 | bytes[#bytes + 1] = high_byte 135 | bytes[#bytes + 1] = low_byte 136 | end 137 | 138 | return bytes 139 | end 140 | 141 | function Uxn:get_n(n, keep_bit, return_bit, short_bit) 142 | -- Choose which stack to operate on 143 | local stack = return_bit and self.return_stack or self.program_stack 144 | 145 | -- Fetch 2 bytes for each number needed 146 | if short_bit then n = n * 2 end 147 | 148 | -- Make sure the stack has enough space to fetch n bytes 149 | if not stack:check(-n) then 150 | error("Stack not big enough to get "..tostring(n).." bytes") 151 | end 152 | 153 | local output = {} 154 | 155 | for i = 1, n do 156 | if keep_bit then 157 | output[(n - i) + 1] = stack:getnth(i-1) 158 | else 159 | output[(n - i) + 1] = stack:pop() 160 | end 161 | end 162 | 163 | if short_bit then 164 | output = bytes_to_shorts(output) 165 | end 166 | 167 | return output 168 | end 169 | 170 | function Uxn:push(value, k, r, s) 171 | local stack = r and self.return_stack or self.program_stack 172 | --assert(stack:check(1 + (s and 1 or 0)), "Can't push", value, "k", k, "r", r, "s", s) 173 | 174 | if stack.head > 0xfe then 175 | error(r and 'return stack overflow' or 'working stack overflow') 176 | end 177 | if s then 178 | stack:push(band(rshift(value, 8), 0xff)) 179 | end 180 | stack:push(band(value, 0xff)) 181 | end 182 | 183 | function Uxn:pop(k, r, s) 184 | local stack = r and self.return_stack or self.program_stack 185 | local value 186 | if k then 187 | local offset = self.peek_offset 188 | if s then 189 | local low = stack:getnth(offset) 190 | local high = stack:getnth(offset+1) 191 | value = low and high and bytes_to_short(high, low) 192 | offset = offset + 2 193 | else 194 | value = stack:getnth(offset) 195 | offset = offset + 1 196 | end 197 | self.peek_offset = offset 198 | else 199 | if s then 200 | local low = stack:pop() 201 | local high = stack:pop() 202 | value = low and high and bytes_to_short(high, low) 203 | else 204 | value = stack:pop() 205 | end 206 | end 207 | if not value then 208 | error(r and 'return stack underflow' or 'working stack underflow') 209 | end 210 | return value 211 | end 212 | 213 | local function extractOpcode(byte) 214 | local keep_bit = band(byte, 0x80) ~= 0 -- 0b1000 0000 215 | local return_bit = band(byte, 0x40) ~= 0 -- 0b0100 0000 216 | local short_bit = band(byte, 0x20) ~= 0 -- 0b0010 0000 217 | 218 | local opcode = band(byte, 0x1f) -- 0b0001 1111 219 | 220 | return opcode, keep_bit, return_bit, short_bit 221 | end 222 | 223 | function Uxn:device_read(addr, k, r, s) 224 | local device_num = rshift(addr, 4) 225 | local device = self.devices[device_num] 226 | 227 | if device then 228 | self.device_reads[device_num] = (self.device_reads[device_num] or 0) + 1 229 | local port = band(addr, 0x0f) 230 | 231 | local value 232 | if s then 233 | value = device:readShort(port) 234 | else 235 | value = band(device[port], 0xff) 236 | end 237 | 238 | return value 239 | else 240 | return 0 241 | end 242 | end 243 | 244 | function Uxn:device_write(addr, value, k, r, s) 245 | local device_num = rshift(addr, 4) 246 | local device = self.devices[device_num] 247 | 248 | local port_num = band(addr, 0x0f) 249 | 250 | if device then 251 | --self:profile("DEO", device_num, port_num) 252 | self.device_writes[device_num] = (self.device_writes[device_num] or 0) + 1 253 | if self.PRINT then print("wrote", bit.tohex(value), "to", bit.tohex(addr)) end 254 | 255 | if s then 256 | device:writeShort(port_num, value) 257 | else 258 | device[port_num] = value 259 | end 260 | end 261 | end 262 | 263 | function Uxn:addDevice(device_num, device) 264 | if self.devices[device_num] then 265 | error("Device already exists at ", bit.tohex(device_num)) 266 | end 267 | 268 | device.cpu = self 269 | device.device_num = device_num 270 | 271 | self.devices[device_num] = device 272 | 273 | return device 274 | end 275 | 276 | function Uxn:triggerDevice(device_num) 277 | local vector = self.vectors[device_num] 278 | --local vector = self.devices[device_num]:readShort(0) 279 | if vector then 280 | self.device_triggers[device_num] = (self.device_triggers[device_num] or 0) + 1 281 | self.ip = vector 282 | return self:runUntilBreak() 283 | end 284 | end 285 | 286 | local opNames = { 287 | [0] = "LIT", 288 | "INC", 289 | "POP", 290 | "DUP", 291 | "NIP", 292 | "SWAP", 293 | "OVER", 294 | "ROT", 295 | 296 | "EQU", 297 | "NEQ", 298 | "GTH", 299 | "LTH", 300 | "JMP", 301 | "JCN", 302 | "JSR", 303 | "STASH", 304 | 305 | "LDZ", 306 | "STZ", 307 | "LDR", 308 | "STR", 309 | "LDA", 310 | "STA", 311 | "DEI", 312 | "DEO", 313 | 314 | "ADD", 315 | "SUB", 316 | "MUL", 317 | "DIV", 318 | "AND", 319 | "OR", 320 | "XOR", 321 | "SHIFT", 322 | } 323 | 324 | local opTable = { 325 | -- STACK 326 | -- 327 | -- 0x00 BRK/LIT 328 | function(self, k, r, s) 329 | local value 330 | if s then 331 | value = bytes_to_short(self.memory[self.ip], self.memory[self.ip+1]) 332 | else 333 | value = self.memory[self.ip] 334 | end 335 | if self.PRINT then print("Push "..(s and "short" or "byte").." value = ",bit.tohex(value)) end 336 | self:push(value, k, r, s) 337 | self.ip = self.ip + (s and 2 or 1) 338 | end, 339 | 340 | -- 0x01 INC 341 | function(self, k, r, s) 342 | local a = self:pop(k, r, s) 343 | self:push(a + 1, k, r, s) 344 | end, 345 | 346 | -- 0x02 POP 347 | function(self, k, r, s) 348 | self:pop(k, r, s) 349 | end, 350 | 351 | -- 0x03 DUP 352 | function(self, k, r, s) 353 | local a = self:pop(k, r, s) 354 | self:push(a, k, r, s) 355 | self:push(a, k, r, s) 356 | end, 357 | 358 | -- 0x04 NIP 359 | function(self, k, r, s) 360 | local b = self:pop(k, r, s) 361 | self:pop(k, r, s) 362 | self:push(b, k, r, s) 363 | end, 364 | 365 | -- 0x05 SWAP 366 | function(self, k, r, s) 367 | local b = self:pop(k, r, s) 368 | local a = self:pop(k, r, s) 369 | self:push(b, k, r, s) 370 | self:push(a, k, r, s) 371 | end, 372 | 373 | -- 0x06 OVER 374 | function(self, k, r, s) 375 | local b = self:pop(k, r, s) 376 | local a = self:pop(k, r, s) 377 | self:push(a, k, r, s) 378 | self:push(b, k, r, s) 379 | self:push(a, k, r, s) 380 | end, 381 | 382 | -- 0x07 ROT 383 | function(self, k, r, s) 384 | local c = self:pop(k, r, s) 385 | local b = self:pop(k, r, s) 386 | local a = self:pop(k, r, s) 387 | self:push(b, k, r, s) 388 | self:push(c, k, r, s) 389 | self:push(a, k, r, s) 390 | end, 391 | 392 | 393 | -- LOGIC 394 | -- 395 | -- 0x08 EQU 396 | function(self, k, r, s) 397 | local b = self:pop(k, r, s) 398 | local a = self:pop(k, r, s) 399 | -- Push the flag as a single byte, so short mode is false in Uxn:push 400 | self:push(a == b and 1 or 0, k, r, false) 401 | end, 402 | 403 | -- 0x09 NEQ 404 | function(self, k, r, s) 405 | local b = self:pop(k, r, s) 406 | local a = self:pop(k, r, s) 407 | self:push(a ~= b and 1 or 0, k, r, false) 408 | end, 409 | 410 | -- 0x0a GTH 411 | function(self, k, r, s) 412 | local b = self:pop(k, r, s) 413 | local a = self:pop(k, r, s) 414 | self:push(a > b and 1 or 0, k, r, false) 415 | end, 416 | 417 | -- 0x0b LTH 418 | function(self, k, r, s) 419 | local b = self:pop(k, r, s) 420 | local a = self:pop(k, r, s) 421 | self:push(a < b and 1 or 0, k, r, false) 422 | end, 423 | 424 | -- 0x0c JMP 425 | function(self, k, r, s) 426 | local addr = self:pop(k, r, s) 427 | if not s then 428 | -- relative jump 429 | addr = uint8_to_int8(addr) + self.ip 430 | end 431 | 432 | self.ip = addr 433 | end, 434 | 435 | -- 0x0d JCN 436 | function(self, k, r, s) 437 | local addr = self:pop(k, r, s) 438 | local flag = self:pop(k, r, false) 439 | 440 | if flag ~= 0 then 441 | if not s then 442 | addr = self.ip + uint8_to_int8(addr) 443 | end 444 | 445 | self.ip = addr 446 | end 447 | end, 448 | 449 | -- 0x0e JSR 450 | function(self, k, r, s) 451 | local addr = self:pop(k, r, s) 452 | if not s then 453 | addr = uint8_to_int8(addr) + self.ip 454 | end 455 | 456 | -- Stash 457 | self:push(self.ip, k, not r, true) 458 | 459 | self.ip = addr 460 | end, 461 | 462 | -- 0x0f STH 463 | function(self, k, r, s) 464 | local a = self:pop(k, r, s) 465 | self:push(a, k, not r, s) 466 | end, 467 | 468 | -- MEMORY 469 | -- 470 | -- 0x10 LDZ 471 | function(self, k, r, s) 472 | local offset = self:pop(k, r, false) 473 | 474 | local value = self.memory[offset] 475 | 476 | if s then 477 | value = lshift(value, 8) + self.memory[offset + 1] 478 | end 479 | 480 | self:push(value, k, r, s) 481 | end, 482 | 483 | -- 0x11 STZ 484 | function(self, k, r, s) 485 | local data = self:get_n(s and 3 or 2, k, r, false) 486 | 487 | local offset = table.remove(data) 488 | 489 | self.memory[offset] = data[1] 490 | 491 | if s then 492 | self.memory[offset+1] = data[2] 493 | end 494 | end, 495 | 496 | -- 0x12 LDR 497 | function(self, k, r, s) 498 | local offset = self:pop(k, r, false) 499 | 500 | local addr = uint8_to_int8(offset) + self.ip 501 | 502 | local value = self.memory[addr] 503 | 504 | if s then 505 | value = lshift(value, 8) + self.memory[addr+1] 506 | end 507 | 508 | self:push(value, k, r, s) 509 | end, 510 | 511 | -- 0x13 STR 512 | function(self, k, r, s) 513 | local data = self:get_n(s and 3 or 2, k, r, false) 514 | 515 | local address = uint8_to_int8(table.remove(data)) + self.ip 516 | 517 | self.memory[address] = data[1] 518 | 519 | if s then 520 | self.memory[address+1] = data[2] 521 | end 522 | end, 523 | 524 | -- 0x14 LDA 525 | function(self, k, r, s) 526 | local addr = self:pop(k, r, true) 527 | 528 | local value = self.memory[addr] 529 | 530 | if s then 531 | value = lshift(value, 8) + self.memory[addr+1] 532 | end 533 | 534 | self:push(value, k, r, s) 535 | end, 536 | 537 | -- 0x15 STA 538 | function(self, k, r, s) 539 | local data = self:get_n(s and 4 or 3, k, r, false) 540 | 541 | local address = bytes_to_short(data[#data-1], data[#data]) 542 | 543 | self.memory[address] = data[1] 544 | 545 | if s then 546 | self.memory[address+1] = data[2] 547 | end 548 | end, 549 | 550 | 551 | -- 0x16 DEI 552 | function(self, k, r, s) 553 | local offset = self:pop(k, r, false) 554 | self:push(self:device_read(offset, k, r, s), k, r, s) 555 | end, 556 | 557 | 558 | -- 0x17 DEO 559 | function(self, k, r, s) 560 | local address = self:pop(k, r, false) 561 | local value = self:pop(k, r, s) 562 | 563 | self:device_write(address, value, k, r, s) 564 | end, 565 | 566 | 567 | -- ARITHMETIC 568 | -- 569 | -- 0x18 ADD 570 | function(self, k, r, s) 571 | local b = self:pop(k, r, s) 572 | local a = self:pop(k, r, s) 573 | self:push(a + b, k, r, s) 574 | end, 575 | 576 | -- 0x19 SUB 577 | function(self, k, r, s) 578 | local b = self:pop(k, r, s) 579 | local a = self:pop(k, r, s) 580 | self:push(a - b, k, r, s) 581 | end, 582 | 583 | -- 0x1a MUL 584 | function(self, k, r, s) 585 | local b = self:pop(k, r, s) 586 | local a = self:pop(k, r, s) 587 | self:push(a * b, k, r, s) 588 | end, 589 | 590 | -- 0x1b DIV 591 | function(self, k, r, s) 592 | local b = self:pop(k, r, s) 593 | local a = self:pop(k, r, s) 594 | assert(b ~= 0, "Can't divide by zero!") 595 | self:push(math.floor(a / b), k, r, s) 596 | end, 597 | 598 | -- 0x1c AND 599 | function(self, k, r, s) 600 | local b = self:pop(k, r, s) 601 | local a = self:pop(k, r, s) 602 | self:push(band(a, b), k, r, s) 603 | end, 604 | 605 | -- 0x1d OR 606 | function(self, k, r, s) 607 | local b = self:pop(k, r, s) 608 | local a = self:pop(k, r, s) 609 | self:push(bor(a, b), k, r, s) 610 | end, 611 | 612 | -- 0x1e EOR 613 | function(self, k, r, s) 614 | local b = self:pop(k, r, s) 615 | local a = self:pop(k, r, s) 616 | self:push(bxor(a, b), k, r, s) 617 | end, 618 | 619 | -- 0x1f SFT 620 | -- "Shift in short mode expects a single byte" 621 | function(self, k, r, s) 622 | local amount = self:pop(k, r, false) 623 | local value = self:pop(k, r, s) 624 | 625 | value = arshift(value, band(amount, 0x0f)) 626 | value = lshift(value, rshift(band(amount, 0xf0), 4)) 627 | 628 | self:push(value, k, r, s) 629 | end, 630 | } 631 | 632 | function Uxn:runUntilBreak() 633 | local count = 0 634 | local extractOpcode = extractOpcode 635 | local memory = self.memory 636 | while true do 637 | local opline 638 | local opByte = memory[self.ip] 639 | if opByte == 0 then break end 640 | if self.PRINT then 641 | opline = bit.tohex(self.ip) 642 | end 643 | self.ip = self.ip + 1 644 | 645 | local opcode, k, r, s = extractOpcode(opByte) 646 | --self:profile(opNames[opcode]) 647 | if self.PRINT then 648 | print(opline.." : "..opNames[opcode]..(s and "2" or "")..(r and "r" or "")..(k and "k" or "")) 649 | print("PS", self.program_stack:debug()) 650 | print("RS", self.return_stack:debug()) 651 | end 652 | -- Reset the peek offset so consective calls to :pop maintain state 653 | if k then self.peek_offset = 0 end 654 | opTable[opcode+1](self, k, r, s) 655 | 656 | count = count + 1 657 | end 658 | return count 659 | end 660 | 661 | return {Uxn = Uxn, uint8_to_int8 = uint8_to_int8} 662 | -------------------------------------------------------------------------------- /instruction_tests.lua: -------------------------------------------------------------------------------- 1 | local Device = require "device" 2 | local uxn = require "uxn" 3 | 4 | local Uxn = uxn.Uxn 5 | 6 | function init_zero_page(memory) 7 | for i = 0, 255 do 8 | memory[i] = 0x00 9 | end 10 | end 11 | 12 | function load_program(memory, program) 13 | for i = 1, #program do 14 | memory[i+255] = program[i] 15 | end 16 | -- Don't forget a trailing BRK 17 | memory[#program+256] = 0x00 18 | end 19 | 20 | describe("the uxn instruction", function() 21 | local cpu 22 | local memory 23 | 24 | local function PS(offset) 25 | offset = offset or 0 26 | 27 | return cpu.program_stack[cpu.program_stack:len() + offset] 28 | end 29 | 30 | local function RS(offset) 31 | offset = offset or 0 32 | 33 | return cpu.return_stack[cpu.return_stack:len() + offset] 34 | end 35 | 36 | local function run_program(program) 37 | load_program(memory, program) 38 | 39 | return cpu:runUntilBreak() 40 | end 41 | 42 | before_each(function() 43 | memory = {} 44 | for i = 0, 255 do 45 | memory[i] = 0x00 46 | end 47 | cpu = Uxn:new(memory) 48 | cpu.ip = 0x100 49 | end) 50 | 51 | describe("LIT", function() 52 | it("requires a value after the opcode in memory", function() 53 | -- Run a program that has no literal after the LIT instruction 54 | assert.has_errors(function() run_program { 55 | 0x80, -- LIT 56 | } end) 57 | end) 58 | 59 | it("must have the keep, return, or short bit set", function() 60 | run_program { 61 | 0x00, -- BRK 62 | 0x12, 63 | 0x34, 64 | } 65 | 66 | assert(cpu.program_stack:len() == 0) 67 | assert(cpu.return_stack:len() == 0) 68 | end) 69 | 70 | it("pushes a short in the correct byte order", function() 71 | local high = 0x12 72 | local low = 0x34 73 | run_program { 74 | 0x20, -- LIT2 75 | high, 76 | low, 77 | } 78 | 79 | assert(PS() == low) 80 | assert(PS(-1) == high) 81 | end) 82 | describe("pushes bytes to", function() 83 | it("the program stack by default", function() 84 | local value = 0x12 85 | run_program { 86 | 0x80, -- LIT 87 | value 88 | } 89 | 90 | assert(cpu.program_stack[cpu.program_stack:len()] == value) 91 | assert(cpu.return_stack:len() == 0) 92 | end) 93 | it("the return stack when requested", function() 94 | local value = 0x12 95 | run_program { 96 | 0x40, -- LITr 97 | value, 98 | } 99 | 100 | assert(cpu.return_stack[cpu.return_stack:len()] == value) 101 | assert(cpu.program_stack:len() == 0) 102 | end) 103 | end) 104 | end) 105 | 106 | describe("INC", function() 107 | it("increments bytes", function() 108 | run_program { 109 | 0x80, 0x90, -- LIT 0x90 110 | 0x01, -- INC 111 | } 112 | 113 | assert(PS() == 0x91) 114 | end) 115 | 116 | it("increments shorts", function() 117 | run_program { 118 | 0x20, 0x90, 0xab, -- LIT 0x90ab 119 | 0x21, -- INC2 120 | } 121 | value = 0x90ab 122 | value = value + 1 123 | 124 | assert(PS() == bit.band(value, 0xff)) 125 | assert(PS(-1) == bit.band(bit.rshift(value, 8), 0xff)) 126 | end) 127 | 128 | it("wraps bytes", function() 129 | run_program { 130 | 0x80, 0xff, -- LIT 0xff 131 | 0x01 -- INC 132 | } 133 | 134 | assert(PS() == 0x00) 135 | end) 136 | 137 | describe("wraps shorts", function() 138 | it("with one byte", function() 139 | run_program { 140 | 0x20, 0xab, 0xff, -- LIT 0xffff 141 | 0x21 -- INC2 142 | } 143 | 144 | assert(PS() == 0x00) 145 | assert(PS(-1) == 0xac) 146 | end) 147 | 148 | it("with two bytes", function() 149 | run_program { 150 | 0x20, 0xff, 0xff, -- LIT 0xffff 151 | 0x21 -- INC2 152 | } 153 | 154 | assert(PS() == 0x00) 155 | assert(PS(-1) == 0x00) 156 | end) 157 | end) 158 | end) 159 | 160 | describe("POP", function() 161 | it("removes bytes", function() 162 | run_program { 163 | 0x80, 0x00, -- LIT 0x00 164 | 0x40, 0x00, -- LITr 0x00 165 | 0x02 -- POP 166 | } 167 | 168 | assert(cpu.program_stack:len() == 0) 169 | assert(cpu.return_stack:len() == 1) 170 | end) 171 | 172 | it("removes bytes from return stack", function() 173 | run_program { 174 | 0x80, 0x00, -- LIT 0x00 175 | 0x40, 0x00, -- LITr 0x00 176 | 0x42 -- POPr 177 | } 178 | 179 | assert(cpu.program_stack:len() == 1) 180 | assert(cpu.return_stack:len() == 0) 181 | end) 182 | 183 | it("removes no bytes with keep", function() 184 | run_program { 185 | 0x80, 0xab, --LIT 0xab 186 | 0x82 -- POPk 187 | } 188 | 189 | assert(PS() == 0xab) 190 | end) 191 | 192 | it("pops shorts", function() 193 | run_program { 194 | 0x80, 0xab, -- LIT 0xab 195 | 0x20, 0x12, 0x34, -- LIT2 0x1234 196 | 0x22 -- POP2 197 | } 198 | 199 | assert(PS() == 0xab) 200 | assert(cpu.program_stack:len() == 1) 201 | end) 202 | 203 | it("can't pop shorts with only one byte on the stack", function() 204 | assert.has_errors(function() 205 | run_program { 206 | 0x80, 0xab, -- LIT 0xab 207 | 0x22 -- POP2 208 | } 209 | end) 210 | end) 211 | 212 | it("can't pop a byte when the stack is empty", function() 213 | assert.has_errors(function() 214 | run_program { 215 | 0x02, -- POP 216 | } 217 | end) 218 | end) 219 | 220 | it("can't pop a short when the stack is empty", function() 221 | assert.has_errors(function() 222 | run_program { 223 | 0x22, -- POP2 224 | } 225 | end) 226 | end) 227 | end) 228 | 229 | describe("DUP", function() 230 | it("leaves 6 bytes on the stack with K and S", function() 231 | run_program { 232 | 0x20, 0x78, 0x9a, 233 | 0xa3, -- DUP2k 1010 0011 234 | } 235 | 236 | assert(cpu.program_stack:len() == 6) 237 | end) 238 | 239 | it("leaves 3 bytes on the stack with Keep", function() 240 | run_program { 241 | 0x80, 0x12, 242 | 0x83, -- DUPk 243 | } 244 | 245 | assert(cpu.program_stack:len() == 3) 246 | end) 247 | 248 | it("duplicates shorts correctly", function() 249 | run_program { 250 | 0x20, 0x21, 0x43, -- LIT 0x2143 251 | 0x23, -- DUP2 252 | } 253 | 254 | assert(cpu.program_stack:len() == 4) 255 | assert(PS() == 0x43) 256 | assert(PS(-1) == 0x21) 257 | assert(PS() == PS(-2)) 258 | assert(PS(-1) == PS(-3)) 259 | end) 260 | 261 | it("duplicates bytes correctly", function() 262 | run_program { 263 | 0x80, 0x83, 264 | 0x03, 265 | } 266 | 267 | assert(cpu.program_stack:len() == 2) 268 | assert(PS() == 0x83) 269 | assert(PS() == PS(-1)) 270 | end) 271 | end) 272 | 273 | describe("NIP", function() 274 | pending("one byte fail", true) 275 | pending("one short fail", true) 276 | pending("one byte short fail", true) 277 | 278 | pending("byte", true) 279 | pending("short", true) 280 | 281 | pending("return stack", true) 282 | 283 | pending("keep", true) 284 | end) 285 | 286 | pending("SWAP", function() 287 | 288 | end) 289 | 290 | pending("OVER", function() 291 | 292 | end) 293 | 294 | describe("EQU", function() 295 | describe("returns 1", function() 296 | it("with equal bytes", function() 297 | run_program { 298 | 0x80, 0x34, 299 | 0x80, 0x34, 300 | 0x08 301 | } 302 | 303 | assert(PS() == 0x01) 304 | assert(cpu.program_stack:len() == 1) 305 | end) 306 | 307 | it("with equal shorts", function() 308 | run_program { 309 | 0x20, 0x12, 0x34, -- LIT 0x1234 310 | 0x20, 0x12, 0x34, -- LIT 0x1234 311 | 0x28 -- EQU2 312 | } 313 | 314 | assert(PS() == 0x01) 315 | assert(cpu.program_stack:len() == 1) 316 | end) 317 | 318 | pending("keep", true) 319 | end) 320 | 321 | describe("returns 0", function() 322 | it("not equal bytes", function() 323 | run_program { 324 | 0x80, 0xab, -- LIT 0xab 325 | 0x80, 0x34, -- LIT 0x34 326 | 0x08 327 | } 328 | 329 | assert(PS() == 0x00) 330 | assert(cpu.program_stack:len() == 1) 331 | end) 332 | 333 | it("fully not equal shorts", function() 334 | run_program { 335 | 0x20, 0x12, 0x34, -- LIT 0x1234 336 | 0x20, 0x21, 0x43, -- LIT 0x2143 337 | 0x28, -- EQU2 338 | } 339 | 340 | assert(PS() == 0x00) 341 | assert(cpu.program_stack:len() == 1) 342 | end) 343 | 344 | it("with shorts with same low bytes", function() 345 | run_program { 346 | 0x20, 0x12, 0x34, -- LIT 0x1234 347 | 0x20, 0xab, 0x34, -- LIT 0x1234 348 | 0x28 -- EQU2 349 | } 350 | 351 | assert(PS() == 0x00) 352 | assert(cpu.program_stack:len() == 1) 353 | end) 354 | 355 | it("with shorts with same high bytes", function() 356 | run_program { 357 | 0x20, 0x12, 0x34, -- LIT 0x1234 358 | 0x20, 0x12, 0xde, -- LIT 0x1234 359 | 0x28 -- EQU2 360 | } 361 | 362 | assert(PS() == 0x00) 363 | assert(cpu.program_stack:len() == 1) 364 | end) 365 | end) 366 | end) 367 | 368 | describe("NEQ", function() 369 | describe("returns 0", function() 370 | it("with equal bytes", function() 371 | run_program { 372 | 0x80, 0x34, 373 | 0x80, 0x34, 374 | 0x09 375 | } 376 | 377 | assert(PS() == 0x00) 378 | assert(cpu.program_stack:len() == 1) 379 | end) 380 | 381 | it("with equal shorts", function() 382 | run_program { 383 | 0x20, 0x12, 0x34, -- LIT 0x1234 384 | 0x20, 0x12, 0x34, -- LIT 0x1234 385 | 0x29 -- NEQ2 386 | } 387 | 388 | assert(PS() == 0x00) 389 | assert(cpu.program_stack:len() == 1) 390 | end) 391 | 392 | pending("keep", true) 393 | end) 394 | 395 | describe("returns 1", function() 396 | it("not equal bytes", function() 397 | run_program { 398 | 0x80, 0x34, -- LIT 0x34 399 | 0x80, 0xc3, -- LIT 0xc3 400 | 0x09 401 | } 402 | 403 | assert(PS() == 0x01) 404 | assert(cpu.program_stack:len() == 1) 405 | end) 406 | 407 | it("fully not equal shorts", function() 408 | run_program { 409 | 0x20, 0x12, 0x34, -- LIT 0x1234 410 | 0x20, 0x21, 0x43, -- LIT 0x2143 411 | 0x29, -- NEQ2 412 | } 413 | 414 | assert(PS() == 0x01) 415 | assert(cpu.program_stack:len() == 1) 416 | end) 417 | 418 | it("with shorts with same low bytes", function() 419 | run_program { 420 | 0x20, 0x12, 0x34, -- LIT 0x1234 421 | 0x20, 0xab, 0x34, -- LIT 0x1234 422 | 0x29 -- NEQ2 423 | } 424 | 425 | assert(PS() == 0x01) 426 | assert(cpu.program_stack:len() == 1) 427 | end) 428 | 429 | it("with shorts with same high bytes", function() 430 | run_program { 431 | 0x20, 0x12, 0x34, -- LIT 0x1234 432 | 0x20, 0x12, 0xde, -- LIT 0x1234 433 | 0x29 -- NEQ2 434 | } 435 | 436 | assert(PS() == 0x01) 437 | assert(cpu.program_stack:len() == 1) 438 | end) 439 | end) 440 | end) 441 | 442 | pending("GTH", function() 443 | 444 | end) 445 | 446 | pending("LTH", function() 447 | 448 | end) 449 | 450 | describe("JMP", function() 451 | describe("jumps to a relative", function() 452 | it("negative offset", function() 453 | offset = -10 454 | init_zero_page(memory) 455 | load_program(memory, { 456 | 0x80, bit.band(offset, 0xff), -- LIT value 457 | 0x0c, -- relative byte jump 458 | }) 459 | 460 | cpu:executeOnce() 461 | assert(cpu.ip == 0x102) 462 | 463 | cpu:executeOnce() 464 | 465 | assert(cpu.ip == 0x102 + 1 + offset, "ip is incremented by 1, so must take that into account") 466 | end) 467 | 468 | it("positive offset", function() 469 | offset = 10 470 | load_program(memory, { 471 | 0x80, bit.band(offset, 0xff), -- LIT value 472 | 0x0c, -- relative byte jump 473 | 0x00, -- padding 474 | 0x00, 0x00, 0x00, 0x00, 0x00, 475 | 0x00, 0x00, 0x00, 0x00, 0x00, 476 | }) 477 | 478 | cpu:executeOnce() 479 | assert(cpu.ip == 0x102) 480 | 481 | cpu:executeOnce() 482 | 483 | assert(cpu.ip == 0x102 + 1 + offset, "ip is incremented by 1, so must take that into account") 484 | 485 | end) 486 | 487 | it("zero offset", function() 488 | run_program { 489 | 0x80, 0x00, -- LIT 0x00 490 | 0x0c, 491 | 0x80, 0xab, 492 | } 493 | 494 | assert(PS() == 0xab) 495 | assert(cpu.program_stack:len() == 1) 496 | end) 497 | 498 | it("wrapping offset", function() 499 | load_program(memory, { 500 | 0x80, 0x7f, -- LIT 0x7f (127) 501 | 0x80, 0x7b, -- LIT 0x7b (123) 502 | 0x0c, -- Relative byte jump 503 | }) 504 | 505 | for i = 0x107, 0x201 do 506 | memory[i] = 0x00 507 | end 508 | 509 | for i = 1, 3 do 510 | cpu:executeOnce() 511 | end 512 | 513 | assert(cpu.ip == 0x180) 514 | 515 | memory[cpu.ip] = 0x0c -- relative byte jump 516 | 517 | cpu:executeOnce() 518 | assert(cpu.ip == 0x200) 519 | end) 520 | end) 521 | 522 | describe("jumps to an absolute", function() 523 | pending("short", function() end) 524 | pending("zero-page address", function() end) 525 | end) 526 | end) 527 | 528 | pending("JCN", function() 529 | 530 | end) 531 | 532 | pending("JSR", function() 533 | 534 | end) 535 | 536 | describe("STASH", function() 537 | describe("moves one", function() 538 | it("byte from program to return", function() 539 | run_program { 540 | 0x80, 0x12, -- LIT 0x12 541 | 0x40, 0x34, -- LITr 0x34 542 | 0x0f, -- STH 543 | } 544 | 545 | assert(cpu.program_stack:len() == 0) 546 | assert(cpu.return_stack:len() == 2) 547 | 548 | assert(RS() == 0x12) 549 | assert(RS(-1) == 0x34) 550 | end) 551 | 552 | it("byte from return to program", function() 553 | run_program { 554 | 0x80, 0x12, -- LIT 0x12 555 | 0x40, 0x34, -- LITr 0x34 556 | 0x4f, -- STHr 557 | } 558 | 559 | assert(cpu.program_stack:len() == 2) 560 | assert(cpu.return_stack:len() == 0) 561 | 562 | assert(PS() == 0x34) 563 | assert(PS(-1) == 0x12) 564 | end) 565 | 566 | it("short from program to return", function() 567 | run_program { 568 | 0x20, 0x12, 0x34, -- LIT 0x1234 569 | 0x60, 0xab, 0xcd, -- LITr 0xabcd 570 | 0x2f -- STH 571 | } 572 | 573 | assert(cpu.program_stack:len() == 0) 574 | assert(cpu.return_stack:len() == 4) 575 | 576 | assert(RS() == 0x34) 577 | assert(RS(-1) == 0x12) 578 | assert(RS(-2) == 0xcd) 579 | assert(RS(-3) == 0xab) 580 | end) 581 | 582 | it("short from return to program", function() 583 | run_program { 584 | 0x20, 0x12, 0x34, -- LIT 0x1234 585 | 0x60, 0xab, 0xcd, -- LITr 0xabcd 586 | 0x6f -- STH 587 | } 588 | 589 | assert(cpu.program_stack:len() == 4) 590 | assert(cpu.return_stack:len() == 0) 591 | 592 | assert(PS() == 0xcd) 593 | assert(PS(-1) == 0xab) 594 | assert(PS(-2) == 0x34) 595 | assert(PS(-3) == 0x12) 596 | end) 597 | end) 598 | 599 | it("keeps number of bytes properly", function() 600 | run_program { 601 | 0x20, 0x21, 0x43, -- LIT 0x2143 602 | 0xaf, -- STH2k 603 | } 604 | 605 | assert(cpu.program_stack:len() == 2) 606 | assert(cpu.return_stack:len() == 2) 607 | end) 608 | end) 609 | 610 | describe("ADD", function() 611 | it("adds two bytes without overflow", function() 612 | value1 = 0x45 613 | value2 = 0x2c 614 | 615 | run_program { 616 | 0x80, value1, 617 | 0x80, value2, 618 | 0x18, -- ADD 619 | } 620 | 621 | assert(PS() == value1 + value2) 622 | end) 623 | 624 | it("adds two bytes with overflow", function() 625 | value1 = 0xdf 626 | value2 = 0x30 627 | 628 | run_program { 629 | 0x80, value1, 630 | 0x80, value2, 631 | 0x18, -- ADD 632 | } 633 | 634 | assert(PS() == 0x0f) 635 | end) 636 | 637 | describe("adds two shorts", function() 638 | it("with no overflow", function() 639 | run_program { 640 | 0x20, 0x12, 0xa0, -- LIT 0x12fe 641 | 0x20, 0x34, 0x02, -- LIT 0x3402 642 | 0x38, -- ADD2 643 | } 644 | 645 | assert(PS() == 0xa0 + 0x02) 646 | assert(PS(-1) == 0x12 + 0x34) 647 | end) 648 | 649 | it("with low overflow", function() 650 | run_program { 651 | 0x20, 0x12, 0xfe, -- LIT 0x12fe 652 | 0x20, 0x34, 0x02, -- LIT 0x3402 653 | 0x38, -- ADD2 654 | } 655 | 656 | assert(PS(-1) == 0x12 + 0x34 + 0x01, "top byte should be + 1") 657 | assert(PS() == 0x00) 658 | end) 659 | 660 | it("with high overflow", function() 661 | run_program { 662 | 0x20, 0xdf, 0x12, -- LIT 0x12fe 663 | 0x20, 0x21, 0x34, -- LIT 0x3402 664 | 0x38, -- ADD2 665 | } 666 | 667 | assert(PS(-1) == 0x00) 668 | assert(PS() == 0x12 + 0x34) 669 | end) 670 | end) 671 | end) 672 | 673 | describe("DIV", function() 674 | it("doesn't divide by zero", function() 675 | assert.has_error(function() 676 | run_program { 677 | 0x80, 0x40, 678 | 0x80, 0x00, 679 | 0x1b, 680 | } 681 | end) 682 | end) 683 | 684 | it("divides cleanly by 2", function() 685 | run_program { 686 | -- DIV 0x34/0x02 687 | 0x80, 0x34, 688 | 0x80, 0x02, 689 | 0x1b, 690 | 691 | -- DIV 0xfe/0x02 692 | 0x80, 0xfe, 693 | 0x80, 0x02, 694 | 0x1b, 695 | 696 | -- DIV 0xceaa/0x0002 697 | 0x20, 0xce, 0xaa, 698 | 0x20, 0x00, 0x02, 699 | 0x3b, 700 | } 701 | 702 | assert(cpu.program_stack:len() == 4) 703 | assert(PS() == 0x55) 704 | assert(PS(-1) == 0x67) 705 | 706 | assert(PS(-2) == 0x7f) 707 | 708 | assert(PS(-3) == 0x1a) 709 | end) 710 | end) 711 | 712 | describe("LDZ", function() 713 | before_each(function() 714 | -- Initialize the zero page to avoid uninitialized errors 715 | for i = 0x00, 0xff do 716 | memory[i] = 0 717 | end 718 | 719 | -- Boundary conditions 720 | memory[0x00] = 0x12 721 | memory[0x01] = 0x34 722 | 723 | memory[0xfe] = 0x78 724 | memory[0xff] = 0x9a 725 | 726 | -- Byte test 727 | memory[0x09] = 0x1d 728 | memory[0x0a] = 0x3c 729 | memory[0x0b] = 0x5a 730 | 731 | -- Short test 732 | memory[0x9f] = 0xab 733 | memory[0xa0] = 0xcd 734 | memory[0xa1] = 0xef 735 | end) 736 | 737 | it("fetches bytes", function() 738 | run_program { 739 | -- Fetch 0x00 740 | 0x80, 0x00, 0x10, 741 | 742 | -- Fetch a byte in the middle of the page 743 | 0x80, 0x0a, 0x10, 744 | 745 | -- Fetch 0xff 746 | 0x80, 0xff, 0x10, 747 | } 748 | 749 | assert(cpu.program_stack:len() == 3) 750 | assert(PS() == 0x9a) 751 | assert(PS(-1) == 0x3c) 752 | assert(PS(-2) == 0x12) 753 | end) 754 | 755 | it("fetches shorts", function() 756 | run_program { 757 | -- Fetch 0x00 758 | 0x80, 0x00, 0x30, 759 | 760 | -- Fetch a short in the middle of the page 761 | 0x80, 0xa0, 0x30, 762 | 763 | -- Fetch 0xff 764 | 0x80, 0xfe, 0x30, 765 | } 766 | 767 | assert(cpu.program_stack:len() == 6) 768 | assert(PS() == 0x9a) 769 | assert(PS(-1) == 0x78) 770 | assert(PS(-2) == 0xef) 771 | assert(PS(-3) == 0xcd) 772 | assert(PS(-4) == 0x34) 773 | assert(PS(-5) == 0x12) 774 | end) 775 | end) 776 | 777 | describe("STZ", function() 778 | it("only takes one byte of address", function() 779 | run_program { 780 | 0x20, 0xff, 0xff, -- Push values that should not be touched 781 | 0x80, 0xab, 782 | 0x80, 0x0a, 783 | 784 | 0x11, -- STZ 785 | 786 | 0x20, 0x21, 0x43, 787 | 0x80, 0xa0, -- Store a short at 0xa0 788 | 0x31, -- STZ2 789 | 790 | } 791 | 792 | assert(cpu.program_stack:len() == 2) 793 | end) 794 | 795 | it("overwrites manually set values ", function() 796 | memory[0x9c] = 0xab 797 | 798 | run_program { 799 | 0x80, 0x34, -- LIT 0x34 800 | 0x80, 0x9c, -- Address 0x9c 801 | 0x11, -- STZ 802 | } 803 | 804 | assert(memory[0x9c] == 0x34) 805 | end) 806 | end) 807 | 808 | pending("LDA", function() 809 | 810 | end) 811 | 812 | pending("STA", function() 813 | 814 | end) 815 | 816 | describe("LDR", function() 817 | it("loads positive offsets", function() 818 | run_program { 819 | 0x80, 0x02, -- Offset 0x02 820 | 0x12, -- LDR 821 | 0x00, 822 | 0x00, 823 | 0x12, 824 | } 825 | 826 | assert(PS() == 0x12) 827 | end) 828 | 829 | it("loads negative offsets", function() 830 | run_program { 831 | 0x80, 0x08, -- Jump offset 832 | 0x0c, -- Relative jump 833 | 0x00, 834 | 0x00, 835 | 0x00, 836 | 0x00, 837 | 0xab, -- The desired data 838 | 0x00, 839 | 0x00, 840 | 0x00, 841 | 0x80, 0xf9, -- Load offset (-6) 842 | 0x12, 843 | } 844 | 845 | assert(PS() == 0xab) 846 | end) 847 | 848 | it("loads shorts", function() 849 | run_program { 850 | 0x80, 0x02, -- Load offset 851 | 0x32, -- LDR2 852 | 0x00, 853 | 0x00, 854 | 0x21, 855 | 0x43, 856 | 0x65, 857 | 0x00, 858 | } 859 | 860 | assert(cpu.program_stack:len() == 2) 861 | assert(PS() == 0x43) 862 | assert(PS(-1) == 0x21) 863 | end) 864 | end) 865 | 866 | pending("STR", function() 867 | 868 | end) 869 | 870 | describe("DEI", function() 871 | before_each(function() 872 | local mem_device = Device:new() 873 | mem_device:addPort(2, true) 874 | 875 | mem_device:addPort(4, true) 876 | 877 | mem_device:addPort(6, false) 878 | mem_device:addPort(7, false) 879 | 880 | mem_device:writeShort(0x02, 0x2143) 881 | mem_device[0x04] = 0x7c -- Too short value in short port 882 | 883 | mem_device[0x06] = 0xab 884 | mem_device:writeShort(0x07, 0x1337) -- Too long value in byte port 885 | 886 | cpu:addDevice(1, mem_device) 887 | 888 | local func_device = Device:new() 889 | 890 | func_device:addPort(2, true, function() return 0x87a9 end, nil) 891 | func_device:addPort(4, true, function() return 0x34 end, nil) 892 | 893 | func_device:addPort(6, false, function() return 0xab end, nil) 894 | func_device:addPort(7, false, function() return 0x1337 end, nil) 895 | 896 | cpu:addDevice(2, func_device) 897 | end) 898 | 899 | it("fetches stored bytes", function() 900 | run_program { 901 | 0x80, 0x17, -- Address of too long byte 902 | 0x16, -- DEI 903 | 904 | 0x80, 0x12, -- Address of short port 905 | 0x16, -- DEI 906 | 907 | 0x80, 0x16, -- Address of proper byte port 908 | 0x16, -- DEI 909 | } 910 | 911 | assert(cpu.program_stack:len() == 3, "Only bytes fetched") 912 | assert(PS() == 0xab) 913 | assert(PS(-1) == 0x21) 914 | assert(PS(-2) == 0x13) 915 | end) 916 | 917 | it("fetches stored shorts", function() 918 | run_program { 919 | 0x80, 0x12, -- Address of short port 920 | 0x36, -- DEI2 921 | 922 | 0x80, 0x14, -- Address of too short short port 923 | 0x36, -- DEI2 924 | 925 | 0x80, 0x16, -- Address of byte port 926 | 0x36, -- DEI2 927 | } 928 | 929 | assert(cpu.program_stack:len() == 6, "Only fetch shorts") 930 | print("PS") 931 | cpu.program_stack:debug() 932 | 933 | -- Gets the byte from the next mem slot 934 | assert(PS() == 0x13) 935 | assert(PS(-1) == 0xab) 936 | 937 | -- Fetching from a port with too short of a value 938 | assert(PS(-2) == 0x00) 939 | assert(PS(-3) == 0x7c) 940 | 941 | -- Fetches normal short properly 942 | assert(PS(-4) == 0x43) 943 | assert(PS(-5) == 0x21) 944 | end) 945 | 946 | pending("fetches function bytes", function() 947 | 948 | end) 949 | end) 950 | end) 951 | 952 | describe("helper function", function() 953 | test("uint8_to_int8", function() 954 | local u = uxn.uint8_to_int8 955 | assert(u(0xff) == -1) 956 | assert(u(128) == -128) 957 | assert(u(127) == 127) 958 | assert(u(0) == 0) 959 | assert(u(-10) == -10) 960 | assert(u(55) == 55) 961 | assert(u(0xfffe) == -2) 962 | assert(u(0x000e) == 0x0e) 963 | end) 964 | 965 | describe("bytes_to_short", function() 966 | pending("larger than 16 bits", true) 967 | end) 968 | end) 969 | --------------------------------------------------------------------------------