Class: Zemu::Instance
- Inherits:
-
Object
- Object
- Zemu::Instance
- Defined in:
- lib/zemu/instance.rb
Overview
Represents an instance of a Zemu emulator.
Provides methods by which the state of the emulator can be observed and the execution of the program controlled.
Defined Under Namespace
Classes: RunState
Constant Summary collapse
- REGISTERS =
Mapping of register names to the ID numbers used to identify them by the debug functionality of the built library.
{ # Special purpose registers "PC" => 0, "SP" => 1, "IY" => 2, "IX" => 3, # Main register set "A" => 4, "F" => 5, "B" => 6, "C" => 7, "D" => 8, "E" => 9, "H" => 10, "L" => 11, # Alternate register set "A'" => 12, "F'" => 13, "B'" => 14, "C'" => 15, "D'" => 16, "E'" => 17, "H'" => 18, "L'" => 19 }
- REGISTERS_EXTENDED =
Mapping of extended registers to the registers that comprise them.
{ "HL" => ["H", "L"], "BC" => ["B", "C"], "DE" => ["D", "E"] }
Instance Method Summary collapse
-
#break(address, type) ⇒ Object
Set a breakpoint of the given type at the given address.
-
#break? ⇒ Boolean
Returns true if a breakpoint has been hit, false otherwise.
-
#clock_speed ⇒ Object
Returns the clock speed of this instance in Hz.
-
#continue(run_cycles = -1)) ⇒ Object
Continue running this instance until either: * A HALT instruction is executed * A breakpoint is hit * The number of cycles given has been executed.
-
#device(name) ⇒ Object
Returns the device with the given name, or nil if no such device exists.
-
#drive_readbyte(sector, offset) ⇒ Object
Get a byte from the attached disk drive.
-
#halted? ⇒ Boolean
Returns true if the CPU has halted, false otherwise.
-
#initialize(configuration) ⇒ Instance
constructor
A new instance of Instance.
-
#memory(address) ⇒ Object
Access the value in memory at a given address.
-
#method_missing(method, *args) ⇒ Object
Redirects calls to I/O FFI functions.
-
#quit ⇒ Object
Powers off the emulated CPU and destroys this instance.
-
#registers ⇒ Object
Returns a hash containing current values of the emulated machine’s registers.
-
#remove_break(address, type) ⇒ Object
Remove a breakpoint of the given type at the given address.
-
#serial_delay ⇒ Object
Returns the delay between characters on the serial port for this instance in seconds.
-
#serial_gets(count = nil) ⇒ Object
Get a number of characters from the serial line of the emulated CPU.
-
#serial_puts(string) ⇒ Object
Write a string to the serial line of the emulated CPU.
-
#set_memory(address, value) ⇒ Object
Set the value in memory at a given address.
-
#trace(address, &block) ⇒ Object
Set a tracepoint to execute the given block at an address.
Constructor Details
#initialize(configuration) ⇒ Instance
Returns a new instance of Instance.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/zemu/instance.rb', line 74 def initialize(configuration) @devices = configuration.devices # Methods defined by bus devices that we make # accessible to the user. @device_methods = [] @clock = configuration.clock_speed @serial_delay = configuration.serial_delay @wrapper = make_wrapper(configuration) @serial = [] @instance = @wrapper.zemu_init # Declare handlers. # Memory write handler. @mem_write = Proc.new do |addr, value| @devices.each do |d| d.mem_write(addr, value) end end # Memory read handler. @mem_read = Proc.new do |addr| r = 0 @devices.each do |d| v = d.mem_read(addr) unless v.nil? r = v break end end r end # IO write handler. @io_write = Proc.new do |port, value| @devices.each do |d| d.io_write(port, value) end end # IO read handler. @io_read = Proc.new do |port| r = 0 @devices.each do |d| v = d.io_read(port) unless v.nil? r = v break end end r end # IO read handler. @io_clock = Proc.new do |cycles| @devices.each do |d| d.clock(cycles) end bus_state = 0 if @devices.any? { |d| d.nmi? } bus_state |= 1 end if @devices.any? { |d| d.interrupt? } bus_state |= 2 end bus_state end # Attach handlers. @wrapper.zemu_set_mem_write_handler(@mem_write) @wrapper.zemu_set_mem_read_handler(@mem_read) @wrapper.zemu_set_io_write_handler(@io_write) @wrapper.zemu_set_io_read_handler(@io_read) @wrapper.zemu_set_io_clock_handler(@io_clock) @wrapper.zemu_power_on(@instance) @wrapper.zemu_reset(@instance) @state = RunState::UNDEFINED @breakpoints = {} @tracepoints = {} end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args) ⇒ Object
Redirects calls to I/O FFI functions.
426 427 428 429 430 431 432 |
# File 'lib/zemu/instance.rb', line 426 def method_missing(method, *args) if @device_methods.include? method return @wrapper.send(method) end super end |
Instance Method Details
#break(address, type) ⇒ Object
Set a breakpoint of the given type at the given address.
325 326 327 |
# File 'lib/zemu/instance.rb', line 325 def break(address, type) @breakpoints[address] = true end |
#break? ⇒ Boolean
Returns true if a breakpoint has been hit, false otherwise.
364 365 366 |
# File 'lib/zemu/instance.rb', line 364 def break? return @state == RunState::BREAK end |
#clock_speed ⇒ Object
Returns the clock speed of this instance in Hz.
181 182 183 |
# File 'lib/zemu/instance.rb', line 181 def clock_speed return @clock end |
#continue(run_cycles = -1)) ⇒ Object
Continue running this instance until either:
-
A HALT instruction is executed
-
A breakpoint is hit
-
The number of cycles given has been executed
Returns the number of cycles executed.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/zemu/instance.rb', line 285 def continue(run_cycles=-1) # Return immediately if we're HALTED. return if @state == RunState::HALTED cycles_executed = 0 @state = RunState::RUNNING # Run as long as: # We haven't hit a breakpoint # We haven't halted # We haven't hit the number of cycles we've been told to execute for. while (run_cycles == -1 || cycles_executed < run_cycles) && (@state == RunState::RUNNING) cycles_executed += @wrapper.zemu_debug_step(@instance) pc = @wrapper.zemu_debug_pc(@instance) # If there's a tracepoint at this address, # execute the associated proc. unless @tracepoints[pc].nil? @tracepoints[pc].call(self) end # If the PC is now pointing to one of our breakpoints, # we're in the BREAK state. if @breakpoints[pc] @state = RunState::BREAK elsif @wrapper.zemu_debug_halted() @state = RunState::HALTED end end return cycles_executed end |
#device(name) ⇒ Object
Returns the device with the given name, or nil if no such device exists.
170 171 172 173 174 175 176 177 178 |
# File 'lib/zemu/instance.rb', line 170 def device(name) @devices.each do |d| if d.name == name return d end end nil end |
#drive_readbyte(sector, offset) ⇒ Object
Get a byte from the attached disk drive.
Gets a byte at the given offset in the given sector.
275 276 277 |
# File 'lib/zemu/instance.rb', line 275 def drive_readbyte(sector, offset) return @wrapper.zemu_io_block_drive_readbyte(sector, offset) end |
#halted? ⇒ Boolean
Returns true if the CPU has halted, false otherwise.
359 360 361 |
# File 'lib/zemu/instance.rb', line 359 def halted? return @state == RunState::HALTED end |
#memory(address) ⇒ Object
Access the value in memory at a given address.
Returns 0 if the memory address is not mapped, otherwise returns the value in the given memory location.
218 219 220 |
# File 'lib/zemu/instance.rb', line 218 def memory(address) return @wrapper.zemu_debug_get_memory(address) end |
#quit ⇒ Object
Powers off the emulated CPU and destroys this instance.
369 370 371 372 |
# File 'lib/zemu/instance.rb', line 369 def quit @wrapper.zemu_power_off(@instance) @wrapper.zemu_free(@instance) end |
#registers ⇒ Object
Returns a hash containing current values of the emulated machine’s registers. All names are as those given in the Z80 reference manual.
16-bit general-purpose registers must be accessed by their 8-bit component registers.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/zemu/instance.rb', line 196 def registers r = {} REGISTERS.each do |reg, num| r[reg] = @wrapper.zemu_debug_register(@instance, num) end REGISTERS_EXTENDED.each do |reg, components| hi = components[0] lo = components[1] r[reg] = (r[hi] << 8) | r[lo] end return r end |
#remove_break(address, type) ⇒ Object
Remove a breakpoint of the given type at the given address. Does nothing if no breakpoint previously existed at that address.
354 355 356 |
# File 'lib/zemu/instance.rb', line 354 def remove_break(address, type) @breakpoints[address] = false end |
#serial_delay ⇒ Object
Returns the delay between characters on the serial port for this instance in seconds.
186 187 188 |
# File 'lib/zemu/instance.rb', line 186 def serial_delay return @serial_delay end |
#serial_gets(count = nil) ⇒ Object
Get a number of characters from the serial line of the emulated CPU.
Gets the given number of characters from the emulated machine’s send buffer.
Note: If count is greater than the number of characters currently in the buffer, the returned string will be shorter than the given count.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/zemu/instance.rb', line 253 def serial_gets(count=nil) return_string = "" actual_count = device('serial').transmitted_count() if count.nil? || actual_count < count count = actual_count end count.to_i.times do return_string += device('serial').get_byte().chr end return return_string end |
#serial_puts(string) ⇒ Object
Write a string to the serial line of the emulated CPU.
Sends each character in the string to the receive buffer of the emulated machine.
238 239 240 241 242 |
# File 'lib/zemu/instance.rb', line 238 def serial_puts(string) string.each_char do |c| device('serial').put_byte(c.ord) end end |
#set_memory(address, value) ⇒ Object
Set the value in memory at a given address.
Returns nothing.
228 229 230 |
# File 'lib/zemu/instance.rb', line 228 def set_memory(address, value) @wrapper.zemu_debug_set_memory(address, value) end |
#trace(address, &block) ⇒ Object
Set a tracepoint to execute the given block at an address.
340 341 342 343 344 345 346 347 |
# File 'lib/zemu/instance.rb', line 340 def trace(address, &block) if block.arity != 1 raise InstanceError, "Wrong arity for tracepoint - expected 1, got #{block.arity}" end @tracepoints[address] = block end |