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 +4 -4
- data/lib/zemu/config.rb +215 -2
- data/lib/zemu/debug.rb +68 -0
- data/lib/zemu/instance.rb +56 -1
- data/lib/zemu/interactive.rb +102 -12
- data/lib/zemu.rb +12 -7
- 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: 0542a6b70d68a1ea2de55fb02802f487ec2ff06f00d824d8113dd8d0b40da40b
|
4
|
+
data.tar.gz: d806954f2a981777e57b2dcd3d8e1ba4946ce62c6ef440d63ceb2993ab1cd82c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
@@ -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
|
-
|
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/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.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:
|
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
|
-
|
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.
|