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 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.