zemu 0.3.9 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3278a3fa602a14489f1bfe77bee8d51781be503fac50dba4334dd400f492b5ca
4
- data.tar.gz: 0dc3644e09613e7449bbc2dd3167f4823fca647cd0b5df94b35a0d39768a8b26
3
+ metadata.gz: caa59294431c1faadda8328bc7fbefa504526ae43a5531e2cf1d6c2bc7dd5c87
4
+ data.tar.gz: 72ced5e284f94e586862e80e97f5d95f55a9a3dbc931dc889fdc73352cef3a82
5
5
  SHA512:
6
- metadata.gz: bb87eef723a62c64742f06ec85bf7f5c976103c6e5b8678b5efec665b49ea3242efe59238f1ea28808c7ef3e597471ec050cda5f0152f3c74fa900f88df294ea
7
- data.tar.gz: 29de4f2e78acae5a24a443595e3b683acb3fd3a6d00ec84991c1126e3e7b07ce35078de8721347ec84926ee99fd2bd21912b1ef768fa868c0d1cc75ae966db02
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
 
@@ -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
- log "B: #{r("B")} C: #{r("C")}"
85
- log "D: #{r("D")} E: #{r("E")}"
86
- log "H: #{r("H")} L: #{r("L")}"
92
+
93
+ registers_gp('B', 'C')
94
+ registers_gp('D', 'E')
95
+ registers_gp('H', 'L')
96
+
87
97
  log ""
88
- log "IX: #{r16("IX")}"
89
- log "IY: #{r16("IY")}"
90
- log "SP: #{r16("SP")}"
91
- log "PC: #{r16("PC")}"
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
@@ -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.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zemu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
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: 2020-08-26 00:00:00.000000000 Z
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
- rubyforge_project:
337
- rubygems_version: 2.7.6
337
+ rubygems_version: 3.1.2
338
338
  signing_key:
339
339
  specification_version: 4
340
340
  summary: A configurable Z80 emulator.