zemu 0.3.3 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0be58ea6333733b1c2b6467ecc9e0ec1ebebc16cd1e3ba5f6b34454d22941313
4
- data.tar.gz: 373889194e89aec21f878ef44dfe28b5432b9893d311532e83c460904bd1e292
3
+ metadata.gz: 0542a6b70d68a1ea2de55fb02802f487ec2ff06f00d824d8113dd8d0b40da40b
4
+ data.tar.gz: d806954f2a981777e57b2dcd3d8e1ba4946ce62c6ef440d63ceb2993ab1cd82c
5
5
  SHA512:
6
- metadata.gz: ccb11a107eb8aa7f39634ecdfd0d1765583655c1c33c0d87c3ae01f9b6634ffc965e2234b8fe3460b2f8de3b8848a0387488bd401d588a47e69967b87afda864
7
- data.tar.gz: 6d727da2887d8bf9d3e94f376d8659b29f03d3bb32865a2b66f3c61772bbeb300a347994c4341b494ed50649f0fe8cddf51d6c4b991c34b0955313d67907e932
6
+ metadata.gz: 45a3ca398596119e345dd6b146c571c7f635f5067b2ea58ecc9a2ebb6808ea3c60db327dbb0027c7d75ee1a73a79f41dc334cc9eea0e4bd7b3d634752c5047af
7
+ data.tar.gz: b580177e4823c0ecb5b54268c3820f32345560a8199054dc8754f855554ef74d38f15895d337a43fc3b7d6363e5e5ed148d479b6106db9673904765736693611
data/lib/zemu/config.rb CHANGED
@@ -439,6 +439,218 @@ 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
+ @initialize_from = nil
470
+
471
+ super
472
+
473
+ # Initialize from provided file if applicable.
474
+ unless @initialize_from.nil?
475
+ # Check file size.
476
+ file_size = File.size(@initialize_from)
477
+ if (file_size != num_sectors * sector_size)
478
+ raise RangeError, "Initialization file for Zemu::Config::BlockDrive '#{name}' is of wrong size."
479
+ end
480
+ end
481
+
482
+ when_setup do
483
+ <<-eos
484
+ #include <stdio.h>
485
+
486
+ zuint8 sector_data_#{name}[#{sector_size}];
487
+ zuint32 sector_data_#{name}_offset;
488
+ zuint32 loaded_sector_#{name} = Z_UINT32_MAXIMUM;
489
+ zuint8 drive_mode_#{name};
490
+ zuint8 drive_status_#{name} = 0b01000000;
491
+
492
+ zuint8 lba_#{name}_0;
493
+ zuint8 lba_#{name}_1;
494
+ zuint8 lba_#{name}_2;
495
+ zuint8 lba_#{name}_3;
496
+
497
+ void internal_#{name}_load_sector(zuint32 sector)
498
+ {
499
+ if (loaded_sector_#{name} == sector) return;
500
+
501
+ FILE * fptr = fopen("#{@initialize_from}", "rb");
502
+ fseek(fptr, sector * #{sector_size}, SEEK_SET);
503
+ fread(sector_data_#{name}, #{sector_size}, 1, fptr);
504
+ fclose(fptr);
505
+
506
+ loaded_sector_#{name} = sector;
507
+ }
508
+
509
+ void internal_#{name}_write_current_sector()
510
+ {
511
+ FILE * fptr = fopen("#{@initialize_from}", "r+b");
512
+ fseek(fptr, loaded_sector_#{name} * #{sector_size}, SEEK_SET);
513
+ fwrite(sector_data_#{name}, 1, #{sector_size}, fptr);
514
+ fclose(fptr);
515
+ }
516
+
517
+ zuint8 zemu_io_#{name}_readbyte(zuint32 sector, zuint32 offset)
518
+ {
519
+ internal_#{name}_load_sector(sector);
520
+ return sector_data_#{name}[offset];
521
+ }
522
+ eos
523
+ end
524
+
525
+ when_read do
526
+ <<-eos
527
+ if (port == #{base_port})
528
+ {
529
+ zuint8 b = sector_data_#{name}[sector_data_#{name}_offset];
530
+ sector_data_#{name}_offset++;
531
+ if (sector_data_#{name}_offset >= #{sector_size})
532
+ {
533
+ drive_status_#{name} = 0b01000000;
534
+ }
535
+ return b;
536
+ }
537
+ else if (port == #{base_port+7})
538
+ {
539
+ return drive_status_#{name};
540
+ }
541
+ eos
542
+ end
543
+
544
+ when_write do
545
+ <<-eos
546
+ if (port == #{base_port})
547
+ {
548
+ if (drive_mode_#{name} == 0x01)
549
+ {
550
+ sector_data_#{name}[sector_data_#{name}_offset] = value;
551
+ sector_data_#{name}_offset++;
552
+ if (sector_data_#{name}_offset >= #{sector_size})
553
+ {
554
+ internal_#{name}_write_current_sector();
555
+ drive_status_#{name} = 0b01000000;
556
+ }
557
+ }
558
+ }
559
+ else if (port == #{base_port+3})
560
+ {
561
+ lba_#{name}_0 = value;
562
+ }
563
+ else if (port == #{base_port+4})
564
+ {
565
+ lba_#{name}_1 = value;
566
+ }
567
+ else if (port == #{base_port+5})
568
+ {
569
+ lba_#{name}_2 = value;
570
+ }
571
+ else if (port == #{base_port+6})
572
+ {
573
+ lba_#{name}_3 = value & 0b00011111;
574
+ }
575
+ else if (port == #{base_port+7})
576
+ {
577
+ if (value == 0x20)
578
+ {
579
+ zuint32 sector = 0;
580
+ sector |= (zuint32)lba_#{name}_3 << 24;
581
+ sector |= (zuint32)lba_#{name}_2 << 16;
582
+ sector |= (zuint32)lba_#{name}_1 << 8;
583
+ sector |= (zuint32)lba_#{name}_0;
584
+
585
+ internal_#{name}_load_sector(sector);
586
+ sector_data_#{name}_offset = 0;
587
+
588
+ drive_mode_#{name} = 0x00;
589
+ drive_status_#{name} = 0b00001000;
590
+ }
591
+ else if (value == 0x30)
592
+ {
593
+ zuint32 sector = 0;
594
+ sector |= (zuint32)lba_#{name}_3 << 24;
595
+ sector |= (zuint32)lba_#{name}_2 << 16;
596
+ sector |= (zuint32)lba_#{name}_1 << 8;
597
+ sector |= (zuint32)lba_#{name}_0;
598
+
599
+ internal_#{name}_load_sector(sector);
600
+ sector_data_#{name}_offset = 0;
601
+
602
+ drive_mode_#{name} = 0x01;
603
+ drive_status_#{name} = 0b00001000;
604
+ }
605
+ }
606
+ eos
607
+ end
608
+ end
609
+
610
+ # Array of sectors of this drive.
611
+ def blocks
612
+ b = []
613
+
614
+ if @initialize_from.nil?
615
+ num_sectors.times do
616
+ this_block = []
617
+ sector_size.times do
618
+ this_block << 0
619
+ end
620
+ b << this_block
621
+ end
622
+ return b
623
+ end
624
+
625
+ File.open(@initialize_from, "rb") do |f|
626
+ num_sectors.times do
627
+ this_block = f.read(sector_size)
628
+ b << this_block.unpack("C" * sector_size)
629
+ end
630
+ end
631
+
632
+ b
633
+ end
634
+
635
+ # Set file to initialize from.
636
+ def initialize_from(file)
637
+ @initialize_from = file
638
+ end
639
+
640
+ # Defines FFI API which will be available to the instance wrapper if this IO device is used.
641
+ def functions
642
+ [
643
+ {"name" => "zemu_io_#{name}_readbyte", "args" => [:uint32, :uint32], "return" => :uint8},
644
+ ]
645
+ end
646
+
647
+ # Valid parameters for a BlockDrive, along with those
648
+ # defined in [Zemu::Config::IOPort].
649
+ def params
650
+ super + %w(base_port sector_size num_sectors)
651
+ end
652
+ end
653
+
442
654
  # Non-Maskable Interrupt Timer
443
655
  #
444
656
  # Represents a timer device, the period of which can be controlled
@@ -490,7 +702,7 @@ module Zemu
490
702
 
491
703
  # Parameters accessible by this configuration object.
492
704
  def params
493
- return %w(name compiler output_directory clock_speed)
705
+ return %w(name compiler output_directory clock_speed serial_delay)
494
706
  end
495
707
 
496
708
  # Initial value for parameters of this configuration object.
@@ -498,7 +710,8 @@ module Zemu
498
710
  return {
499
711
  "compiler" => "clang",
500
712
  "output_directory" => "bin",
501
- "clock_speed" => 0
713
+ "clock_speed" => 0,
714
+ "serial_delay" => 0
502
715
  }
503
716
  end
504
717
 
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
@@ -37,6 +37,14 @@ module Zemu
37
37
  "L'" => 19
38
38
  }
39
39
 
40
+ # Mapping of extended registers
41
+ # to the registers that comprise them.
42
+ REGISTERS_EXTENDED = {
43
+ "HL" => ["H", "L"],
44
+ "BC" => ["B", "C"],
45
+ "DE" => ["D", "E"]
46
+ }
47
+
40
48
  # States that the emulated machine can be in.
41
49
  class RunState
42
50
  # Currently executing an instruction.
@@ -53,7 +61,12 @@ module Zemu
53
61
  end
54
62
 
55
63
  def initialize(configuration)
64
+ # Methods defined by IO devices that we make
65
+ # accessible to the user.
66
+ @io_methods = []
67
+
56
68
  @clock = configuration.clock_speed
69
+ @serial_delay = configuration.serial_delay
57
70
 
58
71
  @wrapper = make_wrapper(configuration)
59
72
 
@@ -73,6 +86,11 @@ module Zemu
73
86
  return @clock
74
87
  end
75
88
 
89
+ # Returns the delay between characters on the serial port for this instance in seconds.
90
+ def serial_delay
91
+ return @serial_delay
92
+ end
93
+
76
94
  # Returns a hash containing current values of the emulated
77
95
  # machine's registers. All names are as those given in the Z80
78
96
  # reference manual.
@@ -85,6 +103,12 @@ module Zemu
85
103
  REGISTERS.each do |reg, num|
86
104
  r[reg] = @wrapper.zemu_debug_register(@instance, num)
87
105
  end
106
+
107
+ REGISTERS_EXTENDED.each do |reg, components|
108
+ hi = components[0]
109
+ lo = components[1]
110
+ r[reg] = (r[hi] << 8) | r[lo]
111
+ end
88
112
 
89
113
  return r
90
114
  end
@@ -99,6 +123,16 @@ module Zemu
99
123
  return @wrapper.zemu_debug_get_memory(address)
100
124
  end
101
125
 
126
+ # Set the value in memory at a given address.
127
+ #
128
+ # @param address The address in memory to be set.
129
+ # @param value The value to set to.
130
+ #
131
+ # Returns nothing.
132
+ def set_memory(address, value)
133
+ @wrapper.zemu_debug_set_memory(address, value)
134
+ end
135
+
102
136
  # Write a string to the serial line of the emulated CPU.
103
137
  #
104
138
  # @param string The string to be sent.
@@ -136,6 +170,16 @@ module Zemu
136
170
  return return_string
137
171
  end
138
172
 
173
+ # Get a byte from the attached disk drive.
174
+ #
175
+ # @param sector The sector to read
176
+ # @param offset The offset in sector to read
177
+ #
178
+ # Gets a byte at the given offset in the given sector.
179
+ def drive_readbyte(sector, offset)
180
+ return @wrapper.zemu_io_block_drive_readbyte(sector, offset)
181
+ end
182
+
139
183
  # Continue running this instance until either:
140
184
  # * A HALT instruction is executed
141
185
  # * A breakpoint is hit
@@ -229,16 +273,27 @@ module Zemu
229
273
  wrapper.attach_function :zemu_debug_pc, [:pointer], :uint16
230
274
 
231
275
  wrapper.attach_function :zemu_debug_get_memory, [:uint16], :uint8
276
+ wrapper.attach_function :zemu_debug_set_memory, [:uint16, :uint8], :void
232
277
 
233
278
  configuration.io.each do |device|
234
279
  device.functions.each do |f|
235
- wrapper.attach_function(f["name"], f["args"], f["return"])
280
+ wrapper.attach_function(f["name"].to_sym, f["args"], f["return"])
281
+ @io_methods << f["name"].to_sym
236
282
  end
237
283
  end
238
284
 
239
285
  return wrapper
240
286
  end
241
287
 
288
+ # Redirects calls to I/O FFI functions.
289
+ def method_missing(method, *args)
290
+ if @io_methods.include? method
291
+ return @wrapper.send(method)
292
+ end
293
+
294
+ super
295
+ end
296
+
242
297
  private :make_wrapper
243
298
  end
244
299
  end
@@ -5,9 +5,17 @@ module Zemu
5
5
  # Constructor.
6
6
  #
7
7
  # Create a new interactive wrapper for the given instance.
8
- def initialize(instance)
8
+ # The options hash allows the user to configure the behaviour
9
+ # of the interactive instance:
10
+ # :print_serial => true if serial input/output should be logged
11
+ # to the emulator window.
12
+ def initialize(instance, options = {})
13
+ @print_serial = options[:print_serial]
14
+
9
15
  @instance = instance
10
16
 
17
+ @symbol_table = {}
18
+
11
19
  @master, @slave = PTY.open
12
20
  log "Opened PTY at #{@slave.path}"
13
21
  end
@@ -59,6 +67,9 @@ module Zemu
59
67
  memory(cmd[1], cmd[2])
60
68
  end
61
69
 
70
+ elsif cmd[0] == "map"
71
+ load_map(cmd[1])
72
+
62
73
  elsif cmd[0] == "help"
63
74
  log "Available commands:"
64
75
  log " continue [<n>] - Continue execution for <n> cycles"
@@ -66,6 +77,7 @@ module Zemu
66
77
  log " registers - View register contents"
67
78
  log " memory <a> [<n>] - View <n> bytes of memory, starting at address <a>."
68
79
  log " <n> defaults to 1 if omitted."
80
+ log " map <path> - Load symbols from map file at <path>"
69
81
  log " break <a> - Set a breakpoint at the given address <a>."
70
82
  log " quit - End this emulator instance."
71
83
 
@@ -79,16 +91,53 @@ module Zemu
79
91
  end
80
92
 
81
93
  # Outputs a table giving the current values of the instance's registers.
94
+ # For the 16-bit registers (BC, DE, HL, IX, IY, SP, PC), attempts to identify the symbol
95
+ # to which they point.
82
96
  def registers
83
97
  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")}"
98
+
99
+ registers_gp('B', 'C')
100
+ registers_gp('D', 'E')
101
+ registers_gp('H', 'L')
102
+
87
103
  log ""
88
- log "IX: #{r16("IX")}"
89
- log "IY: #{r16("IY")}"
90
- log "SP: #{r16("SP")}"
91
- log "PC: #{r16("PC")}"
104
+
105
+ register_16("IX")
106
+ register_16("IY")
107
+ register_16("SP")
108
+ register_16("PC")
109
+ end
110
+
111
+ # Displays the value of a 16-bit register.
112
+ def register_16(r)
113
+ value = @instance.registers[r]
114
+
115
+ log "#{r}: #{r16(r)} (#{get_symbol(value)})"
116
+ end
117
+
118
+ # Displays the value of a general-purpose 16-bit register pair.
119
+ def registers_gp(hi, lo)
120
+ value = hilo(@instance.registers[hi], @instance.registers[lo])
121
+
122
+ log "#{hi}: #{r(hi)} #{lo}: #{r(lo)} (#{get_symbol(value)})"
123
+ end
124
+
125
+ # Gets the symbol associated with the given value.
126
+ # If no matching symbol, gives offset from previous symbol.
127
+ def get_symbol(value)
128
+ syms = nil
129
+ addr = value
130
+ while addr > 0 do
131
+ syms = @symbol_table[addr]
132
+ break unless syms.nil?
133
+ addr -= 1
134
+ end
135
+
136
+ sym = if syms.nil? then nil else syms[0] end
137
+
138
+ sym_str = "<#{if sym.nil? then 'undefined' else sym.label end}#{if addr == value then '' else "+#{value-addr}" end}>"
139
+
140
+ return sym_str
92
141
  end
93
142
 
94
143
  # Returns a particular 8-bit register value.
@@ -101,6 +150,11 @@ module Zemu
101
150
  return "0x%04x" % @instance.registers[reg]
102
151
  end
103
152
 
153
+ # Concatenates two 8-bit values, in big-endian format.
154
+ def hilo(hi, lo)
155
+ return (hi << 8) | lo
156
+ end
157
+
104
158
  # Continue for *up to* the given number of cycles.
105
159
  # Fewer cycles may be executed, depending on the behaviour of the processor.
106
160
  def continue(cycles=-1)
@@ -114,13 +168,19 @@ module Zemu
114
168
  cycles_left = cycles
115
169
  actual_cycles = 0
116
170
 
171
+ serial_count = @instance.serial_delay.to_f
172
+
117
173
  while ((cycles == -1) || (cycles_left > 0))
118
174
  # Get time before execution.
119
175
  start = Time.now
120
176
 
121
177
  old_pc = r16("PC")
122
178
 
123
- process_serial
179
+ if (serial_count >= @instance.serial_delay)
180
+ process_serial
181
+ serial_count = 0.0
182
+ end
183
+
124
184
  cycles_done = @instance.continue(1)
125
185
  cycles_left -= cycles_done
126
186
  actual_cycles += cycles_done
@@ -132,7 +192,9 @@ module Zemu
132
192
  if @instance.clock_speed > 0
133
193
  elapsed = ending - start
134
194
 
135
- execution_time = cycles_done * (1/@instance.clock_speed)
195
+ execution_time = cycles_done * (1.0/@instance.clock_speed)
196
+ serial_count += execution_time
197
+
136
198
  padding = execution_time - elapsed
137
199
  sleep(padding) unless padding < 0
138
200
  end
@@ -177,6 +239,34 @@ module Zemu
177
239
  end
178
240
  end
179
241
 
242
+ # Loads a MAP file from the given path.
243
+ def load_map(path)
244
+ if path.nil?
245
+ log "No path specified."
246
+ return
247
+ end
248
+
249
+ unless File.exist?(path.to_s)
250
+ log "Map file '#{path}' does not exist."
251
+ return
252
+ end
253
+
254
+ if File.directory?(path.to_s)
255
+ log "Cannot open '#{path}': it is a directory."
256
+ return
257
+ end
258
+
259
+ syms = {}
260
+ begin
261
+ syms.merge! Debug.load_map(path.to_s)
262
+ rescue ArgumentError => e
263
+ log "Error loading map file: #{e.message}"
264
+ syms.clear
265
+ end
266
+
267
+ @symbol_table.merge! syms
268
+ end
269
+
180
270
  # Process serial input/output via the TTY.
181
271
  def process_serial
182
272
  # Read/write serial.
@@ -191,12 +281,12 @@ module Zemu
191
281
 
192
282
  unless input.empty?
193
283
  @instance.serial_puts input
194
- log "Serial in: #{input}"
284
+ log "Serial in: #{input} ($#{input.ord.to_s(16)})" if @print_serial
195
285
  end
196
286
 
197
287
  unless output.empty?
198
288
  @master.write output
199
- log "Serial out: #{output}"
289
+ log "Serial out: #{output} ($#{output.ord.to_s(16)})" if @print_serial
200
290
  end
201
291
  end
202
292
  end
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.
@@ -61,10 +62,12 @@ module Zemu
61
62
  SRC = File.join(__dir__, "..", "src")
62
63
 
63
64
  # Build and start an emulator according to the given configuration.
65
+ # Returns the emulator instance.
64
66
  #
65
67
  # @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
66
- def Zemu::start(configuration)
67
- build(configuration)
68
+ # @param user_defines Any user-defined preprocessor macros.
69
+ def Zemu::start(configuration, user_defines={})
70
+ build(configuration, user_defines)
68
71
 
69
72
  return Instance.new(configuration)
70
73
  end
@@ -72,19 +75,19 @@ module Zemu
72
75
  # Starts an interactive instance of an emulator, according to the given configuration.
73
76
  #
74
77
  # @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
75
- def Zemu::start_interactive(configuration)
78
+ def Zemu::start_interactive(configuration, options = {})
76
79
  instance = start(configuration)
77
80
 
78
- interactive = InteractiveInstance.new(instance)
81
+ interactive = InteractiveInstance.new(instance, options)
79
82
  interactive.run
80
83
  end
81
84
 
82
85
  # Builds a library according to the given configuration.
86
+ # Returns true if the build is a success, false (build failed) or nil (compiler not found) otherwise.
83
87
  #
84
88
  # @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
85
- #
86
- # @returns true if the build is a success, false (build failed) or nil (compiler not found) otherwise.
87
- def Zemu::build(configuration)
89
+ # @param user_defines Any user-defined preprocessor macros.
90
+ def Zemu::build(configuration, user_defines={})
88
91
  # Create the output directory unless it already exists.
89
92
  unless Dir.exist? configuration.output_directory
90
93
  Dir.mkdir configuration.output_directory
@@ -115,6 +118,8 @@ module Zemu
115
118
  "CPU_Z80_USE_LOCAL_HEADER" => 1
116
119
  }
117
120
 
121
+ defines.merge! user_defines
122
+
118
123
  defines_str = defines.map { |d, v| "-D#{d}=#{v}" }.join(" ")
119
124
 
120
125
  includes = [
data/src/debug.c CHANGED
@@ -72,3 +72,8 @@ zuint8 zemu_debug_get_memory(zuint16 address)
72
72
  {
73
73
  return zemu_memory_peek(address);
74
74
  }
75
+
76
+ void zemu_debug_set_memory(zuint16 address, zuint8 value)
77
+ {
78
+ zemu_memory_poke(address, value);
79
+ }
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| %>
data/src/memory.c.erb CHANGED
@@ -9,10 +9,11 @@
9
9
 
10
10
  zuint8 zemu_memory_read(void * context, zuint16 address)
11
11
  {
12
+ zuint32 address_32 = address;
12
13
  <% memory.each do |mem| %>
13
- if (address >= 0x<%= mem.address.to_s(16) %> && address < 0x<%= (mem.address + mem.size).to_s(16) %>)
14
+ if (address_32 >= 0x<%= mem.address.to_s(16) %> && address_32 < 0x<%= (mem.address + mem.size).to_s(16) %>)
14
15
  {
15
- return zemu_memory_block_<%= mem.name %>[address - 0x<%= mem.address.to_s(16) %>];
16
+ return zemu_memory_block_<%= mem.name %>[address_32 - 0x<%= mem.address.to_s(16) %>];
16
17
  }
17
18
  <% end %>
18
19
  /* Unmapped memory has a value of 0. */
@@ -21,23 +22,38 @@ zuint8 zemu_memory_read(void * context, zuint16 address)
21
22
 
22
23
  void zemu_memory_write(void * context, zuint16 address, zuint8 value)
23
24
  {
25
+ zuint32 address_32 = address;
24
26
  <% memory.each do |mem| %>
25
27
  <% next if mem.readonly? %>
26
- if (address >= 0x<%= mem.address.to_s(16) %> && address < 0x<%= (mem.address + mem.size).to_s(16) %>)
28
+ if (address_32 >= 0x<%= mem.address.to_s(16) %> && address_32 < 0x<%= (mem.address + mem.size).to_s(16) %>)
27
29
  {
28
- zemu_memory_block_<%= mem.name %>[address - 0x<%= mem.address.to_s(16) %>] = value;
30
+ zemu_memory_block_<%= mem.name %>[address_32 - 0x<%= mem.address.to_s(16) %>] = value;
29
31
  }
30
32
  <% end %>
31
33
  }
32
34
 
33
35
  zuint8 zemu_memory_peek(zuint16 address)
34
36
  {
37
+ zuint32 address_32 = address;
35
38
  <% memory.each do |mem| %>
36
- if (address >= 0x<%= mem.address.to_s(16) %> && address < 0x<%= (mem.address + mem.size).to_s(16) %>)
39
+ if (address_32 >= 0x<%= mem.address.to_s(16) %> && address_32 < 0x<%= (mem.address + mem.size).to_s(16) %>)
37
40
  {
38
- return zemu_memory_block_<%= mem.name %>[address - 0x<%= mem.address.to_s(16) %>];
41
+ return zemu_memory_block_<%= mem.name %>[address_32 - 0x<%= mem.address.to_s(16) %>];
39
42
  }
40
43
  <% end %>
41
44
  /* Unmapped memory has a value of 0. */
42
45
  return 0;
43
46
  }
47
+
48
+ void zemu_memory_poke(zuint16 address, zuint8 value)
49
+ {
50
+ zuint32 address_32 = address;
51
+ <% memory.each do |mem| %>
52
+ <% next if mem.readonly? %>
53
+ if (address_32 >= 0x<%= mem.address.to_s(16) %> && address_32 < 0x<%= (mem.address + mem.size).to_s(16) %>)
54
+ {
55
+ zemu_memory_block_<%= mem.name %>[address_32 - 0x<%= mem.address.to_s(16) %>] = value;
56
+ return;
57
+ }
58
+ <% end %>
59
+ }
data/src/memory.h.erb CHANGED
@@ -7,3 +7,5 @@ zuint8 zemu_memory_read(void * context, zuint16 address);
7
7
  void zemu_memory_write(void * context, zuint16 address, zuint8 value);
8
8
 
9
9
  zuint8 zemu_memory_peek(zuint16 address);
10
+
11
+ void zemu_memory_poke(zuint16 address, zuint8 value);
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.3
4
+ version: 0.4.2
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-30 00:00:00.000000000 Z
11
+ date: 2021-11-19 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.