zemu 0.3.9 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/zemu/config.rb +212 -0
- data/lib/zemu/debug.rb +96 -0
- data/lib/zemu/instance.rb +50 -1
- data/lib/zemu/interactive.rb +94 -12
- data/lib/zemu.rb +12 -7
- data/src/debug.c +5 -0
- data/src/memory.c.erb +22 -6
- data/src/memory.h.erb +2 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7ab3a65fd7f45896cac4297f3715d138a1753def97266f78dd1af1b30e0f012
|
4
|
+
data.tar.gz: 6d33e40e95a4add449616e6793b2817d2a9c5113f7fcc6fe0f7bf075d0d9dec2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4551b92671a66d326ec426cbfd240427d93d181ef000c47bda8505185f9ac865b51411bfc35222d097f79e810af9cb9337a50473c7ef80c5b0a63c7d98fa550f
|
7
|
+
data.tar.gz: aeb983650f57cbb5a7e76b6caffbf81a0ccef919133754fae2608d1a8b04708b329ae5d75e2f5606e8409808b8b456d26b776c28c0ee9a7c39f6f4c1b1ddf038
|
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
|
data/lib/zemu/debug.rb
ADDED
@@ -0,0 +1,96 @@
|
|
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
|
+
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
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
return at_address.sort_by(&:label)
|
42
|
+
end
|
43
|
+
|
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
|
48
|
+
end
|
49
|
+
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Represents a symbol definition, of the form `label = address`.
|
55
|
+
class Symbol
|
56
|
+
# Parse a symbol definition, returning a Symbol instance.
|
57
|
+
def self.parse(s)
|
58
|
+
# Split on whitespace.
|
59
|
+
tokens = s.to_s.split(' ')
|
60
|
+
|
61
|
+
if tokens.size < 3
|
62
|
+
raise ArgumentError, "Invalid symbol definition: '#{s}'"
|
63
|
+
end
|
64
|
+
|
65
|
+
label = tokens[0]
|
66
|
+
|
67
|
+
address = nil
|
68
|
+
|
69
|
+
if /0x[0-9a-fA-F]+/ =~ tokens[2]
|
70
|
+
address = tokens[2][2..-1].to_i(16)
|
71
|
+
elsif /\$[0-9a-fA-F]+/ =~ tokens[2]
|
72
|
+
address = tokens[2][1..-1].to_i(16)
|
73
|
+
elsif /\d+/ =~ tokens[2]
|
74
|
+
address = tokens[2].to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
if address.nil?
|
78
|
+
raise ArgumentError, "Invalid symbol address: '#{tokens[2]}'"
|
79
|
+
end
|
80
|
+
|
81
|
+
return self.new(label, address)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Textual label for this symbol.
|
85
|
+
attr_reader :label
|
86
|
+
|
87
|
+
# Address of this symbol in the binary.
|
88
|
+
attr_reader :address
|
89
|
+
|
90
|
+
def initialize(label, address)
|
91
|
+
@label = label
|
92
|
+
@address = address
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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,6 +61,10 @@ 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
|
57
69
|
@serial_delay = configuration.serial_delay
|
58
70
|
|
@@ -91,6 +103,12 @@ module Zemu
|
|
91
103
|
REGISTERS.each do |reg, num|
|
92
104
|
r[reg] = @wrapper.zemu_debug_register(@instance, num)
|
93
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
|
94
112
|
|
95
113
|
return r
|
96
114
|
end
|
@@ -105,6 +123,16 @@ module Zemu
|
|
105
123
|
return @wrapper.zemu_debug_get_memory(address)
|
106
124
|
end
|
107
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
|
+
|
108
136
|
# Write a string to the serial line of the emulated CPU.
|
109
137
|
#
|
110
138
|
# @param string The string to be sent.
|
@@ -142,6 +170,16 @@ module Zemu
|
|
142
170
|
return return_string
|
143
171
|
end
|
144
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
|
+
|
145
183
|
# Continue running this instance until either:
|
146
184
|
# * A HALT instruction is executed
|
147
185
|
# * A breakpoint is hit
|
@@ -235,16 +273,27 @@ module Zemu
|
|
235
273
|
wrapper.attach_function :zemu_debug_pc, [:pointer], :uint16
|
236
274
|
|
237
275
|
wrapper.attach_function :zemu_debug_get_memory, [:uint16], :uint8
|
276
|
+
wrapper.attach_function :zemu_debug_set_memory, [:uint16, :uint8], :void
|
238
277
|
|
239
278
|
configuration.io.each do |device|
|
240
279
|
device.functions.each do |f|
|
241
|
-
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
|
242
282
|
end
|
243
283
|
end
|
244
284
|
|
245
285
|
return wrapper
|
246
286
|
end
|
247
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
|
+
|
248
297
|
private :make_wrapper
|
249
298
|
end
|
250
299
|
end
|
data/lib/zemu/interactive.rb
CHANGED
@@ -5,9 +5,17 @@ module Zemu
|
|
5
5
|
# Constructor.
|
6
6
|
#
|
7
7
|
# Create a new interactive wrapper for the given instance.
|
8
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
98
|
+
|
99
|
+
registers_gp('B', 'C')
|
100
|
+
registers_gp('D', 'E')
|
101
|
+
registers_gp('H', 'L')
|
102
|
+
|
87
103
|
log ""
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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,7 +168,7 @@ module Zemu
|
|
114
168
|
cycles_left = cycles
|
115
169
|
actual_cycles = 0
|
116
170
|
|
117
|
-
serial_count = @instance.serial_delay
|
171
|
+
serial_count = @instance.serial_delay.to_f
|
118
172
|
|
119
173
|
while ((cycles == -1) || (cycles_left > 0))
|
120
174
|
# Get time before execution.
|
@@ -124,7 +178,7 @@ module Zemu
|
|
124
178
|
|
125
179
|
if (serial_count >= @instance.serial_delay)
|
126
180
|
process_serial
|
127
|
-
serial_count = 0
|
181
|
+
serial_count = 0.0
|
128
182
|
end
|
129
183
|
|
130
184
|
cycles_done = @instance.continue(1)
|
@@ -185,6 +239,34 @@ module Zemu
|
|
185
239
|
end
|
186
240
|
end
|
187
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
|
+
|
188
270
|
# Process serial input/output via the TTY.
|
189
271
|
def process_serial
|
190
272
|
# Read/write serial.
|
@@ -199,12 +281,12 @@ module Zemu
|
|
199
281
|
|
200
282
|
unless input.empty?
|
201
283
|
@instance.serial_puts input
|
202
|
-
log "Serial in: #{input}"
|
284
|
+
log "Serial in: #{input} ($#{input.ord.to_s(16)})" if @print_serial
|
203
285
|
end
|
204
286
|
|
205
287
|
unless output.empty?
|
206
288
|
@master.write output
|
207
|
-
log "Serial out: #{output}"
|
289
|
+
log "Serial out: #{output} ($#{output.ord.to_s(16)})" if @print_serial
|
208
290
|
end
|
209
291
|
end
|
210
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
|
-
|
67
|
-
|
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
|
-
|
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
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 (
|
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 %>[
|
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 (
|
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 %>[
|
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 (
|
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 %>[
|
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
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.5.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:
|
11
|
+
date: 2021-11-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
|
@@ -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
|
-
|
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.
|