Class: Zemu::InteractiveInstance
- Inherits:
-
Object
- Object
- Zemu::InteractiveInstance
- Defined in:
- lib/zemu/interactive.rb
Overview
An interactive instance of a Zemu emulator. Wraps a Zemu::Instance to allow for user input and debugging.
Instance Method Summary collapse
-
#add_breakpoint(addr_str) ⇒ Object
Add a breakpoint at the address given by the string.
-
#close ⇒ Object
Close the interactive wrapper.
-
#continue(cycles = -1)) ⇒ Object
Continue for *up to* the given number of cycles.
-
#get_symbol(value) ⇒ Object
Gets the symbol associated with the given value.
-
#hilo(hi, lo) ⇒ Object
Concatenates two 8-bit values, in big-endian format.
-
#initialize(instance, options = {}) ⇒ InteractiveInstance
constructor
Constructor.
-
#load_map(path) ⇒ Object
Loads a MAP file from the given path.
-
#log(message) ⇒ Object
Logs a message to the user output.
-
#memory(address, size = "1") ⇒ Object
Dump an amount of memory.
-
#process_serial ⇒ Object
Process serial input/output via the TTY.
-
#r(reg) ⇒ Object
Returns a particular 8-bit register value.
-
#r16(reg) ⇒ Object
Returns a particular 16-bit register value.
-
#register_16(r) ⇒ Object
Displays the value of a 16-bit register.
-
#registers ⇒ Object
Outputs a table giving the current values of the instance’s registers.
-
#registers_gp(hi, lo) ⇒ Object
Displays the value of a general-purpose 16-bit register pair.
-
#run ⇒ Object
Run the interactive emulator until the user exits.
-
#trace ⇒ Object
Print trace for the emulator instance (last 200 addresses visited).
Constructor Details
#initialize(instance, options = {}) ⇒ InteractiveInstance
Constructor.
Create a new interactive wrapper for the given instance. The options hash allows the user to configure the behaviour of the interactive instance:
:print_serial => true if serial input/output should be logged
to the emulator window.
12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/zemu/interactive.rb', line 12 def initialize(instance, = {}) @print_serial = [:print_serial] @trace = [] @instance = instance @symbol_table = {} @master, @slave = PTY.open log "Opened PTY at #{@slave.path}" end |
Instance Method Details
#add_breakpoint(addr_str) ⇒ Object
Add a breakpoint at the address given by the string.
217 218 219 |
# File 'lib/zemu/interactive.rb', line 217 def add_breakpoint(addr_str) @instance.break(addr_str.to_i(16), :program) end |
#close ⇒ Object
Close the interactive wrapper
30 31 32 33 34 |
# File 'lib/zemu/interactive.rb', line 30 def close @master.close @slave.close @instance.quit end |
#continue(cycles = -1)) ⇒ Object
Continue for *up to* the given number of cycles. Fewer cycles may be executed, depending on the behaviour of the processor.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/zemu/interactive.rb', line 172 def continue(cycles=-1) if cycles == 0 log "Invalid value: #{cycles}" return end # Continue executing instruction-by-instruction. # Process IO in-between. cycles_left = cycles actual_cycles = 0 serial_count = @instance.serial_delay.to_f while ((cycles == -1) || (cycles_left > 0)) old_pc = r16("PC") if (serial_count >= @instance.serial_delay) process_serial serial_count = 0.0 end cycles_done = @instance.continue(1) cycles_left -= cycles_done actual_cycles += cycles_done # Get elapsed time and calculate padding time to match clock speed. if @instance.clock_speed > 0 execution_time = cycles_done * (1.0/@instance.clock_speed) serial_count += execution_time end # Have we hit a breakpoint or HALT instruction? if @instance.break? log "Hit breakpoint at #{r16("PC")}." break elsif @instance.halted? log "Executed HALT instruction at #{old_pc}." break end end log "Executed for #{actual_cycles} cycles." end |
#get_symbol(value) ⇒ Object
Gets the symbol associated with the given value. If no matching symbol, gives offset from previous symbol.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/zemu/interactive.rb', line 139 def get_symbol(value) syms = nil addr = value while addr > 0 do syms = @symbol_table[addr] break unless syms.nil? addr -= 1 end sym = if syms.nil? then nil else syms[0] end sym_str = "<#{if sym.nil? then 'undefined' else sym.label end}#{if addr == value then '' else "+#{value-addr}" end}>" return sym_str end |
#hilo(hi, lo) ⇒ Object
Concatenates two 8-bit values, in big-endian format.
166 167 168 |
# File 'lib/zemu/interactive.rb', line 166 def hilo(hi, lo) return (hi << 8) | lo end |
#load_map(path) ⇒ Object
Loads a MAP file from the given path.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/zemu/interactive.rb', line 244 def load_map(path) if path.nil? log "No path specified." return end unless File.exist?(path.to_s) log "Map file '#{path}' does not exist." return end if File.directory?(path.to_s) log "Cannot open '#{path}': it is a directory." return end syms = {} begin syms.merge! Debug.load_map(path.to_s).hash rescue ArgumentError => e log "Error loading map file: #{e.message}" syms.clear end @symbol_table.merge! syms end |
#log(message) ⇒ Object
Logs a message to the user output.
25 26 27 |
# File 'lib/zemu/interactive.rb', line 25 def log() STDOUT.puts " " + end |
#memory(address, size = "1") ⇒ Object
Dump an amount of memory.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/zemu/interactive.rb', line 222 def memory(address, size="1") if address.nil? log "Expected an address, got #{address}." return end if (address.to_i(16) < 1 || address.to_i(16) > 0xffff) log "Invalid address: 0x%04x" % address.to_i(16) return end (address.to_i(16)...address.to_i(16) + size.to_i(16)).each do |a| m = @instance.memory(a) if (m < 32 || m > 126) log "%04x: %02x ." % [a, m] else log ("%04x: %02x " % [a, m]) + m.chr("UTF-8") end end end |
#process_serial ⇒ Object
Process serial input/output via the TTY.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/zemu/interactive.rb', line 272 def process_serial # Read/write serial. # Get the strings to be input/output. input = "" ready = IO.select([@master], [], [], 0) unless ready.nil? || ready.empty? input = @master.read(1) end output = @instance.serial_gets(1) unless input.empty? @instance.serial_puts input log "Serial in: #{input} ($#{input.ord.to_s(16)})" if @print_serial end unless output.empty? @master.write output log "Serial out: #{output} ($#{output.ord.to_s(16)})" if @print_serial end end |
#r(reg) ⇒ Object
Returns a particular 8-bit register value.
156 157 158 |
# File 'lib/zemu/interactive.rb', line 156 def r(reg) return "0x%02x" % @instance.registers[reg] end |
#r16(reg) ⇒ Object
Returns a particular 16-bit register value.
161 162 163 |
# File 'lib/zemu/interactive.rb', line 161 def r16(reg) return "0x%04x" % @instance.registers[reg] end |
#register_16(r) ⇒ Object
Displays the value of a 16-bit register.
124 125 126 127 128 |
# File 'lib/zemu/interactive.rb', line 124 def register_16(r) value = @instance.registers[r] log "#{r}: #{r16(r)} (#{get_symbol(value)})" end |
#registers ⇒ Object
Outputs a table giving the current values of the instance’s registers. For the 16-bit registers (BC, DE, HL, IX, IY, SP, PC), attempts to identify the symbol to which they point.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/zemu/interactive.rb', line 108 def registers log "A: #{r("A")} F: #{r("F")}" registers_gp('B', 'C') registers_gp('D', 'E') registers_gp('H', 'L') log "" register_16("IX") register_16("IY") register_16("SP") register_16("PC") end |
#registers_gp(hi, lo) ⇒ Object
Displays the value of a general-purpose 16-bit register pair.
131 132 133 134 135 |
# File 'lib/zemu/interactive.rb', line 131 def registers_gp(hi, lo) value = hilo(@instance.registers[hi], @instance.registers[lo]) log "#{hi}: #{r(hi)} #{lo}: #{r(lo)} (#{get_symbol(value)})" end |
#run ⇒ Object
Run the interactive emulator until the user exits.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/zemu/interactive.rb', line 37 def run quit = false until quit print "ZEMU> " # Get a command from the user. cmd = STDIN.gets.split if cmd[0] == "quit" quit = true elsif cmd[0] == "continue" if cmd[1].nil? continue else continue(cmd[1].to_i) end elsif cmd[0] == "step" continue(1) elsif cmd[0] == "registers" registers elsif cmd[0] == "break" add_breakpoint(cmd[1]) elsif cmd[0] == "memory" if cmd[2].nil? memory(cmd[1]) else memory(cmd[1], cmd[2]) end elsif cmd[0] == "map" load_map(cmd[1]) elsif cmd[0] == "trace" trace() elsif cmd[0] == "help" log "Available commands:" log " continue [<n>] - Continue execution for <n> cycles" log " step - Step over a single instruction" log " registers - View register contents" log " memory <a> [<n>] - View <n> bytes of memory, starting at address <a>." log " <n> defaults to 1 if omitted." log " map <path> - Load symbols from map file at <path>" log " break <a> - Set a breakpoint at the given address <a>." log " quit - End this emulator instance." else log "Invalid command. Type 'help' for available commands." end end close end |
#trace ⇒ Object
Print trace for the emulator instance (last 200 addresses visited).
99 100 101 102 103 |
# File 'lib/zemu/interactive.rb', line 99 def trace @trace.each do |t| puts "%04x" % t end end |