zemu 0.4.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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