zemu 0.2.2 → 0.3.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/zemu.rb +1 -1
- data/lib/zemu/config.rb +80 -10
- data/lib/zemu/instance.rb +65 -8
- data/lib/zemu/interactive.rb +11 -3
- data/src/debug.c +13 -61
- data/src/debug.h +5 -17
- data/src/io.c.erb +22 -0
- data/src/io.h.erb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3278a3fa602a14489f1bfe77bee8d51781be503fac50dba4334dd400f492b5ca
|
4
|
+
data.tar.gz: 0dc3644e09613e7449bbc2dd3167f4823fca647cd0b5df94b35a0d39768a8b26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb87eef723a62c64742f06ec85bf7f5c976103c6e5b8678b5efec665b49ea3242efe59238f1ea28808c7ef3e597471ec050cda5f0152f3c74fa900f88df294ea
|
7
|
+
data.tar.gz: 29de4f2e78acae5a24a443595e3b683acb3fd3a6d00ec84991c1126e3e7b07ce35078de8721347ec84926ee99fd2bd21912b1ef768fa868c0d1cc75ae966db02
|
data/lib/zemu.rb
CHANGED
@@ -128,7 +128,7 @@ module Zemu
|
|
128
128
|
|
129
129
|
includes_str += " -I" + autogen
|
130
130
|
|
131
|
-
command = "#{compiler} -Werror -Wno-unknown-warning-option -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup #{includes_str} #{defines_str} -o #{output} #{inputs_str}"
|
131
|
+
command = "#{compiler} -O2 -Werror -Wno-unknown-warning-option -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup #{includes_str} #{defines_str} -o #{output} #{inputs_str}"
|
132
132
|
|
133
133
|
# Run the compiler and generate a library.
|
134
134
|
return system(command)
|
data/lib/zemu/config.rb
CHANGED
@@ -228,6 +228,10 @@ module Zemu
|
|
228
228
|
end
|
229
229
|
|
230
230
|
@ports = []
|
231
|
+
@setup_block = nil
|
232
|
+
@read_block = nil
|
233
|
+
@write_block = nil
|
234
|
+
@clock_block = nil
|
231
235
|
|
232
236
|
super
|
233
237
|
end
|
@@ -269,28 +273,46 @@ module Zemu
|
|
269
273
|
@write_block = block
|
270
274
|
end
|
271
275
|
|
276
|
+
# Defines the per-cycle behaviour of this IO device.
|
277
|
+
#
|
278
|
+
# Expects a block, the return value of which is a string
|
279
|
+
# defining the behaviour of the IO device on each system clock cycle.
|
280
|
+
# Care must be taken to ensure that this functionality does not conflict with that of
|
281
|
+
# any other IO devices.
|
282
|
+
#
|
283
|
+
# The block will be instance-evaluated at build-time, so it is possible to use
|
284
|
+
# instance variables of the IO device.
|
285
|
+
def when_clock(&block)
|
286
|
+
@clock_block = block
|
287
|
+
end
|
288
|
+
|
272
289
|
# Evaluates the when_setup block of this IO device and returns the resulting string.
|
273
290
|
def setup
|
274
|
-
instance_eval(&@setup_block)
|
291
|
+
return instance_eval(&@setup_block) unless @setup_block.nil?
|
292
|
+
return ""
|
275
293
|
end
|
276
294
|
|
277
295
|
# Evaluates the when_read block of this IO device and returns the resulting string.
|
278
296
|
def read
|
279
|
-
instance_eval(&@read_block)
|
297
|
+
return instance_eval(&@read_block) unless @read_block.nil?
|
298
|
+
return ""
|
280
299
|
end
|
281
300
|
|
282
301
|
# Evaluates the when_write block of this IO device and returns the resulting string.
|
283
302
|
def write
|
284
|
-
instance_eval(&@write_block)
|
303
|
+
return instance_eval(&@write_block) unless @write_block.nil?
|
304
|
+
return ""
|
305
|
+
end
|
306
|
+
|
307
|
+
# Evaluates the when_clock block of this IO device and returns the resulting string.
|
308
|
+
def clock
|
309
|
+
return instance_eval(&@clock_block) unless @clock_block.nil?
|
310
|
+
return ""
|
285
311
|
end
|
286
312
|
|
287
313
|
# Defines FFI API which will be available to the instance wrapper if this IO device is used.
|
288
314
|
def functions
|
289
|
-
[
|
290
|
-
{"name" => "zemu_io_#{name}_master_puts".to_sym, "args" => [:uint8], "return" => :void},
|
291
|
-
{"name" => "zemu_io_#{name}_master_gets".to_sym, "args" => [], "return" => :uint8},
|
292
|
-
{"name" => "zemu_io_#{name}_buffer_size".to_sym, "args" => [], "return" => :uint64}
|
293
|
-
]
|
315
|
+
[]
|
294
316
|
end
|
295
317
|
|
296
318
|
# Valid parameters for this object.
|
@@ -401,6 +423,15 @@ module Zemu
|
|
401
423
|
end
|
402
424
|
end
|
403
425
|
|
426
|
+
# Defines FFI API which will be available to the instance wrapper if this IO device is used.
|
427
|
+
def functions
|
428
|
+
[
|
429
|
+
{"name" => "zemu_io_#{name}_master_puts".to_sym, "args" => [:uint8], "return" => :void},
|
430
|
+
{"name" => "zemu_io_#{name}_master_gets".to_sym, "args" => [], "return" => :uint8},
|
431
|
+
{"name" => "zemu_io_#{name}_buffer_size".to_sym, "args" => [], "return" => :uint64}
|
432
|
+
]
|
433
|
+
end
|
434
|
+
|
404
435
|
# Valid parameters for a SerialPort, along with those
|
405
436
|
# defined in [Zemu::Config::IOPort].
|
406
437
|
def params
|
@@ -408,6 +439,44 @@ module Zemu
|
|
408
439
|
end
|
409
440
|
end
|
410
441
|
|
442
|
+
# Non-Maskable Interrupt Timer
|
443
|
+
#
|
444
|
+
# Represents a timer device, the period of which can be controlled
|
445
|
+
# by the CPU through an IO port. The timer generates an NMI once this
|
446
|
+
# period has expired. The timer can be reset via a control port.
|
447
|
+
class Timer < IOPort
|
448
|
+
def initialize
|
449
|
+
super
|
450
|
+
|
451
|
+
when_setup do
|
452
|
+
"zuint8 io_#{name}_count;\n" +
|
453
|
+
"zuint8 io_#{name}_running = 0;\n"
|
454
|
+
end
|
455
|
+
|
456
|
+
when_read do
|
457
|
+
end
|
458
|
+
|
459
|
+
when_write do
|
460
|
+
"if (port == #{count_port}) io_#{name}_count = value;\n" +
|
461
|
+
"else if (port == #{control_port}) io_#{name}_running = value;\n"
|
462
|
+
end
|
463
|
+
|
464
|
+
when_clock do
|
465
|
+
"if (io_#{name}_running)\n" +
|
466
|
+
"{\n" +
|
467
|
+
" if (io_#{name}_count > 0) io_#{name}_count--;\n" +
|
468
|
+
" else zemu_io_nmi(instance);\n" +
|
469
|
+
"}\n"
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# Valid parameters for a Timer, along with those defined in
|
474
|
+
# [Zemu::Config::IOPort].
|
475
|
+
def params
|
476
|
+
super + %w(count_port control_port)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
411
480
|
# Gets a binding for this object.
|
412
481
|
def get_binding
|
413
482
|
return binding
|
@@ -421,7 +490,7 @@ module Zemu
|
|
421
490
|
|
422
491
|
# Parameters accessible by this configuration object.
|
423
492
|
def params
|
424
|
-
return %w(name compiler output_directory clock_speed)
|
493
|
+
return %w(name compiler output_directory clock_speed serial_delay)
|
425
494
|
end
|
426
495
|
|
427
496
|
# Initial value for parameters of this configuration object.
|
@@ -429,7 +498,8 @@ module Zemu
|
|
429
498
|
return {
|
430
499
|
"compiler" => "clang",
|
431
500
|
"output_directory" => "bin",
|
432
|
-
"clock_speed" => 0
|
501
|
+
"clock_speed" => 0,
|
502
|
+
"serial_delay" => 0
|
433
503
|
}
|
434
504
|
end
|
435
505
|
|
data/lib/zemu/instance.rb
CHANGED
@@ -37,8 +37,24 @@ module Zemu
|
|
37
37
|
"L'" => 19
|
38
38
|
}
|
39
39
|
|
40
|
+
# States that the emulated machine can be in.
|
41
|
+
class RunState
|
42
|
+
# Currently executing an instruction.
|
43
|
+
RUNNING = 0
|
44
|
+
|
45
|
+
# Executed a HALT instruction in the previous cycle.
|
46
|
+
HALTED = 1
|
47
|
+
|
48
|
+
# Hit a breakpoint in the previous cycle.
|
49
|
+
BREAK = 2
|
50
|
+
|
51
|
+
# Undefined. Emulated machine has not yet reached a well-defined state.
|
52
|
+
UNDEFINED = -1
|
53
|
+
end
|
54
|
+
|
40
55
|
def initialize(configuration)
|
41
56
|
@clock = configuration.clock_speed
|
57
|
+
@serial_delay = configuration.serial_delay
|
42
58
|
|
43
59
|
@wrapper = make_wrapper(configuration)
|
44
60
|
|
@@ -47,6 +63,10 @@ module Zemu
|
|
47
63
|
@instance = @wrapper.zemu_init
|
48
64
|
@wrapper.zemu_power_on(@instance)
|
49
65
|
@wrapper.zemu_reset(@instance)
|
66
|
+
|
67
|
+
@state = RunState::UNDEFINED
|
68
|
+
|
69
|
+
@breakpoints = {}
|
50
70
|
end
|
51
71
|
|
52
72
|
# Returns the clock speed of this instance in Hz.
|
@@ -54,6 +74,11 @@ module Zemu
|
|
54
74
|
return @clock
|
55
75
|
end
|
56
76
|
|
77
|
+
# Returns the delay between characters on the serial port for this instance in seconds.
|
78
|
+
def serial_delay
|
79
|
+
return @serial_delay
|
80
|
+
end
|
81
|
+
|
57
82
|
# Returns a hash containing current values of the emulated
|
58
83
|
# machine's registers. All names are as those given in the Z80
|
59
84
|
# reference manual.
|
@@ -124,7 +149,32 @@ module Zemu
|
|
124
149
|
#
|
125
150
|
# Returns the number of cycles executed.
|
126
151
|
def continue(run_cycles=-1)
|
127
|
-
|
152
|
+
# Return immediately if we're HALTED.
|
153
|
+
return if @state == RunState::HALTED
|
154
|
+
|
155
|
+
cycles_executed = 0
|
156
|
+
|
157
|
+
@state = RunState::RUNNING
|
158
|
+
|
159
|
+
# Run as long as:
|
160
|
+
# We haven't hit a breakpoint
|
161
|
+
# We haven't halted
|
162
|
+
# We haven't hit the number of cycles we've been told to execute for.
|
163
|
+
while (run_cycles == -1 || cycles_executed < run_cycles) && (@state == RunState::RUNNING)
|
164
|
+
cycles_executed += @wrapper.zemu_debug_step(@instance)
|
165
|
+
|
166
|
+
pc = @wrapper.zemu_debug_pc(@instance)
|
167
|
+
|
168
|
+
# If the PC is now pointing to one of our breakpoints,
|
169
|
+
# we're in the BREAK state.
|
170
|
+
if @breakpoints[pc]
|
171
|
+
@state = RunState::BREAK
|
172
|
+
elsif @wrapper.zemu_debug_halted()
|
173
|
+
@state = RunState::HALTED
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
return cycles_executed
|
128
178
|
end
|
129
179
|
|
130
180
|
# Set a breakpoint of the given type at the given address.
|
@@ -133,17 +183,26 @@ module Zemu
|
|
133
183
|
# @param type The type of breakpoint:
|
134
184
|
# * :program => Break when the program counter hits the address given.
|
135
185
|
def break(address, type)
|
136
|
-
@
|
186
|
+
@breakpoints[address] = true
|
187
|
+
end
|
188
|
+
|
189
|
+
# Remove a breakpoint of the given type at the given address.
|
190
|
+
# Does nothing if no breakpoint previously existed at that address.
|
191
|
+
#
|
192
|
+
# @param address The address of the breakpoint to be removed.
|
193
|
+
# @param type The type of breakpoint. See Instance#break.
|
194
|
+
def remove_break(address, type)
|
195
|
+
@breakpoints[address] = false
|
137
196
|
end
|
138
197
|
|
139
198
|
# Returns true if the CPU has halted, false otherwise.
|
140
199
|
def halted?
|
141
|
-
return @
|
200
|
+
return @state == RunState::HALTED
|
142
201
|
end
|
143
202
|
|
144
203
|
# Returns true if a breakpoint has been hit, false otherwise.
|
145
204
|
def break?
|
146
|
-
return @
|
205
|
+
return @state == RunState::BREAK
|
147
206
|
end
|
148
207
|
|
149
208
|
# Powers off the emulated CPU and destroys this instance.
|
@@ -168,14 +227,12 @@ module Zemu
|
|
168
227
|
|
169
228
|
wrapper.attach_function :zemu_reset, [:pointer], :void
|
170
229
|
|
171
|
-
wrapper.attach_function :
|
230
|
+
wrapper.attach_function :zemu_debug_step, [:pointer], :uint64
|
172
231
|
|
173
232
|
wrapper.attach_function :zemu_debug_halted, [], :bool
|
174
|
-
wrapper.attach_function :zemu_debug_break, [], :bool
|
175
|
-
|
176
|
-
wrapper.attach_function :zemu_debug_set_breakpoint, [:uint16], :void
|
177
233
|
|
178
234
|
wrapper.attach_function :zemu_debug_register, [:pointer, :uint16], :uint16
|
235
|
+
wrapper.attach_function :zemu_debug_pc, [:pointer], :uint16
|
179
236
|
|
180
237
|
wrapper.attach_function :zemu_debug_get_memory, [:uint16], :uint8
|
181
238
|
|
data/lib/zemu/interactive.rb
CHANGED
@@ -114,13 +114,19 @@ module Zemu
|
|
114
114
|
cycles_left = cycles
|
115
115
|
actual_cycles = 0
|
116
116
|
|
117
|
+
serial_count = @instance.serial_delay
|
118
|
+
|
117
119
|
while ((cycles == -1) || (cycles_left > 0))
|
118
120
|
# Get time before execution.
|
119
121
|
start = Time.now
|
120
122
|
|
121
123
|
old_pc = r16("PC")
|
122
124
|
|
123
|
-
|
125
|
+
if (serial_count >= @instance.serial_delay)
|
126
|
+
process_serial
|
127
|
+
serial_count = 0
|
128
|
+
end
|
129
|
+
|
124
130
|
cycles_done = @instance.continue(1)
|
125
131
|
cycles_left -= cycles_done
|
126
132
|
actual_cycles += cycles_done
|
@@ -132,7 +138,9 @@ module Zemu
|
|
132
138
|
if @instance.clock_speed > 0
|
133
139
|
elapsed = ending - start
|
134
140
|
|
135
|
-
execution_time =
|
141
|
+
execution_time = cycles_done * (1.0/@instance.clock_speed)
|
142
|
+
serial_count += execution_time
|
143
|
+
|
136
144
|
padding = execution_time - elapsed
|
137
145
|
sleep(padding) unless padding < 0
|
138
146
|
end
|
@@ -169,7 +177,7 @@ module Zemu
|
|
169
177
|
|
170
178
|
(address.to_i(16)...address.to_i(16) + size.to_i(16)).each do |a|
|
171
179
|
m = @instance.memory(a)
|
172
|
-
if (m < 32)
|
180
|
+
if (m < 32 || m > 126)
|
173
181
|
log "%04x: %02x ." % [a, m]
|
174
182
|
else
|
175
183
|
log ("%04x: %02x " % [a, m]) + m.chr("UTF-8")
|
data/src/debug.c
CHANGED
@@ -1,69 +1,16 @@
|
|
1
1
|
#include "debug.h"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
/* Currently, the number of breakpoints is defined statically.
|
6
|
-
* Perhaps in future there will be an unlimited number.
|
7
|
-
*/
|
8
|
-
zuint16 breakpoints[ZEMU_DEBUG_MAX_BREAKPOINTS];
|
9
|
-
unsigned int breakpoint_count = 0;
|
10
|
-
|
11
|
-
zusize zemu_debug_continue(Z80 * instance, zinteger run_cycles)
|
12
|
-
{
|
13
|
-
/* Return if we've halted. */
|
14
|
-
if (zemu_debug_state == HALTED) return 0;
|
15
|
-
|
16
|
-
zusize cycles = 0;
|
17
|
-
|
18
|
-
zemu_debug_state = RUNNING;
|
19
|
-
|
20
|
-
/* Run as long as:
|
21
|
-
* We don't hit a breakpoint
|
22
|
-
* We haven't run for more than the number of cycles given
|
23
|
-
*/
|
24
|
-
while (zemu_debug_state == RUNNING && (run_cycles < 0 || cycles < run_cycles))
|
25
|
-
{
|
26
|
-
cycles += zemu_debug_step(instance);
|
27
|
-
|
28
|
-
/* See if the Program Counter now matches any address
|
29
|
-
* in the breakpoint array.
|
30
|
-
*/
|
31
|
-
for (unsigned int b = 0; b < breakpoint_count; b++)
|
32
|
-
{
|
33
|
-
if (instance->state.pc == breakpoints[b])
|
34
|
-
{
|
35
|
-
zemu_debug_state = BREAK;
|
36
|
-
}
|
37
|
-
}
|
38
|
-
}
|
39
|
-
|
40
|
-
return cycles;
|
41
|
-
}
|
3
|
+
zboolean halted = FALSE;
|
42
4
|
|
43
5
|
zusize zemu_debug_step(Z80 * instance)
|
44
6
|
{
|
45
7
|
/* Will run for at least one cycle. */
|
46
8
|
zusize cycles = z80_run(instance, 1);
|
47
9
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
void zemu_debug_halt(void * context, zboolean state)
|
52
|
-
{
|
53
|
-
if (state)
|
54
|
-
{
|
55
|
-
zemu_debug_state = HALTED;
|
56
|
-
}
|
57
|
-
else
|
58
|
-
{
|
59
|
-
zemu_debug_state = RUNNING;
|
60
|
-
}
|
61
|
-
}
|
10
|
+
/* Execute the per-cycle behaviour of the peripheral devices. */
|
11
|
+
for (zusize i = 0; i < cycles; i++) zemu_io_clock(instance);
|
62
12
|
|
63
|
-
|
64
|
-
{
|
65
|
-
breakpoints[breakpoint_count] = address;
|
66
|
-
breakpoint_count++;
|
13
|
+
return cycles;
|
67
14
|
}
|
68
15
|
|
69
16
|
zuint16 zemu_debug_register(Z80 * instance, zuint16 r)
|
@@ -106,14 +53,19 @@ zuint16 zemu_debug_register(Z80 * instance, zuint16 r)
|
|
106
53
|
}
|
107
54
|
}
|
108
55
|
|
109
|
-
|
56
|
+
zuint16 zemu_debug_pc(Z80 * instance)
|
57
|
+
{
|
58
|
+
return instance->state.pc;
|
59
|
+
}
|
60
|
+
|
61
|
+
void zemu_debug_halt(void * context, zboolean state)
|
110
62
|
{
|
111
|
-
|
63
|
+
halted = state;
|
112
64
|
}
|
113
65
|
|
114
|
-
zboolean
|
66
|
+
zboolean zemu_debug_halted(void)
|
115
67
|
{
|
116
|
-
return (
|
68
|
+
return (halted);
|
117
69
|
}
|
118
70
|
|
119
71
|
zuint8 zemu_debug_get_memory(zuint16 address)
|
data/src/debug.h
CHANGED
@@ -3,28 +3,16 @@
|
|
3
3
|
#include <stdio.h>
|
4
4
|
|
5
5
|
#include "memory.h"
|
6
|
-
|
7
|
-
/* Define number of breakpoints, if not done so already. */
|
8
|
-
#ifndef ZEMU_DEBUG_MAX_BREAKPOINTS
|
9
|
-
#define ZEMU_DEBUG_MAX_BREAKPOINTS 256
|
10
|
-
#endif
|
11
|
-
|
12
|
-
typedef enum RunState
|
13
|
-
{
|
14
|
-
RUNNING,
|
15
|
-
HALTED,
|
16
|
-
BREAK,
|
17
|
-
UNDEFINED
|
18
|
-
} RunState;
|
19
|
-
|
20
|
-
zusize zemu_debug_continue(Z80 * instance, zinteger run_cycles);
|
6
|
+
#include "io.h"
|
21
7
|
|
22
8
|
zusize zemu_debug_step(Z80 * instance);
|
23
9
|
|
24
10
|
void zemu_debug_halt(void * context, zboolean state);
|
25
11
|
|
26
12
|
zboolean zemu_debug_halted(void);
|
27
|
-
|
28
|
-
|
13
|
+
zboolean zemu_debug_break(void);
|
14
|
+
zboolean zemu_debug_running(void);
|
29
15
|
|
30
16
|
zuint16 zemu_debug_register(Z80 * instance, zuint16 r);
|
17
|
+
|
18
|
+
zuint16 zemu_debug_pc(Z80 * instance);
|
data/src/io.c.erb
CHANGED
@@ -4,6 +4,21 @@
|
|
4
4
|
<%= device.setup %>
|
5
5
|
<% end %>
|
6
6
|
|
7
|
+
void zemu_io_nmi(Z80 * instance)
|
8
|
+
{
|
9
|
+
z80_nmi(instance);
|
10
|
+
}
|
11
|
+
|
12
|
+
void zemu_io_int_on(Z80 * instance)
|
13
|
+
{
|
14
|
+
z80_int(instance, TRUE);
|
15
|
+
}
|
16
|
+
|
17
|
+
void zemu_io_int_off(Z80 * instance)
|
18
|
+
{
|
19
|
+
z80_int(instance, FALSE);
|
20
|
+
}
|
21
|
+
|
7
22
|
zuint8 zemu_io_in(void * context, zuint16 port)
|
8
23
|
{
|
9
24
|
/* Z80 IO ports occupy the lower half of the address bus.
|
@@ -28,3 +43,10 @@ void zemu_io_out(void * context, zuint16 port, zuint8 value)
|
|
28
43
|
<%= device.write %>
|
29
44
|
<% end %>
|
30
45
|
}
|
46
|
+
|
47
|
+
void zemu_io_clock(Z80 * instance)
|
48
|
+
{
|
49
|
+
<% io.each do |device| %>
|
50
|
+
<%= device.clock %>
|
51
|
+
<% end %>
|
52
|
+
}
|
data/src/io.h.erb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
#ifndef _ZEMU_IO_H
|
2
|
+
#define _ZEMU_IO_H
|
3
|
+
|
1
4
|
#include "emulation/CPU/Z80.h"
|
2
5
|
|
3
6
|
#ifndef ZEMU_IO_SERIAL_BUFFER_SIZE
|
@@ -16,3 +19,7 @@ zusize zemu_io_serial_buffer_size(void);
|
|
16
19
|
|
17
20
|
zuint8 zemu_io_in(void * context, zuint16 port);
|
18
21
|
void zemu_io_out(void * context, zuint16 port, zuint8 value);
|
22
|
+
void zemu_io_nmi(Z80 * instance);
|
23
|
+
void zemu_io_clock(Z80 * instance);
|
24
|
+
|
25
|
+
#endif
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zemu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jay Valentine
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Zemu is a gem which allows the user to configure a Z80-based system
|