Class: Zemu::Instance

Inherits:
Object
  • Object
show all
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

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.

Parameters:

  • address

    The address of the breakpoint

  • type

    The type of breakpoint:

    • :program => Break when the program counter hits the address given.



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.

Returns:

  • (Boolean)


364
365
366
# File 'lib/zemu/instance.rb', line 364

def break?
    return @state == RunState::BREAK
end

#clock_speedObject

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.

Parameters:

  • sector

    The sector to read

  • offset

    The offset in sector to read



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.

Returns:

  • (Boolean)


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.

Parameters:

  • address

    The address in memory to be accessed.



218
219
220
# File 'lib/zemu/instance.rb', line 218

def memory(address)
    return @wrapper.zemu_debug_get_memory(address)
end

#quitObject

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

#registersObject

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.

Parameters:

  • address

    The address of the breakpoint to be removed.

  • type

    The type of breakpoint. See Instance#break.



354
355
356
# File 'lib/zemu/instance.rb', line 354

def remove_break(address, type)
    @breakpoints[address] = false
end

#serial_delayObject

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.

Parameters:

  • count (defaults to: nil)

    The number of characters to get, or nil if all characters in the buffer should be retrieved.



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.

Parameters:

  • string

    The string to be sent.



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.

Parameters:

  • address

    The address in memory to be set.

  • value

    The value to set to.



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.

Examples:

instance.trace(0x1234) do |i|
    puts "HL value: 04x" % i.registers["HL"]
end

Parameters:

  • address

    The address of the tracepoint

  • block

    The block to execute at the tracepoint. The block takes the instance as a parameter.



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