zemu 0.3.2 → 0.4.1
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 +4 -4
- data/lib/zemu/config.rb +215 -2
- data/lib/zemu/debug.rb +68 -0
- data/lib/zemu/instance.rb +42 -1
- data/lib/zemu/interactive.rb +103 -13
- data/lib/zemu.rb +3 -2
- data/src/debug.c +5 -0
- data/src/io.c.erb +15 -5
- 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: 99d9e825251363ade2a565f28d7545f81b3dc93221951980a12bed3d3b8ab82c
|
4
|
+
data.tar.gz: c93e03bccdf934341909811d4a2d07fb58d31121cd25983b0659495a2d9b176c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c4024d10bfd6521b25f98f8172e40163cd635d354961554406532c48f58b2063a3ac945a0378907c472db3287dff3f8643f7bfbbf256684b81a9511a15dd3e1
|
7
|
+
data.tar.gz: c826fa009c55c9890faa7d9ef5f172c3ec123a1912e55ff75dca57c51e377cac7014b9d07f8ea9ab682e9ae1ea718c0fae70fda42b0eb0a471fe24c2f494ef96
|
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.
|
@@ -54,6 +62,7 @@ module Zemu
|
|
54
62
|
|
55
63
|
def initialize(configuration)
|
56
64
|
@clock = configuration.clock_speed
|
65
|
+
@serial_delay = configuration.serial_delay
|
57
66
|
|
58
67
|
@wrapper = make_wrapper(configuration)
|
59
68
|
|
@@ -73,6 +82,11 @@ module Zemu
|
|
73
82
|
return @clock
|
74
83
|
end
|
75
84
|
|
85
|
+
# Returns the delay between characters on the serial port for this instance in seconds.
|
86
|
+
def serial_delay
|
87
|
+
return @serial_delay
|
88
|
+
end
|
89
|
+
|
76
90
|
# Returns a hash containing current values of the emulated
|
77
91
|
# machine's registers. All names are as those given in the Z80
|
78
92
|
# reference manual.
|
@@ -85,6 +99,12 @@ module Zemu
|
|
85
99
|
REGISTERS.each do |reg, num|
|
86
100
|
r[reg] = @wrapper.zemu_debug_register(@instance, num)
|
87
101
|
end
|
102
|
+
|
103
|
+
REGISTERS_EXTENDED.each do |reg, components|
|
104
|
+
hi = components[0]
|
105
|
+
lo = components[1]
|
106
|
+
r[reg] = (r[hi] << 8) | r[lo]
|
107
|
+
end
|
88
108
|
|
89
109
|
return r
|
90
110
|
end
|
@@ -99,6 +119,16 @@ module Zemu
|
|
99
119
|
return @wrapper.zemu_debug_get_memory(address)
|
100
120
|
end
|
101
121
|
|
122
|
+
# Set the value in memory at a given address.
|
123
|
+
#
|
124
|
+
# @param address The address in memory to be set.
|
125
|
+
# @param value The value to set to.
|
126
|
+
#
|
127
|
+
# Returns nothing.
|
128
|
+
def set_memory(address, value)
|
129
|
+
@wrapper.zemu_debug_set_memory(address, value)
|
130
|
+
end
|
131
|
+
|
102
132
|
# Write a string to the serial line of the emulated CPU.
|
103
133
|
#
|
104
134
|
# @param string The string to be sent.
|
@@ -136,6 +166,16 @@ module Zemu
|
|
136
166
|
return return_string
|
137
167
|
end
|
138
168
|
|
169
|
+
# Get a byte from the attached disk drive.
|
170
|
+
#
|
171
|
+
# @param sector The sector to read
|
172
|
+
# @param offset The offset in sector to read
|
173
|
+
#
|
174
|
+
# Gets a byte at the given offset in the given sector.
|
175
|
+
def drive_readbyte(sector, offset)
|
176
|
+
return @wrapper.zemu_io_block_drive_readbyte(sector, offset)
|
177
|
+
end
|
178
|
+
|
139
179
|
# Continue running this instance until either:
|
140
180
|
# * A HALT instruction is executed
|
141
181
|
# * A breakpoint is hit
|
@@ -229,10 +269,11 @@ module Zemu
|
|
229
269
|
wrapper.attach_function :zemu_debug_pc, [:pointer], :uint16
|
230
270
|
|
231
271
|
wrapper.attach_function :zemu_debug_get_memory, [:uint16], :uint8
|
272
|
+
wrapper.attach_function :zemu_debug_set_memory, [:uint16, :uint8], :void
|
232
273
|
|
233
274
|
configuration.io.each do |device|
|
234
275
|
device.functions.each do |f|
|
235
|
-
wrapper.attach_function(f["name"], f["args"], f["return"])
|
276
|
+
wrapper.attach_function(f["name"].to_sym, f["args"], f["return"])
|
236
277
|
end
|
237
278
|
end
|
238
279
|
|
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,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
|
-
|
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
|
@@ -169,7 +231,7 @@ module Zemu
|
|
169
231
|
|
170
232
|
(address.to_i(16)...address.to_i(16) + size.to_i(16)).each do |a|
|
171
233
|
m = @instance.memory(a)
|
172
|
-
if (m < 32)
|
234
|
+
if (m < 32 || m > 126)
|
173
235
|
log "%04x: %02x ." % [a, m]
|
174
236
|
else
|
175
237
|
log ("%04x: %02x " % [a, m]) + m.chr("UTF-8")
|
@@ -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.
|
@@ -72,10 +73,10 @@ module Zemu
|
|
72
73
|
# Starts an interactive instance of an emulator, according to the given configuration.
|
73
74
|
#
|
74
75
|
# @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
|
75
|
-
def Zemu::start_interactive(configuration)
|
76
|
+
def Zemu::start_interactive(configuration, options = {})
|
76
77
|
instance = start(configuration)
|
77
78
|
|
78
|
-
interactive = InteractiveInstance.new(instance)
|
79
|
+
interactive = InteractiveInstance.new(instance, options)
|
79
80
|
interactive.run
|
80
81
|
end
|
81
82
|
|
data/src/debug.c
CHANGED
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 (
|
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.4.1
|
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-18 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.
|