zemu 0.4.2 → 1.1.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.
data/lib/zemu/debug.rb CHANGED
@@ -5,22 +5,55 @@ module Zemu
5
5
  # Loads a map file at the given path, and returns a hash of address => Symbol
6
6
  # for the symbols defined within.
7
7
  def self.load_map(path)
8
- symbols = {}
8
+ symbols = []
9
9
 
10
10
  File.open(path, "r") do |f|
11
11
  f.each_line do |l|
12
12
  s = Symbol.parse(l)
13
13
 
14
- if symbols[s.address].nil?
15
- symbols[s.address] = []
14
+ symbols << s
15
+ end
16
+ end
17
+
18
+ return Symbols.new(symbols)
19
+ end
20
+
21
+ # Contains a set of symbols.
22
+ # Allows for various lookup operations.
23
+ class Symbols
24
+ # Constructor.
25
+ def initialize(syms)
26
+ @syms = []
27
+ syms.each do |s|
28
+ @syms << s
29
+ end
30
+ end
31
+
32
+ # Access all symbols with a given address.
33
+ def [](address)
34
+ at_address = []
35
+ @syms.each do |s|
36
+ if s.address == address
37
+ at_address << s
16
38
  end
39
+ end
40
+
41
+ return at_address.sort_by(&:label)
42
+ end
17
43
 
18
- symbols[s.address] << s
19
- symbols[s.address].sort_by!(&:label)
44
+ # Find a symbol with a given name.
45
+ def find_by_name(name)
46
+ @syms.each do |s|
47
+ return s if s.label == name
20
48
  end
49
+
50
+ return nil
21
51
  end
22
52
 
23
- return symbols
53
+ # Get symbols as a hash.
54
+ def hash
55
+ return @syms
56
+ end
24
57
  end
25
58
 
26
59
  # Represents a symbol definition, of the form `label = address`.
data/lib/zemu/instance.rb CHANGED
@@ -2,6 +2,17 @@ require 'ffi'
2
2
  require 'ostruct'
3
3
 
4
4
  module Zemu
5
+ # Exception raised when an error occurs in a Zemu::Instance.
6
+ class InstanceError < StandardError
7
+ # Constructor.
8
+ #
9
+ # @param msg The exception message
10
+ #
11
+ def initialize(msg="An error occurred with the Zemu::Instance")
12
+ super
13
+ end
14
+ end
15
+
5
16
  # Represents an instance of a Zemu emulator.
6
17
  #
7
18
  # Provides methods by which the state of the emulator can be observed
@@ -61,9 +72,11 @@ module Zemu
61
72
  end
62
73
 
63
74
  def initialize(configuration)
64
- # Methods defined by IO devices that we make
75
+ @devices = configuration.devices
76
+
77
+ # Methods defined by bus devices that we make
65
78
  # accessible to the user.
66
- @io_methods = []
79
+ @device_methods = []
67
80
 
68
81
  @clock = configuration.clock_speed
69
82
  @serial_delay = configuration.serial_delay
@@ -73,12 +86,95 @@ module Zemu
73
86
  @serial = []
74
87
 
75
88
  @instance = @wrapper.zemu_init
89
+
90
+ # Declare handlers.
91
+ # Memory write handler.
92
+ @mem_write = Proc.new do |addr, value|
93
+ @devices.each do |d|
94
+ d.mem_write(addr, value)
95
+ end
96
+ end
97
+
98
+ # Memory read handler.
99
+ @mem_read = Proc.new do |addr|
100
+ r = 0
101
+ @devices.each do |d|
102
+ v = d.mem_read(addr)
103
+ unless v.nil?
104
+ r = v
105
+ break
106
+ end
107
+ end
108
+
109
+ r
110
+ end
111
+
112
+ # IO write handler.
113
+ @io_write = Proc.new do |port, value|
114
+ @devices.each do |d|
115
+ d.io_write(port, value)
116
+ end
117
+ end
118
+
119
+ # IO read handler.
120
+ @io_read = Proc.new do |port|
121
+ r = 0
122
+ @devices.each do |d|
123
+ v = d.io_read(port)
124
+ unless v.nil?
125
+ r = v
126
+ break
127
+ end
128
+ end
129
+
130
+ r
131
+ end
132
+
133
+ # IO read handler.
134
+ @io_clock = Proc.new do |cycles|
135
+ @devices.each do |d|
136
+ d.clock(cycles)
137
+ end
138
+
139
+ bus_state = 0
140
+
141
+ if @devices.any? { |d| d.nmi? }
142
+ bus_state |= 1
143
+ end
144
+
145
+ if @devices.any? { |d| d.interrupt? }
146
+ bus_state |= 2
147
+ end
148
+
149
+ bus_state
150
+ end
151
+
152
+ # Attach handlers.
153
+ @wrapper.zemu_set_mem_write_handler(@mem_write)
154
+ @wrapper.zemu_set_mem_read_handler(@mem_read)
155
+ @wrapper.zemu_set_io_write_handler(@io_write)
156
+ @wrapper.zemu_set_io_read_handler(@io_read)
157
+ @wrapper.zemu_set_io_clock_handler(@io_clock)
158
+
76
159
  @wrapper.zemu_power_on(@instance)
77
160
  @wrapper.zemu_reset(@instance)
78
161
 
79
162
  @state = RunState::UNDEFINED
80
163
 
81
164
  @breakpoints = {}
165
+ @tracepoints = {}
166
+ end
167
+
168
+ # Returns the device with the given name, or nil
169
+ # if no such device exists.
170
+ def device(name)
171
+ @devices.each do |d|
172
+ if d.name == name
173
+ return d
174
+ end
175
+ end
176
+
177
+ nil
82
178
  end
83
179
 
84
180
  # Returns the clock speed of this instance in Hz.
@@ -141,7 +237,7 @@ module Zemu
141
237
  # emulated machine.
142
238
  def serial_puts(string)
143
239
  string.each_char do |c|
144
- @wrapper.zemu_io_serial_master_puts(c.ord)
240
+ device('serial').put_byte(c.ord)
145
241
  end
146
242
  end
147
243
 
@@ -157,14 +253,14 @@ module Zemu
157
253
  def serial_gets(count=nil)
158
254
  return_string = ""
159
255
 
160
- actual_count = @wrapper.zemu_io_serial_buffer_size()
256
+ actual_count = device('serial').transmitted_count()
161
257
 
162
258
  if count.nil? || actual_count < count
163
259
  count = actual_count
164
260
  end
165
261
 
166
262
  count.to_i.times do
167
- return_string += @wrapper.zemu_io_serial_master_gets().chr
263
+ return_string += device('serial').get_byte().chr
168
264
  end
169
265
 
170
266
  return return_string
@@ -203,6 +299,12 @@ module Zemu
203
299
 
204
300
  pc = @wrapper.zemu_debug_pc(@instance)
205
301
 
302
+ # If there's a tracepoint at this address,
303
+ # execute the associated proc.
304
+ unless @tracepoints[pc].nil?
305
+ @tracepoints[pc].call(self)
306
+ end
307
+
206
308
  # If the PC is now pointing to one of our breakpoints,
207
309
  # we're in the BREAK state.
208
310
  if @breakpoints[pc]
@@ -224,6 +326,26 @@ module Zemu
224
326
  @breakpoints[address] = true
225
327
  end
226
328
 
329
+ # Set a tracepoint to execute the given block at an address.
330
+ #
331
+ # @param address The address of the tracepoint
332
+ # @param block The block to execute at the tracepoint.
333
+ # The block takes the instance as a parameter.
334
+ #
335
+ # @example
336
+ # instance.trace(0x1234) do |i|
337
+ # puts "HL value: 04x" % i.registers["HL"]
338
+ # end
339
+ #
340
+ def trace(address, &block)
341
+ if block.arity != 1
342
+ raise InstanceError,
343
+ "Wrong arity for tracepoint - expected 1, got #{block.arity}"
344
+ end
345
+
346
+ @tracepoints[address] = block
347
+ end
348
+
227
349
  # Remove a breakpoint of the given type at the given address.
228
350
  # Does nothing if no breakpoint previously existed at that address.
229
351
  #
@@ -257,6 +379,21 @@ module Zemu
257
379
 
258
380
  wrapper.ffi_lib [File.join(configuration.output_directory, "#{configuration.name}.so")]
259
381
 
382
+ # Handler types for handling bus accesses.
383
+ wrapper.callback :mem_write_handler, [:uint32, :uint8], :void
384
+ wrapper.callback :mem_read_handler, [:uint32], :uint8
385
+
386
+ wrapper.callback :io_write_handler, [:uint8, :uint8], :void
387
+ wrapper.callback :io_read_handler, [:uint8], :uint8
388
+ wrapper.callback :io_clock_handler, [:uint64], :uint8
389
+
390
+ wrapper.attach_function :zemu_set_mem_write_handler, [:mem_write_handler], :void
391
+ wrapper.attach_function :zemu_set_mem_read_handler, [:mem_read_handler], :void
392
+
393
+ wrapper.attach_function :zemu_set_io_write_handler, [:io_write_handler], :void
394
+ wrapper.attach_function :zemu_set_io_read_handler, [:io_read_handler], :void
395
+ wrapper.attach_function :zemu_set_io_clock_handler, [:io_clock_handler], :void
396
+
260
397
  wrapper.attach_function :zemu_init, [], :pointer
261
398
  wrapper.attach_function :zemu_free, [:pointer], :void
262
399
 
@@ -275,10 +412,10 @@ module Zemu
275
412
  wrapper.attach_function :zemu_debug_get_memory, [:uint16], :uint8
276
413
  wrapper.attach_function :zemu_debug_set_memory, [:uint16, :uint8], :void
277
414
 
278
- configuration.io.each do |device|
415
+ configuration.devices.each do |device|
279
416
  device.functions.each do |f|
280
417
  wrapper.attach_function(f["name"].to_sym, f["args"], f["return"])
281
- @io_methods << f["name"].to_sym
418
+ @device_methods << f["name"].to_sym
282
419
  end
283
420
  end
284
421
 
@@ -287,7 +424,7 @@ module Zemu
287
424
 
288
425
  # Redirects calls to I/O FFI functions.
289
426
  def method_missing(method, *args)
290
- if @io_methods.include? method
427
+ if @device_methods.include? method
291
428
  return @wrapper.send(method)
292
429
  end
293
430
 
@@ -11,6 +11,7 @@ module Zemu
11
11
  # to the emulator window.
12
12
  def initialize(instance, options = {})
13
13
  @print_serial = options[:print_serial]
14
+ @trace = []
14
15
 
15
16
  @instance = instance
16
17
 
@@ -70,6 +71,9 @@ module Zemu
70
71
  elsif cmd[0] == "map"
71
72
  load_map(cmd[1])
72
73
 
74
+ elsif cmd[0] == "trace"
75
+ trace()
76
+
73
77
  elsif cmd[0] == "help"
74
78
  log "Available commands:"
75
79
  log " continue [<n>] - Continue execution for <n> cycles"
@@ -90,6 +94,14 @@ module Zemu
90
94
  close
91
95
  end
92
96
 
97
+ # Print trace for the emulator instance
98
+ # (last 200 addresses visited).
99
+ def trace
100
+ @trace.each do |t|
101
+ puts "%04x" % t
102
+ end
103
+ end
104
+
93
105
  # Outputs a table giving the current values of the instance's registers.
94
106
  # For the 16-bit registers (BC, DE, HL, IX, IY, SP, PC), attempts to identify the symbol
95
107
  # to which they point.
@@ -171,9 +183,6 @@ module Zemu
171
183
  serial_count = @instance.serial_delay.to_f
172
184
 
173
185
  while ((cycles == -1) || (cycles_left > 0))
174
- # Get time before execution.
175
- start = Time.now
176
-
177
186
  old_pc = r16("PC")
178
187
 
179
188
  if (serial_count >= @instance.serial_delay)
@@ -185,18 +194,10 @@ module Zemu
185
194
  cycles_left -= cycles_done
186
195
  actual_cycles += cycles_done
187
196
 
188
- # Get time after execution.
189
- ending = Time.now
190
-
191
197
  # Get elapsed time and calculate padding time to match clock speed.
192
198
  if @instance.clock_speed > 0
193
- elapsed = ending - start
194
-
195
199
  execution_time = cycles_done * (1.0/@instance.clock_speed)
196
200
  serial_count += execution_time
197
-
198
- padding = execution_time - elapsed
199
- sleep(padding) unless padding < 0
200
201
  end
201
202
 
202
203
  # Have we hit a breakpoint or HALT instruction?
@@ -258,7 +259,7 @@ module Zemu
258
259
 
259
260
  syms = {}
260
261
  begin
261
- syms.merge! Debug.load_map(path.to_s)
262
+ syms.merge! Debug.load_map(path.to_s).hash
262
263
  rescue ArgumentError => e
263
264
  log "Error loading map file: #{e.message}"
264
265
  syms.clear
data/lib/zemu.rb CHANGED
@@ -111,7 +111,7 @@ module Zemu
111
111
 
112
112
  inputs_str = inputs.map { |i| File.join(SRC, i) }.join(" ")
113
113
 
114
- inputs_str += " " + File.join(autogen, "memory.c") + " " + File.join(autogen, "io.c")
114
+ inputs_str += " " + File.join(autogen, "bus.c")
115
115
 
116
116
  defines = {
117
117
  "CPU_Z80_STATIC" => 1,
@@ -143,14 +143,13 @@ module Zemu
143
143
  #
144
144
  # @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
145
145
  def Zemu::generate(configuration)
146
- generate_memory(configuration)
147
- generate_io(configuration)
146
+ generate_bus(configuration)
148
147
  end
149
148
 
150
- # Generates the memory.c and memory.h files for a given configuration.
151
- def Zemu::generate_memory(configuration)
152
- header_template = ERB.new File.read(File.join(SRC, "memory.h.erb"))
153
- source_template = ERB.new File.read(File.join(SRC, "memory.c.erb"))
149
+ # Generates the bus.c and bus.h files for a given configuration.
150
+ def Zemu::generate_bus(configuration)
151
+ header_template = ERB.new File.read(File.join(SRC, "bus.h.erb"))
152
+ source_template = ERB.new File.read(File.join(SRC, "bus.c.erb"))
154
153
 
155
154
  autogen = File.join(configuration.output_directory, "autogen_#{configuration.name}")
156
155
 
@@ -158,28 +157,10 @@ module Zemu
158
157
  Dir.mkdir autogen
159
158
  end
160
159
 
161
- File.write(File.join(autogen, "memory.h"),
160
+ File.write(File.join(autogen, "bus.h"),
162
161
  header_template.result(configuration.get_binding))
163
162
 
164
- File.write(File.join(autogen, "memory.c"),
165
- source_template.result(configuration.get_binding))
166
- end
167
-
168
- # Generates the io.c and io.h files for a given configuration.
169
- def Zemu::generate_io(configuration)
170
- header_template = ERB.new File.read(File.join(SRC, "io.h.erb"))
171
- source_template = ERB.new File.read(File.join(SRC, "io.c.erb"))
172
-
173
- autogen = File.join(configuration.output_directory, "autogen_#{configuration.name}")
174
-
175
- unless Dir.exist? autogen
176
- Dir.mkdir autogen
177
- end
178
-
179
- File.write(File.join(autogen, "io.h"),
180
- header_template.result(configuration.get_binding))
181
-
182
- File.write(File.join(autogen, "io.c"),
163
+ File.write(File.join(autogen, "bus.c"),
183
164
  source_template.result(configuration.get_binding))
184
165
  end
185
166
  end
data/src/bus.c.erb ADDED
@@ -0,0 +1,119 @@
1
+ #include "bus.h"
2
+
3
+ mem_write_handler_t * mem_write_handler;
4
+ mem_read_handler_t * mem_read_handler;
5
+
6
+ io_write_handler_t * io_write_handler;
7
+ io_read_handler_t * io_read_handler;
8
+ io_clock_handler_t * io_clock_handler;
9
+
10
+ <% devices.each do |d| %>
11
+ <% next if d.memory.nil? %>
12
+ <%= d.memory.setup %>
13
+ <% end %>
14
+
15
+ void zemu_set_mem_write_handler(mem_write_handler_t * h)
16
+ {
17
+ mem_write_handler = h;
18
+ }
19
+
20
+ void zemu_set_mem_read_handler(mem_read_handler_t * h)
21
+ {
22
+ mem_read_handler = h;
23
+ }
24
+
25
+ void zemu_set_io_write_handler(io_write_handler_t * h)
26
+ {
27
+ io_write_handler = h;
28
+ }
29
+
30
+ void zemu_set_io_read_handler(io_read_handler_t * h)
31
+ {
32
+ io_read_handler = h;
33
+ }
34
+
35
+ void zemu_set_io_clock_handler(io_clock_handler_t * h)
36
+ {
37
+ io_clock_handler = h;
38
+ }
39
+
40
+ zuint8 zemu_memory_read(void * context, zuint16 address)
41
+ {
42
+ return zemu_memory_peek(address);
43
+ }
44
+
45
+ void zemu_memory_write(void * context, zuint16 address, zuint8 value)
46
+ {
47
+ zemu_memory_poke(address, value);
48
+ }
49
+
50
+ zuint8 zemu_memory_peek(zuint16 address)
51
+ {
52
+ zuint32 address_32 = address;
53
+
54
+ <% devices.each do |d| %>
55
+ <% next if d.memory.nil? %>
56
+ if ((address_32 >= <%= d.memory.address %>) && (address_32 < <%= d.memory.address + d.memory.size%>))
57
+ return <%= d.memory.access_read %>[address_32 - <%= d.memory.address %>];
58
+ <% end %>
59
+
60
+ return mem_read_handler(address_32);
61
+ }
62
+
63
+ void zemu_memory_poke(zuint16 address, zuint8 value)
64
+ {
65
+ zuint32 address_32 = address;
66
+
67
+ <% devices.each do |d| %>
68
+ <% next if d.memory.nil? %>
69
+ if ((address_32 >= <%= d.memory.address %>) && (address_32 < <%= d.memory.address + d.memory.size%>))
70
+ <%= d.memory.access_write %>[address_32 - <%= d.memory.address %>] = value;
71
+ <% end %>
72
+
73
+ mem_write_handler(address_32, value);
74
+ }
75
+
76
+ void zemu_io_nmi(Z80 * instance)
77
+ {
78
+ z80_nmi(instance);
79
+ }
80
+
81
+ void zemu_io_int_on(Z80 * instance)
82
+ {
83
+ z80_int(instance, TRUE);
84
+ }
85
+
86
+ void zemu_io_int_off(Z80 * instance)
87
+ {
88
+ z80_int(instance, FALSE);
89
+ }
90
+
91
+ zuint8 zemu_io_in(void * context, zuint16 port)
92
+ {
93
+ /* Z80 IO ports occupy the lower half of the address bus.
94
+ * We cannot assume that the top half is valid.
95
+ */
96
+ port &= 0x00FF;
97
+
98
+ return io_read_handler((zuint8)port);
99
+ }
100
+
101
+ void zemu_io_out(void * context, zuint16 port, zuint8 value)
102
+ {
103
+ /* Z80 IO ports occupy the lower half of the address bus.
104
+ * We cannot assume that the top half is valid.
105
+ */
106
+ port &= 0x00FF;
107
+
108
+ io_write_handler((zuint8)port, value);
109
+ }
110
+
111
+ void zemu_io_clock(Z80 * instance, zusize cycles)
112
+ {
113
+ zuint8 bus_state = io_clock_handler(cycles);
114
+
115
+ if (bus_state & 0x01) zemu_io_nmi(instance);
116
+
117
+ if (bus_state & 0x02) zemu_io_int_on(instance);
118
+ else zemu_io_int_off(instance);
119
+ }
data/src/bus.h.erb ADDED
@@ -0,0 +1,53 @@
1
+ #ifndef _ZEMU_IO_H
2
+ #define _ZEMU_IO_H
3
+
4
+ #include "emulation/CPU/Z80.h"
5
+
6
+ #ifndef ZEMU_IO_SERIAL_BUFFER_SIZE
7
+ #define ZEMU_IO_SERIAL_BUFFER_SIZE 256
8
+ #endif
9
+
10
+ typedef struct {
11
+ zuint8 buffer[ZEMU_IO_SERIAL_BUFFER_SIZE];
12
+ unsigned int head;
13
+ unsigned int tail;
14
+ } SerialBuffer;
15
+
16
+ /* Type of a function writing to a memory address. */
17
+ typedef void mem_write_handler_t(zuint32, zuint8);
18
+
19
+ /* Type of a function reading from a memory address. */
20
+ typedef zuint8 mem_read_handler_t(zuint32);
21
+
22
+ /* Type of a function writing to an IO port. */
23
+ typedef void io_write_handler_t(zuint8, zuint8);
24
+
25
+ /* Type of a function reading from an IO port. */
26
+ typedef zuint8 io_read_handler_t(zuint8);
27
+
28
+ /* Type of a function to handle a clock cycle for a peripheral. */
29
+ typedef zuint8 io_clock_handler_t(zusize);
30
+
31
+ void zemu_set_mem_write_handler(mem_write_handler_t * h);
32
+ void zemu_set_mem_read_handler(mem_read_handler_t * h);
33
+
34
+ void zemu_set_io_write_handler(io_write_handler_t * h);
35
+ void zemu_set_io_read_handler(io_read_handler_t * h);
36
+ void zemu_set_io_clock_handler(io_clock_handler_t * h);
37
+
38
+ zuint8 zemu_memory_read(void * context, zuint16 address);
39
+ void zemu_memory_write(void * context, zuint16 address, zuint8 value);
40
+
41
+ zuint8 zemu_memory_peek(zuint16 address);
42
+ void zemu_memory_poke(zuint16 address, zuint8 value);
43
+
44
+ void zemu_io_serial_master_puts(zuint8 val);
45
+ zuint8 zemu_io_serial_master_gets(void);
46
+ zusize zemu_io_serial_buffer_size(void);
47
+
48
+ zuint8 zemu_io_in(void * context, zuint16 port);
49
+ void zemu_io_out(void * context, zuint16 port, zuint8 value);
50
+ void zemu_io_nmi(Z80 * instance);
51
+ void zemu_io_clock(Z80 * instance, zusize cycles);
52
+
53
+ #endif
data/src/debug.c CHANGED
@@ -8,7 +8,7 @@ zusize zemu_debug_step(Z80 * instance)
8
8
  zusize cycles = z80_run(instance, 1);
9
9
 
10
10
  /* Execute the per-cycle behaviour of the peripheral devices. */
11
- for (zusize i = 0; i < cycles; i++) zemu_io_clock(instance);
11
+ zemu_io_clock(instance, cycles);
12
12
 
13
13
  return cycles;
14
14
  }
data/src/debug.h CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  #include <stdio.h>
4
4
 
5
- #include "memory.h"
6
- #include "io.h"
5
+ #include "bus.h"
7
6
 
8
7
  zusize zemu_debug_step(Z80 * instance);
9
8
 
data/src/main.c CHANGED
@@ -4,8 +4,7 @@
4
4
 
5
5
  #include "debug.h"
6
6
 
7
- #include "memory.h"
8
- #include "io.h"
7
+ #include "bus.h"
9
8
  #include "interrupt.h"
10
9
 
11
10
  /* Allocate and initialize a Z80 instance.
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.2
4
+ version: 1.1.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: 2021-11-19 00:00:00.000000000 Z
11
+ date: 2022-01-30 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
@@ -32,6 +32,8 @@ files:
32
32
  - lib/zemu/debug.rb
33
33
  - lib/zemu/instance.rb
34
34
  - lib/zemu/interactive.rb
35
+ - src/bus.c.erb
36
+ - src/bus.h.erb
35
37
  - src/debug.c
36
38
  - src/debug.h
37
39
  - src/external/Z/API/Z/ABIs/generic/allocator.h
@@ -310,11 +312,7 @@ files:
310
312
  - src/external/z80/sources/Z80.c
311
313
  - src/interrupt.c
312
314
  - src/interrupt.h
313
- - src/io.c.erb
314
- - src/io.h.erb
315
315
  - src/main.c
316
- - src/memory.c.erb
317
- - src/memory.h.erb
318
316
  homepage: https://github.com/jayvalentine/zemu
319
317
  licenses:
320
318
  - GPL-3.0