zemu 0.3.9 → 0.4.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 +209 -0
- data/lib/zemu/debug.rb +68 -0
- data/lib/zemu/instance.rb +11 -1
- data/lib/zemu/interactive.rb +85 -9
- data/lib/zemu.rb +1 -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: caa59294431c1faadda8328bc7fbefa504526ae43a5531e2cf1d6c2bc7dd5c87
|
4
|
+
data.tar.gz: 72ced5e284f94e586862e80e97f5d95f55a9a3dbc931dc889fdc73352cef3a82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbb449fa4a076320a3073391f98687098e4e0f8b484099a7ca2c67325f857e8923397267dc01c5bee521cf66e9457149a8845e15f4a8b74ae7962e1f84623ae1
|
7
|
+
data.tar.gz: 846f7d09837e09fdb5711ba7d3ee09ca734cf0454c10efaff77a2d92de742e88332cc2c3e008db7026c528460368e410c9c9ebaacf29694e513880255085d579
|
data/lib/zemu/config.rb
CHANGED
@@ -439,6 +439,215 @@ 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
|
+
@blocks = []
|
470
|
+
@initialize_from = nil
|
471
|
+
|
472
|
+
super
|
473
|
+
|
474
|
+
num_sectors.times do
|
475
|
+
sector = []
|
476
|
+
sector_size.times do
|
477
|
+
sector << 0
|
478
|
+
end
|
479
|
+
@blocks << sector
|
480
|
+
end
|
481
|
+
|
482
|
+
# Initialize from provided file if applicable.
|
483
|
+
unless @initialize_from.nil?
|
484
|
+
# Check file size.
|
485
|
+
file_size = File.size(@initialize_from)
|
486
|
+
if (file_size != num_sectors * sector_size)
|
487
|
+
raise RangeError, "Initialization file for Zemu::Config::BlockDrive '#{name}' is of wrong size."
|
488
|
+
end
|
489
|
+
|
490
|
+
File.open(@initialize_from, "rb") do |f|
|
491
|
+
num_sectors.times do |s|
|
492
|
+
sector_size.times do |b|
|
493
|
+
@blocks[s][b] = f.getbyte()
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
when_setup do
|
500
|
+
<<-eos
|
501
|
+
#include <stdio.h>
|
502
|
+
|
503
|
+
zuint8 sector_data_#{name}[#{sector_size}];
|
504
|
+
zuint32 sector_data_#{name}_offset;
|
505
|
+
zuint32 loaded_sector_#{name} = Z_UINT32_MAXIMUM;
|
506
|
+
zuint8 drive_mode_#{name};
|
507
|
+
zuint8 drive_status_#{name} = 0b01000000;
|
508
|
+
|
509
|
+
zuint8 lba_#{name}_0;
|
510
|
+
zuint8 lba_#{name}_1;
|
511
|
+
zuint8 lba_#{name}_2;
|
512
|
+
zuint8 lba_#{name}_3;
|
513
|
+
|
514
|
+
void internal_#{name}_load_sector(zuint32 sector)
|
515
|
+
{
|
516
|
+
if (loaded_sector_#{name} == sector) return;
|
517
|
+
|
518
|
+
FILE * fptr = fopen("#{@initialize_from}", "rb");
|
519
|
+
fseek(fptr, sector * #{sector_size}, SEEK_SET);
|
520
|
+
fread(sector_data_#{name}, #{sector_size}, 1, fptr);
|
521
|
+
fclose(fptr);
|
522
|
+
|
523
|
+
loaded_sector_#{name} = sector;
|
524
|
+
}
|
525
|
+
|
526
|
+
void internal_#{name}_write_current_sector()
|
527
|
+
{
|
528
|
+
FILE * fptr = fopen("#{@initialize_from}", "r+b");
|
529
|
+
fseek(fptr, loaded_sector_#{name} * #{sector_size}, SEEK_SET);
|
530
|
+
fwrite(sector_data_#{name}, 1, #{sector_size}, fptr);
|
531
|
+
fclose(fptr);
|
532
|
+
}
|
533
|
+
|
534
|
+
zuint8 zemu_io_#{name}_readbyte(zuint32 sector, zuint32 offset)
|
535
|
+
{
|
536
|
+
internal_#{name}_load_sector(sector);
|
537
|
+
return sector_data_#{name}[offset];
|
538
|
+
}
|
539
|
+
eos
|
540
|
+
end
|
541
|
+
|
542
|
+
when_read do
|
543
|
+
<<-eos
|
544
|
+
if (port == #{base_port})
|
545
|
+
{
|
546
|
+
zuint8 b = sector_data_#{name}[sector_data_#{name}_offset];
|
547
|
+
sector_data_#{name}_offset++;
|
548
|
+
if (sector_data_#{name}_offset >= #{sector_size})
|
549
|
+
{
|
550
|
+
drive_status_#{name} = 0b01000000;
|
551
|
+
}
|
552
|
+
return b;
|
553
|
+
}
|
554
|
+
else if (port == #{base_port+7})
|
555
|
+
{
|
556
|
+
return drive_status_#{name};
|
557
|
+
}
|
558
|
+
eos
|
559
|
+
end
|
560
|
+
|
561
|
+
when_write do
|
562
|
+
<<-eos
|
563
|
+
if (port == #{base_port})
|
564
|
+
{
|
565
|
+
if (drive_mode_#{name} == 0x01)
|
566
|
+
{
|
567
|
+
sector_data_#{name}[sector_data_#{name}_offset] = value;
|
568
|
+
sector_data_#{name}_offset++;
|
569
|
+
if (sector_data_#{name}_offset >= #{sector_size})
|
570
|
+
{
|
571
|
+
internal_#{name}_write_current_sector();
|
572
|
+
drive_status_#{name} = 0b01000000;
|
573
|
+
}
|
574
|
+
}
|
575
|
+
}
|
576
|
+
else if (port == #{base_port+3})
|
577
|
+
{
|
578
|
+
lba_#{name}_0 = value;
|
579
|
+
}
|
580
|
+
else if (port == #{base_port+4})
|
581
|
+
{
|
582
|
+
lba_#{name}_1 = value;
|
583
|
+
}
|
584
|
+
else if (port == #{base_port+5})
|
585
|
+
{
|
586
|
+
lba_#{name}_2 = value;
|
587
|
+
}
|
588
|
+
else if (port == #{base_port+6})
|
589
|
+
{
|
590
|
+
lba_#{name}_3 = value & 0b00011111;
|
591
|
+
}
|
592
|
+
else if (port == #{base_port+7})
|
593
|
+
{
|
594
|
+
if (value == 0x20)
|
595
|
+
{
|
596
|
+
zuint32 sector = 0;
|
597
|
+
sector |= (zuint32)lba_#{name}_3 << 24;
|
598
|
+
sector |= (zuint32)lba_#{name}_2 << 16;
|
599
|
+
sector |= (zuint32)lba_#{name}_1 << 8;
|
600
|
+
sector |= (zuint32)lba_#{name}_0;
|
601
|
+
|
602
|
+
internal_#{name}_load_sector(sector);
|
603
|
+
sector_data_#{name}_offset = 0;
|
604
|
+
|
605
|
+
drive_mode_#{name} = 0x00;
|
606
|
+
drive_status_#{name} = 0b00001000;
|
607
|
+
}
|
608
|
+
else if (value == 0x30)
|
609
|
+
{
|
610
|
+
zuint32 sector = 0;
|
611
|
+
sector |= (zuint32)lba_#{name}_3 << 24;
|
612
|
+
sector |= (zuint32)lba_#{name}_2 << 16;
|
613
|
+
sector |= (zuint32)lba_#{name}_1 << 8;
|
614
|
+
sector |= (zuint32)lba_#{name}_0;
|
615
|
+
|
616
|
+
internal_#{name}_load_sector(sector);
|
617
|
+
sector_data_#{name}_offset = 0;
|
618
|
+
|
619
|
+
drive_mode_#{name} = 0x01;
|
620
|
+
drive_status_#{name} = 0b00001000;
|
621
|
+
}
|
622
|
+
}
|
623
|
+
eos
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
# Array of sectors of this drive.
|
628
|
+
def blocks
|
629
|
+
@blocks
|
630
|
+
end
|
631
|
+
|
632
|
+
# Set file to initialize from.
|
633
|
+
def initialize_from(file)
|
634
|
+
@initialize_from = file
|
635
|
+
end
|
636
|
+
|
637
|
+
# Defines FFI API which will be available to the instance wrapper if this IO device is used.
|
638
|
+
def functions
|
639
|
+
[
|
640
|
+
{"name" => "zemu_io_#{name}_readbyte", "args" => [:uint32, :uint32], "return" => :uint8},
|
641
|
+
]
|
642
|
+
end
|
643
|
+
|
644
|
+
# Valid parameters for a BlockDrive, along with those
|
645
|
+
# defined in [Zemu::Config::IOPort].
|
646
|
+
def params
|
647
|
+
super + %w(base_port sector_size num_sectors)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
442
651
|
# Non-Maskable Interrupt Timer
|
443
652
|
#
|
444
653
|
# Represents a timer device, the period of which can be controlled
|
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
@@ -142,6 +142,16 @@ module Zemu
|
|
142
142
|
return return_string
|
143
143
|
end
|
144
144
|
|
145
|
+
# Get a byte from the attached disk drive.
|
146
|
+
#
|
147
|
+
# @param sector The sector to read
|
148
|
+
# @param offset The offset in sector to read
|
149
|
+
#
|
150
|
+
# Gets a byte at the given offset in the given sector.
|
151
|
+
def drive_readbyte(sector, offset)
|
152
|
+
return @wrapper.zemu_io_block_drive_readbyte(sector, offset)
|
153
|
+
end
|
154
|
+
|
145
155
|
# Continue running this instance until either:
|
146
156
|
# * A HALT instruction is executed
|
147
157
|
# * A breakpoint is hit
|
@@ -238,7 +248,7 @@ module Zemu
|
|
238
248
|
|
239
249
|
configuration.io.each do |device|
|
240
250
|
device.functions.each do |f|
|
241
|
-
wrapper.attach_function(f["name"], f["args"], f["return"])
|
251
|
+
wrapper.attach_function(f["name"].to_sym, f["args"], f["return"])
|
242
252
|
end
|
243
253
|
end
|
244
254
|
|
data/lib/zemu/interactive.rb
CHANGED
@@ -8,6 +8,8 @@ module Zemu
|
|
8
8
|
def initialize(instance)
|
9
9
|
@instance = instance
|
10
10
|
|
11
|
+
@symbol_table = {}
|
12
|
+
|
11
13
|
@master, @slave = PTY.open
|
12
14
|
log "Opened PTY at #{@slave.path}"
|
13
15
|
end
|
@@ -59,6 +61,9 @@ module Zemu
|
|
59
61
|
memory(cmd[1], cmd[2])
|
60
62
|
end
|
61
63
|
|
64
|
+
elsif cmd[0] == "map"
|
65
|
+
load_map(cmd[1])
|
66
|
+
|
62
67
|
elsif cmd[0] == "help"
|
63
68
|
log "Available commands:"
|
64
69
|
log " continue [<n>] - Continue execution for <n> cycles"
|
@@ -66,6 +71,7 @@ module Zemu
|
|
66
71
|
log " registers - View register contents"
|
67
72
|
log " memory <a> [<n>] - View <n> bytes of memory, starting at address <a>."
|
68
73
|
log " <n> defaults to 1 if omitted."
|
74
|
+
log " map <path> - Load symbols from map file at <path>"
|
69
75
|
log " break <a> - Set a breakpoint at the given address <a>."
|
70
76
|
log " quit - End this emulator instance."
|
71
77
|
|
@@ -79,16 +85,53 @@ module Zemu
|
|
79
85
|
end
|
80
86
|
|
81
87
|
# Outputs a table giving the current values of the instance's registers.
|
88
|
+
# For the 16-bit registers (BC, DE, HL, IX, IY, SP, PC), attempts to identify the symbol
|
89
|
+
# to which they point.
|
82
90
|
def registers
|
83
91
|
log "A: #{r("A")} F: #{r("F")}"
|
84
|
-
|
85
|
-
|
86
|
-
|
92
|
+
|
93
|
+
registers_gp('B', 'C')
|
94
|
+
registers_gp('D', 'E')
|
95
|
+
registers_gp('H', 'L')
|
96
|
+
|
87
97
|
log ""
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
98
|
+
|
99
|
+
register_16("IX")
|
100
|
+
register_16("IY")
|
101
|
+
register_16("SP")
|
102
|
+
register_16("PC")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Displays the value of a 16-bit register.
|
106
|
+
def register_16(r)
|
107
|
+
value = @instance.registers[r]
|
108
|
+
|
109
|
+
log "#{r}: #{r16(r)} (#{get_symbol(value)})"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Displays the value of a general-purpose 16-bit register pair.
|
113
|
+
def registers_gp(hi, lo)
|
114
|
+
value = hilo(@instance.registers[hi], @instance.registers[lo])
|
115
|
+
|
116
|
+
log "#{hi}: #{r(hi)} #{lo}: #{r(lo)} (#{get_symbol(value)})"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Gets the symbol associated with the given value.
|
120
|
+
# If no matching symbol, gives offset from previous symbol.
|
121
|
+
def get_symbol(value)
|
122
|
+
syms = nil
|
123
|
+
addr = value
|
124
|
+
while addr > 0 do
|
125
|
+
syms = @symbol_table[addr]
|
126
|
+
break unless syms.nil?
|
127
|
+
addr -= 1
|
128
|
+
end
|
129
|
+
|
130
|
+
sym = if syms.nil? then nil else syms[0] end
|
131
|
+
|
132
|
+
sym_str = "<#{if sym.nil? then 'undefined' else sym.label end}#{if addr == value then '' else "+#{value-addr}" end}>"
|
133
|
+
|
134
|
+
return sym_str
|
92
135
|
end
|
93
136
|
|
94
137
|
# Returns a particular 8-bit register value.
|
@@ -101,6 +144,11 @@ module Zemu
|
|
101
144
|
return "0x%04x" % @instance.registers[reg]
|
102
145
|
end
|
103
146
|
|
147
|
+
# Concatenates two 8-bit values, in big-endian format.
|
148
|
+
def hilo(hi, lo)
|
149
|
+
return (hi << 8) | lo
|
150
|
+
end
|
151
|
+
|
104
152
|
# Continue for *up to* the given number of cycles.
|
105
153
|
# Fewer cycles may be executed, depending on the behaviour of the processor.
|
106
154
|
def continue(cycles=-1)
|
@@ -114,7 +162,7 @@ module Zemu
|
|
114
162
|
cycles_left = cycles
|
115
163
|
actual_cycles = 0
|
116
164
|
|
117
|
-
serial_count = @instance.serial_delay
|
165
|
+
serial_count = @instance.serial_delay.to_f
|
118
166
|
|
119
167
|
while ((cycles == -1) || (cycles_left > 0))
|
120
168
|
# Get time before execution.
|
@@ -124,7 +172,7 @@ module Zemu
|
|
124
172
|
|
125
173
|
if (serial_count >= @instance.serial_delay)
|
126
174
|
process_serial
|
127
|
-
serial_count = 0
|
175
|
+
serial_count = 0.0
|
128
176
|
end
|
129
177
|
|
130
178
|
cycles_done = @instance.continue(1)
|
@@ -185,6 +233,34 @@ module Zemu
|
|
185
233
|
end
|
186
234
|
end
|
187
235
|
|
236
|
+
# Loads a MAP file from the given path.
|
237
|
+
def load_map(path)
|
238
|
+
if path.nil?
|
239
|
+
log "No path specified."
|
240
|
+
return
|
241
|
+
end
|
242
|
+
|
243
|
+
unless File.exist?(path.to_s)
|
244
|
+
log "Map file '#{path}' does not exist."
|
245
|
+
return
|
246
|
+
end
|
247
|
+
|
248
|
+
if File.directory?(path.to_s)
|
249
|
+
log "Cannot open '#{path}': it is a directory."
|
250
|
+
return
|
251
|
+
end
|
252
|
+
|
253
|
+
syms = {}
|
254
|
+
begin
|
255
|
+
syms.merge! Debug.load_map(path.to_s)
|
256
|
+
rescue ArgumentError => e
|
257
|
+
log "Error loading map file: #{e.message}"
|
258
|
+
syms.clear
|
259
|
+
end
|
260
|
+
|
261
|
+
@symbol_table.merge! syms
|
262
|
+
end
|
263
|
+
|
188
264
|
# Process serial input/output via the TTY.
|
189
265
|
def process_serial
|
190
266
|
# Read/write serial.
|
data/lib/zemu.rb
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.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-10-23 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.
|