zemu 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e21eb92ebce3eb8f03d9a17ddaec4f857989216a673791267602c0eb848319c7
4
- data.tar.gz: 3ff049b8e132ae33a3376f21768378f06d6bd59b8f5014b81ede14f50e37eda6
3
+ metadata.gz: caa59294431c1faadda8328bc7fbefa504526ae43a5531e2cf1d6c2bc7dd5c87
4
+ data.tar.gz: 72ced5e284f94e586862e80e97f5d95f55a9a3dbc931dc889fdc73352cef3a82
5
5
  SHA512:
6
- metadata.gz: 34706fc39b8212b6b3a07e077f5cd63479550e1832509bb01f742f86928c8c79844ce9689d4a11d79a2664d4ac13c6433f8a502b8806bf5e3390110009a27237
7
- data.tar.gz: 74082dc5aad00935a79f3af11db3b288179015ddce3ce4cbd96439771cd1bce4377f08cc069c23574474942dc4ba5dff736c44cc7cc263df0a6a007e715a6fe0
6
+ metadata.gz: dbb449fa4a076320a3073391f98687098e4e0f8b484099a7ca2c67325f857e8923397267dc01c5bee521cf66e9457149a8845e15f4a8b74ae7962e1f84623ae1
7
+ data.tar.gz: 846f7d09837e09fdb5711ba7d3ee09ca734cf0454c10efaff77a2d92de742e88332cc2c3e008db7026c528460368e410c9c9ebaacf29694e513880255085d579
data/lib/zemu/config.rb CHANGED
@@ -439,6 +439,215 @@ module Zemu
439
439
  end
440
440
  end
441
441
 
442
+ # Block drive object
443
+ #
444
+ # Represents a device with a sequence of sectors of a fixed size,
445
+ # which can be accessed via IO instructions as an IDE drive.
446
+ class BlockDrive < IOPort
447
+ # Constructor.
448
+ #
449
+ # Takes a block in which the parameters of the block drive
450
+ # can be initialized.
451
+ #
452
+ # All parameters can be set within this block.
453
+ # They become readonly as soon as the block completes.
454
+ #
455
+ # Constructor raises RangeError if a file is provided for initialization
456
+ # and it is of the wrong size.
457
+ #
458
+ # @example
459
+ #
460
+ # Zemu::Config::BlockDrive.new do
461
+ # name "drive"
462
+ # base_port 0x0c
463
+ # sector_size 512
464
+ # num_sectors 64
465
+ # end
466
+ #
467
+ #
468
+ def initialize
469
+ @blocks = []
470
+ @initialize_from = nil
471
+
472
+ super
473
+
474
+ num_sectors.times do
475
+ sector = []
476
+ sector_size.times do
477
+ sector << 0
478
+ end
479
+ @blocks << sector
480
+ end
481
+
482
+ # Initialize from provided file if applicable.
483
+ unless @initialize_from.nil?
484
+ # Check file size.
485
+ file_size = File.size(@initialize_from)
486
+ if (file_size != num_sectors * sector_size)
487
+ raise RangeError, "Initialization file for Zemu::Config::BlockDrive '#{name}' is of wrong size."
488
+ end
489
+
490
+ File.open(@initialize_from, "rb") do |f|
491
+ num_sectors.times do |s|
492
+ sector_size.times do |b|
493
+ @blocks[s][b] = f.getbyte()
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ when_setup do
500
+ <<-eos
501
+ #include <stdio.h>
502
+
503
+ zuint8 sector_data_#{name}[#{sector_size}];
504
+ zuint32 sector_data_#{name}_offset;
505
+ zuint32 loaded_sector_#{name} = Z_UINT32_MAXIMUM;
506
+ zuint8 drive_mode_#{name};
507
+ zuint8 drive_status_#{name} = 0b01000000;
508
+
509
+ zuint8 lba_#{name}_0;
510
+ zuint8 lba_#{name}_1;
511
+ zuint8 lba_#{name}_2;
512
+ zuint8 lba_#{name}_3;
513
+
514
+ void internal_#{name}_load_sector(zuint32 sector)
515
+ {
516
+ if (loaded_sector_#{name} == sector) return;
517
+
518
+ FILE * fptr = fopen("#{@initialize_from}", "rb");
519
+ fseek(fptr, sector * #{sector_size}, SEEK_SET);
520
+ fread(sector_data_#{name}, #{sector_size}, 1, fptr);
521
+ fclose(fptr);
522
+
523
+ loaded_sector_#{name} = sector;
524
+ }
525
+
526
+ void internal_#{name}_write_current_sector()
527
+ {
528
+ FILE * fptr = fopen("#{@initialize_from}", "r+b");
529
+ fseek(fptr, loaded_sector_#{name} * #{sector_size}, SEEK_SET);
530
+ fwrite(sector_data_#{name}, 1, #{sector_size}, fptr);
531
+ fclose(fptr);
532
+ }
533
+
534
+ zuint8 zemu_io_#{name}_readbyte(zuint32 sector, zuint32 offset)
535
+ {
536
+ internal_#{name}_load_sector(sector);
537
+ return sector_data_#{name}[offset];
538
+ }
539
+ eos
540
+ end
541
+
542
+ when_read do
543
+ <<-eos
544
+ if (port == #{base_port})
545
+ {
546
+ zuint8 b = sector_data_#{name}[sector_data_#{name}_offset];
547
+ sector_data_#{name}_offset++;
548
+ if (sector_data_#{name}_offset >= #{sector_size})
549
+ {
550
+ drive_status_#{name} = 0b01000000;
551
+ }
552
+ return b;
553
+ }
554
+ else if (port == #{base_port+7})
555
+ {
556
+ return drive_status_#{name};
557
+ }
558
+ eos
559
+ end
560
+
561
+ when_write do
562
+ <<-eos
563
+ if (port == #{base_port})
564
+ {
565
+ if (drive_mode_#{name} == 0x01)
566
+ {
567
+ sector_data_#{name}[sector_data_#{name}_offset] = value;
568
+ sector_data_#{name}_offset++;
569
+ if (sector_data_#{name}_offset >= #{sector_size})
570
+ {
571
+ internal_#{name}_write_current_sector();
572
+ drive_status_#{name} = 0b01000000;
573
+ }
574
+ }
575
+ }
576
+ else if (port == #{base_port+3})
577
+ {
578
+ lba_#{name}_0 = value;
579
+ }
580
+ else if (port == #{base_port+4})
581
+ {
582
+ lba_#{name}_1 = value;
583
+ }
584
+ else if (port == #{base_port+5})
585
+ {
586
+ lba_#{name}_2 = value;
587
+ }
588
+ else if (port == #{base_port+6})
589
+ {
590
+ lba_#{name}_3 = value & 0b00011111;
591
+ }
592
+ else if (port == #{base_port+7})
593
+ {
594
+ if (value == 0x20)
595
+ {
596
+ zuint32 sector = 0;
597
+ sector |= (zuint32)lba_#{name}_3 << 24;
598
+ sector |= (zuint32)lba_#{name}_2 << 16;
599
+ sector |= (zuint32)lba_#{name}_1 << 8;
600
+ sector |= (zuint32)lba_#{name}_0;
601
+
602
+ internal_#{name}_load_sector(sector);
603
+ sector_data_#{name}_offset = 0;
604
+
605
+ drive_mode_#{name} = 0x00;
606
+ drive_status_#{name} = 0b00001000;
607
+ }
608
+ else if (value == 0x30)
609
+ {
610
+ zuint32 sector = 0;
611
+ sector |= (zuint32)lba_#{name}_3 << 24;
612
+ sector |= (zuint32)lba_#{name}_2 << 16;
613
+ sector |= (zuint32)lba_#{name}_1 << 8;
614
+ sector |= (zuint32)lba_#{name}_0;
615
+
616
+ internal_#{name}_load_sector(sector);
617
+ sector_data_#{name}_offset = 0;
618
+
619
+ drive_mode_#{name} = 0x01;
620
+ drive_status_#{name} = 0b00001000;
621
+ }
622
+ }
623
+ eos
624
+ end
625
+ end
626
+
627
+ # Array of sectors of this drive.
628
+ def blocks
629
+ @blocks
630
+ end
631
+
632
+ # Set file to initialize from.
633
+ def initialize_from(file)
634
+ @initialize_from = file
635
+ end
636
+
637
+ # Defines FFI API which will be available to the instance wrapper if this IO device is used.
638
+ def functions
639
+ [
640
+ {"name" => "zemu_io_#{name}_readbyte", "args" => [:uint32, :uint32], "return" => :uint8},
641
+ ]
642
+ end
643
+
644
+ # Valid parameters for a BlockDrive, along with those
645
+ # defined in [Zemu::Config::IOPort].
646
+ def params
647
+ super + %w(base_port sector_size num_sectors)
648
+ end
649
+ end
650
+
442
651
  # Non-Maskable Interrupt Timer
443
652
  #
444
653
  # Represents a timer device, the period of which can be controlled
@@ -490,7 +699,7 @@ module Zemu
490
699
 
491
700
  # Parameters accessible by this configuration object.
492
701
  def params
493
- return %w(name compiler output_directory clock_speed)
702
+ return %w(name compiler output_directory clock_speed serial_delay)
494
703
  end
495
704
 
496
705
  # Initial value for parameters of this configuration object.
@@ -498,7 +707,8 @@ module Zemu
498
707
  return {
499
708
  "compiler" => "clang",
500
709
  "output_directory" => "bin",
501
- "clock_speed" => 0
710
+ "clock_speed" => 0,
711
+ "serial_delay" => 0
502
712
  }
503
713
  end
504
714
 
data/lib/zemu/debug.rb ADDED
@@ -0,0 +1,68 @@
1
+ module Zemu
2
+ # Handles debugging functionality, like mapping of symbols to addresses,
3
+ # disassembling of instructions, etc.
4
+ module Debug
5
+ # Loads a map file at the given path, and returns a hash of address => Symbol
6
+ # for the symbols defined within.
7
+ def self.load_map(path)
8
+ symbols = {}
9
+
10
+ File.open(path, "r") do |f|
11
+ f.each_line do |l|
12
+ s = Symbol.parse(l)
13
+
14
+ if symbols[s.address].nil?
15
+ symbols[s.address] = []
16
+ end
17
+
18
+ symbols[s.address] << s
19
+ symbols[s.address].sort_by!(&:label)
20
+ end
21
+ end
22
+
23
+ return symbols
24
+ end
25
+
26
+ # Represents a symbol definition, of the form `label = address`.
27
+ class Symbol
28
+ # Parse a symbol definition, returning a Symbol instance.
29
+ def self.parse(s)
30
+ # Split on whitespace.
31
+ tokens = s.to_s.split(' ')
32
+
33
+ if tokens.size < 3
34
+ raise ArgumentError, "Invalid symbol definition: '#{s}'"
35
+ end
36
+
37
+ label = tokens[0]
38
+
39
+ address = nil
40
+
41
+ if /0x[0-9a-fA-F]+/ =~ tokens[2]
42
+ address = tokens[2][2..-1].to_i(16)
43
+ elsif /\$[0-9a-fA-F]+/ =~ tokens[2]
44
+ address = tokens[2][1..-1].to_i(16)
45
+ elsif /\d+/ =~ tokens[2]
46
+ address = tokens[2].to_i
47
+ end
48
+
49
+ if address.nil?
50
+ raise ArgumentError, "Invalid symbol address: '#{tokens[2]}'"
51
+ end
52
+
53
+ return self.new(label, address)
54
+ end
55
+
56
+ # Textual label for this symbol.
57
+ attr_reader :label
58
+
59
+ # Address of this symbol in the binary.
60
+ attr_reader :address
61
+
62
+ def initialize(label, address)
63
+ @label = label
64
+ @address = address
65
+ end
66
+ end
67
+ end
68
+ end
data/lib/zemu/instance.rb CHANGED
@@ -54,6 +54,7 @@ module Zemu
54
54
 
55
55
  def initialize(configuration)
56
56
  @clock = configuration.clock_speed
57
+ @serial_delay = configuration.serial_delay
57
58
 
58
59
  @wrapper = make_wrapper(configuration)
59
60
 
@@ -65,7 +66,7 @@ module Zemu
65
66
 
66
67
  @state = RunState::UNDEFINED
67
68
 
68
- @breakpoints = []
69
+ @breakpoints = {}
69
70
  end
70
71
 
71
72
  # Returns the clock speed of this instance in Hz.
@@ -73,6 +74,11 @@ module Zemu
73
74
  return @clock
74
75
  end
75
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
+
76
82
  # Returns a hash containing current values of the emulated
77
83
  # machine's registers. All names are as those given in the Z80
78
84
  # reference manual.
@@ -136,6 +142,16 @@ module Zemu
136
142
  return return_string
137
143
  end
138
144
 
145
+ # Get a byte from the attached disk drive.
146
+ #
147
+ # @param sector The sector to read
148
+ # @param offset The offset in sector to read
149
+ #
150
+ # Gets a byte at the given offset in the given sector.
151
+ def drive_readbyte(sector, offset)
152
+ return @wrapper.zemu_io_block_drive_readbyte(sector, offset)
153
+ end
154
+
139
155
  # Continue running this instance until either:
140
156
  # * A HALT instruction is executed
141
157
  # * A breakpoint is hit
@@ -161,7 +177,7 @@ module Zemu
161
177
 
162
178
  # If the PC is now pointing to one of our breakpoints,
163
179
  # we're in the BREAK state.
164
- if (@breakpoints.select { |b| b == pc }.size) > 0
180
+ if @breakpoints[pc]
165
181
  @state = RunState::BREAK
166
182
  elsif @wrapper.zemu_debug_halted()
167
183
  @state = RunState::HALTED
@@ -177,7 +193,7 @@ module Zemu
177
193
  # @param type The type of breakpoint:
178
194
  # * :program => Break when the program counter hits the address given.
179
195
  def break(address, type)
180
- @breakpoints << address
196
+ @breakpoints[address] = true
181
197
  end
182
198
 
183
199
  # Remove a breakpoint of the given type at the given address.
@@ -186,7 +202,7 @@ module Zemu
186
202
  # @param address The address of the breakpoint to be removed.
187
203
  # @param type The type of breakpoint. See Instance#break.
188
204
  def remove_break(address, type)
189
- @breakpoints.reject! { |b| b == address }
205
+ @breakpoints[address] = false
190
206
  end
191
207
 
192
208
  # Returns true if the CPU has halted, false otherwise.
@@ -232,7 +248,7 @@ module Zemu
232
248
 
233
249
  configuration.io.each do |device|
234
250
  device.functions.each do |f|
235
- wrapper.attach_function(f["name"], f["args"], f["return"])
251
+ wrapper.attach_function(f["name"].to_sym, f["args"], f["return"])
236
252
  end
237
253
  end
238
254
 
@@ -8,6 +8,8 @@ module Zemu
8
8
  def initialize(instance)
9
9
  @instance = instance
10
10
 
11
+ @symbol_table = {}
12
+
11
13
  @master, @slave = PTY.open
12
14
  log "Opened PTY at #{@slave.path}"
13
15
  end
@@ -59,6 +61,9 @@ module Zemu
59
61
  memory(cmd[1], cmd[2])
60
62
  end
61
63
 
64
+ elsif cmd[0] == "map"
65
+ load_map(cmd[1])
66
+
62
67
  elsif cmd[0] == "help"
63
68
  log "Available commands:"
64
69
  log " continue [<n>] - Continue execution for <n> cycles"
@@ -66,6 +71,7 @@ module Zemu
66
71
  log " registers - View register contents"
67
72
  log " memory <a> [<n>] - View <n> bytes of memory, starting at address <a>."
68
73
  log " <n> defaults to 1 if omitted."
74
+ log " map <path> - Load symbols from map file at <path>"
69
75
  log " break <a> - Set a breakpoint at the given address <a>."
70
76
  log " quit - End this emulator instance."
71
77
 
@@ -79,16 +85,53 @@ module Zemu
79
85
  end
80
86
 
81
87
  # Outputs a table giving the current values of the instance's registers.
88
+ # For the 16-bit registers (BC, DE, HL, IX, IY, SP, PC), attempts to identify the symbol
89
+ # to which they point.
82
90
  def registers
83
91
  log "A: #{r("A")} F: #{r("F")}"
84
- log "B: #{r("B")} C: #{r("C")}"
85
- log "D: #{r("D")} E: #{r("E")}"
86
- log "H: #{r("H")} L: #{r("L")}"
92
+
93
+ registers_gp('B', 'C')
94
+ registers_gp('D', 'E')
95
+ registers_gp('H', 'L')
96
+
87
97
  log ""
88
- log "IX: #{r16("IX")}"
89
- log "IY: #{r16("IY")}"
90
- log "SP: #{r16("SP")}"
91
- log "PC: #{r16("PC")}"
98
+
99
+ register_16("IX")
100
+ register_16("IY")
101
+ register_16("SP")
102
+ register_16("PC")
103
+ end
104
+
105
+ # Displays the value of a 16-bit register.
106
+ def register_16(r)
107
+ value = @instance.registers[r]
108
+
109
+ log "#{r}: #{r16(r)} (#{get_symbol(value)})"
110
+ end
111
+
112
+ # Displays the value of a general-purpose 16-bit register pair.
113
+ def registers_gp(hi, lo)
114
+ value = hilo(@instance.registers[hi], @instance.registers[lo])
115
+
116
+ log "#{hi}: #{r(hi)} #{lo}: #{r(lo)} (#{get_symbol(value)})"
117
+ end
118
+
119
+ # Gets the symbol associated with the given value.
120
+ # If no matching symbol, gives offset from previous symbol.
121
+ def get_symbol(value)
122
+ syms = nil
123
+ addr = value
124
+ while addr > 0 do
125
+ syms = @symbol_table[addr]
126
+ break unless syms.nil?
127
+ addr -= 1
128
+ end
129
+
130
+ sym = if syms.nil? then nil else syms[0] end
131
+
132
+ sym_str = "<#{if sym.nil? then 'undefined' else sym.label end}#{if addr == value then '' else "+#{value-addr}" end}>"
133
+
134
+ return sym_str
92
135
  end
93
136
 
94
137
  # Returns a particular 8-bit register value.
@@ -101,6 +144,11 @@ module Zemu
101
144
  return "0x%04x" % @instance.registers[reg]
102
145
  end
103
146
 
147
+ # Concatenates two 8-bit values, in big-endian format.
148
+ def hilo(hi, lo)
149
+ return (hi << 8) | lo
150
+ end
151
+
104
152
  # Continue for *up to* the given number of cycles.
105
153
  # Fewer cycles may be executed, depending on the behaviour of the processor.
106
154
  def continue(cycles=-1)
@@ -114,13 +162,19 @@ module Zemu
114
162
  cycles_left = cycles
115
163
  actual_cycles = 0
116
164
 
165
+ serial_count = @instance.serial_delay.to_f
166
+
117
167
  while ((cycles == -1) || (cycles_left > 0))
118
168
  # Get time before execution.
119
169
  start = Time.now
120
170
 
121
171
  old_pc = r16("PC")
122
172
 
123
- process_serial
173
+ if (serial_count >= @instance.serial_delay)
174
+ process_serial
175
+ serial_count = 0.0
176
+ end
177
+
124
178
  cycles_done = @instance.continue(1)
125
179
  cycles_left -= cycles_done
126
180
  actual_cycles += cycles_done
@@ -132,7 +186,9 @@ module Zemu
132
186
  if @instance.clock_speed > 0
133
187
  elapsed = ending - start
134
188
 
135
- execution_time = cycles_done * (1/@instance.clock_speed)
189
+ execution_time = cycles_done * (1.0/@instance.clock_speed)
190
+ serial_count += execution_time
191
+
136
192
  padding = execution_time - elapsed
137
193
  sleep(padding) unless padding < 0
138
194
  end
@@ -169,7 +225,7 @@ module Zemu
169
225
 
170
226
  (address.to_i(16)...address.to_i(16) + size.to_i(16)).each do |a|
171
227
  m = @instance.memory(a)
172
- if (m < 32)
228
+ if (m < 32 || m > 126)
173
229
  log "%04x: %02x ." % [a, m]
174
230
  else
175
231
  log ("%04x: %02x " % [a, m]) + m.chr("UTF-8")
@@ -177,6 +233,34 @@ module Zemu
177
233
  end
178
234
  end
179
235
 
236
+ # Loads a MAP file from the given path.
237
+ def load_map(path)
238
+ if path.nil?
239
+ log "No path specified."
240
+ return
241
+ end
242
+
243
+ unless File.exist?(path.to_s)
244
+ log "Map file '#{path}' does not exist."
245
+ return
246
+ end
247
+
248
+ if File.directory?(path.to_s)
249
+ log "Cannot open '#{path}': it is a directory."
250
+ return
251
+ end
252
+
253
+ syms = {}
254
+ begin
255
+ syms.merge! Debug.load_map(path.to_s)
256
+ rescue ArgumentError => e
257
+ log "Error loading map file: #{e.message}"
258
+ syms.clear
259
+ end
260
+
261
+ @symbol_table.merge! syms
262
+ end
263
+
180
264
  # Process serial input/output via the TTY.
181
265
  def process_serial
182
266
  # Read/write serial.
data/lib/zemu.rb CHANGED
@@ -5,6 +5,7 @@ require 'pty'
5
5
  require_relative 'zemu/config'
6
6
  require_relative 'zemu/instance'
7
7
  require_relative 'zemu/interactive'
8
+ require_relative 'zemu/debug'
8
9
 
9
10
  # Zemu is a module providing an interface to build and interact with
10
11
  # configurable Z80 emulators.
@@ -128,7 +129,7 @@ module Zemu
128
129
 
129
130
  includes_str += " -I" + autogen
130
131
 
131
- command = "#{compiler} -Werror -Wno-unknown-warning-option -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup #{includes_str} #{defines_str} -o #{output} #{inputs_str}"
132
+ command = "#{compiler} -O2 -Werror -Wno-unknown-warning-option -fPIC -shared -Wl,-undefined -Wl,dynamic_lookup #{includes_str} #{defines_str} -o #{output} #{inputs_str}"
132
133
 
133
134
  # Run the compiler and generate a library.
134
135
  return system(command)
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.
@@ -29,11 +44,6 @@ void zemu_io_out(void * context, zuint16 port, zuint8 value)
29
44
  <% end %>
30
45
  }
31
46
 
32
- void zemu_io_nmi(Z80 * instance)
33
- {
34
- z80_nmi(instance);
35
- }
36
-
37
47
  void zemu_io_clock(Z80 * instance)
38
48
  {
39
49
  <% io.each do |device| %>
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.3.1
4
+ version: 0.4.0
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-03-23 00:00:00.000000000 Z
11
+ date: 2021-10-23 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
@@ -29,6 +29,7 @@ extra_rdoc_files: []
29
29
  files:
30
30
  - lib/zemu.rb
31
31
  - lib/zemu/config.rb
32
+ - lib/zemu/debug.rb
32
33
  - lib/zemu/instance.rb
33
34
  - lib/zemu/interactive.rb
34
35
  - src/debug.c
@@ -333,8 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
333
334
  - !ruby/object:Gem::Version
334
335
  version: '0'
335
336
  requirements: []
336
- rubyforge_project:
337
- rubygems_version: 2.7.6
337
+ rubygems_version: 3.1.2
338
338
  signing_key:
339
339
  specification_version: 4
340
340
  summary: A configurable Z80 emulator.