zemu 0.1.1 → 0.2.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: a6f562e6869c6bcb8df2f0635bf7bc3f70d9668ab91f6d0806e6d1f0c767ec78
4
- data.tar.gz: a83bf92a0244875db8b48d29cdd11eb74ee9ab94c1eb24c6b2255b8d3c81bcee
3
+ metadata.gz: b90e15af47b7ba86466e516ad1d1695c391095e43349536679433d4ac056aa7c
4
+ data.tar.gz: 1d2b05874a9b666c28c467426017ff12fdd1b25fafaadb71d8fcc44e33c89879
5
5
  SHA512:
6
- metadata.gz: 9054649380e73ae6d96d2307c85cc2029d529ef6a0de7f4ebf17d05ac058ed66102b2efccb5a946cc0d828a4932f7121777212e2d8376571adb709aefa08c681
7
- data.tar.gz: 4900928e8fe5f1ba241f3a99f5a17adbfd0b76c9caa08030b4278f8fec9b92ddae61c05326dede2d80ad60917090f4266a6af6b9d9c593178647a583460b57a2
6
+ metadata.gz: 5cb969fc0731adb33864a27eb80314afd926bee8c8a59e3b01164cd0794c764522c3e3171a86d5fccd2babd9273c1067913d8641d8b23e7f41f796670fc0b794
7
+ data.tar.gz: b2a7c1edf04f6a47b0a20ec05e00cd3861d028e81e34d460f98c8006d0f50d792f896dfc7d6d59c1c87b5612f6e72c069025105a4a1a71ab22fa8a1917b64f77
data/lib/zemu/config.rb CHANGED
@@ -224,7 +224,7 @@ module Zemu
224
224
  # Valid parameters for a SerialPort, along with those
225
225
  # defined in [Zemu::Config::IOPort].
226
226
  def params
227
- super + %w(in_port out_port)
227
+ super + %w(in_port out_port ready_port)
228
228
  end
229
229
  end
230
230
 
data/lib/zemu/instance.rb CHANGED
@@ -93,12 +93,14 @@ module Zemu
93
93
  # Gets the given number of characters from the emulated machine's send buffer.
94
94
  #
95
95
  # Note: If count is greater than the number of characters currently in the buffer,
96
- # the returned string may have invalid characters appended to the end.
96
+ # the returned string will be shorter than the given count.
97
97
  def serial_gets(count=nil)
98
98
  return_string = ""
99
99
 
100
- if count.nil?
101
- count = @wrapper.zemu_io_serial_buffer_size()
100
+ actual_count = @wrapper.zemu_io_serial_buffer_size()
101
+
102
+ if count.nil? || actual_count < count
103
+ count = actual_count
102
104
  end
103
105
 
104
106
  count.to_i.times do
@@ -111,8 +113,11 @@ module Zemu
111
113
  # Continue running this instance until either:
112
114
  # * A HALT instruction is executed
113
115
  # * A breakpoint is hit
114
- def continue
115
- @wrapper.zemu_debug_continue(@instance)
116
+ # * The number of cycles given has been executed
117
+ #
118
+ # Returns the number of cycles executed.
119
+ def continue(run_cycles=-1)
120
+ return @wrapper.zemu_debug_continue(@instance, run_cycles)
116
121
  end
117
122
 
118
123
  # Set a breakpoint of the given type at the given address.
@@ -156,7 +161,7 @@ module Zemu
156
161
 
157
162
  wrapper.attach_function :zemu_reset, [:pointer], :void
158
163
 
159
- wrapper.attach_function :zemu_debug_continue, [:pointer], :void
164
+ wrapper.attach_function :zemu_debug_continue, [:pointer, :int64], :uint64
160
165
 
161
166
  wrapper.attach_function :zemu_debug_halted, [], :bool
162
167
  wrapper.attach_function :zemu_debug_break, [], :bool
@@ -0,0 +1,188 @@
1
+ module Zemu
2
+ # An interactive instance of a Zemu emulator.
3
+ # Wraps a Zemu::Instance to allow for user input and debugging.
4
+ class InteractiveInstance
5
+ # Constructor.
6
+ #
7
+ # Create a new interactive wrapper for the given instance.
8
+ def initialize(instance)
9
+ @instance = instance
10
+
11
+ @master, @slave = PTY.open
12
+ log "Opened PTY at #{@slave.path}"
13
+ end
14
+
15
+ # Logs a message to the user output.
16
+ def log(message)
17
+ STDOUT.puts " " + message
18
+ end
19
+
20
+ # Close the interactive wrapper
21
+ def close
22
+ @master.close
23
+ @slave.close
24
+ @instance.quit
25
+ end
26
+
27
+ # Run the interactive emulator until the user exits.
28
+ def run
29
+ quit = false
30
+
31
+ until quit
32
+ print "ZEMU> "
33
+ # Get a command from the user.
34
+ cmd = STDIN.gets.split
35
+
36
+ if cmd[0] == "quit"
37
+ quit = true
38
+
39
+ elsif cmd[0] == "continue"
40
+ if cmd[1].nil?
41
+ continue
42
+ else
43
+ continue(cmd[1].to_i)
44
+ end
45
+
46
+ elsif cmd[0] == "step"
47
+ continue(1)
48
+
49
+ elsif cmd[0] == "registers"
50
+ registers
51
+
52
+ elsif cmd[0] == "break"
53
+ add_breakpoint(cmd[1])
54
+
55
+ elsif cmd[0] == "memory"
56
+ if cmd[2].nil?
57
+ memory(cmd[1])
58
+ else
59
+ memory(cmd[1], cmd[2])
60
+ end
61
+
62
+ elsif cmd[0] == "help"
63
+ log "Available commands:"
64
+ log " continue [<n>] - Continue execution for <n> cycles"
65
+ log " step - Step over a single instruction"
66
+ log " registers - View register contents"
67
+ log " memory <a> [<n>] - View <n> bytes of memory, starting at address <a>."
68
+ log " <n> defaults to 1 if omitted."
69
+ log " break <a> - Set a breakpoint at the given address <a>."
70
+ log " quit - End this emulator instance."
71
+
72
+ else
73
+ log "Invalid command. Type 'help' for available commands."
74
+
75
+ end
76
+ end
77
+
78
+ close
79
+ end
80
+
81
+ # Outputs a table giving the current values of the instance's registers.
82
+ def registers
83
+ 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")}"
87
+ log ""
88
+ log "IX: #{r16("IX")}"
89
+ log "IY: #{r16("IY")}"
90
+ log "SP: #{r16("SP")}"
91
+ log "PC: #{r16("PC")}"
92
+ end
93
+
94
+ # Returns a particular 8-bit register value.
95
+ def r(reg)
96
+ return "0x%02x" % @instance.registers[reg]
97
+ end
98
+
99
+ # Returns a particular 16-bit register value.
100
+ def r16(reg)
101
+ return "0x%04x" % @instance.registers[reg]
102
+ end
103
+
104
+ # Continue for *up to* the given number of cycles.
105
+ # Fewer cycles may be executed, depending on the behaviour of the processor.
106
+ def continue(cycles=-1)
107
+ if cycles == 0
108
+ log "Invalid value: #{cycles}"
109
+ return
110
+ end
111
+
112
+ # Continue executing instruction-by-instruction.
113
+ # Process IO in-between.
114
+ cycles_left = cycles
115
+ actual_cycles = 0
116
+
117
+ while ((cycles == -1) || (cycles_left > 0))
118
+ old_pc = r16("PC")
119
+
120
+ process_serial
121
+ cycles_done = @instance.continue(1)
122
+ cycles_left -= cycles_done
123
+ actual_cycles += cycles_done
124
+
125
+ # Have we hit a breakpoint or HALT instruction?
126
+ if @instance.break?
127
+ log "Hit breakpoint at #{r16("PC")}."
128
+ break
129
+ elsif @instance.halted?
130
+ log "Executed HALT instruction at #{old_pc}."
131
+ break
132
+ end
133
+ end
134
+
135
+ log "Executed for #{actual_cycles} cycles."
136
+ end
137
+
138
+ # Add a breakpoint at the address given by the string.
139
+ def add_breakpoint(addr_str)
140
+ @instance.break(addr_str.to_i(16), :program)
141
+ end
142
+
143
+ # Dump an amount of memory.
144
+ def memory(address, size="1")
145
+ if address.nil?
146
+ log "Expected an address, got #{address}."
147
+ return
148
+ end
149
+
150
+ if (address.to_i(16) < 1 || address.to_i(16) > 0xffff)
151
+ log "Invalid address: 0x%04x" % address.to_i(16)
152
+ return
153
+ end
154
+
155
+ (address.to_i(16)...address.to_i(16) + size.to_i(16)).each do |a|
156
+ m = @instance.memory(a)
157
+ if (m < 32)
158
+ log "%04x: %02x ." % [a, m]
159
+ else
160
+ log ("%04x: %02x " % [a, m]) + m.chr("UTF-8")
161
+ end
162
+ end
163
+ end
164
+
165
+ # Process serial input/output via the TTY.
166
+ def process_serial
167
+ # Read/write serial.
168
+ # Get the strings to be input/output.
169
+ input = ""
170
+ ready = IO.select([@master], [], [], 0)
171
+ unless ready.nil? || ready.empty?
172
+ input = @master.read(1)
173
+ end
174
+
175
+ output = @instance.serial_gets(1)
176
+
177
+ unless input.empty?
178
+ @instance.serial_puts input
179
+ log "Serial in: #{input}"
180
+ end
181
+
182
+ unless output.empty?
183
+ @master.write output
184
+ log "Serial out: #{output}"
185
+ end
186
+ end
187
+ end
188
+ end
data/lib/zemu.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'erb'
2
2
 
3
+ require 'pty'
4
+
3
5
  require_relative 'zemu/config'
4
6
  require_relative 'zemu/instance'
7
+ require_relative 'zemu/interactive'
5
8
 
6
9
  # Zemu is a module providing an interface to build and interact with
7
10
  # configurable Z80 emulators.
@@ -66,6 +69,16 @@ module Zemu
66
69
  return Instance.new(configuration)
67
70
  end
68
71
 
72
+ # Starts an interactive instance of an emulator, according to the given configuration.
73
+ #
74
+ # @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
75
+ def Zemu::start_interactive(configuration)
76
+ instance = start(configuration)
77
+
78
+ interactive = InteractiveInstance.new(instance)
79
+ interactive.run
80
+ end
81
+
69
82
  # Builds a library according to the given configuration.
70
83
  #
71
84
  # @param [Zemu::Config] configuration The configuration for which an emulator will be generated.
data/src/debug.c CHANGED
@@ -8,7 +8,7 @@ RunState zemu_debug_state = UNDEFINED;
8
8
  zuint16 breakpoints[ZEMU_DEBUG_MAX_BREAKPOINTS];
9
9
  unsigned int breakpoint_count = 0;
10
10
 
11
- zusize zemu_debug_continue(Z80 * instance)
11
+ zusize zemu_debug_continue(Z80 * instance, zinteger run_cycles)
12
12
  {
13
13
  /* Return if we've halted. */
14
14
  if (zemu_debug_state == HALTED) return 0;
@@ -17,7 +17,11 @@ zusize zemu_debug_continue(Z80 * instance)
17
17
 
18
18
  zemu_debug_state = RUNNING;
19
19
 
20
- while (zemu_debug_state == RUNNING)
20
+ /* Run as long as:
21
+ * We don't hit a breakpoint
22
+ * We haven't run for more than the number of cycles given
23
+ */
24
+ while (zemu_debug_state == RUNNING && (run_cycles < 0 || cycles < run_cycles))
21
25
  {
22
26
  cycles += zemu_debug_step(instance);
23
27
 
data/src/debug.h CHANGED
@@ -17,7 +17,7 @@ typedef enum RunState
17
17
  UNDEFINED
18
18
  } RunState;
19
19
 
20
- zusize zemu_debug_continue(Z80 * instance);
20
+ zusize zemu_debug_continue(Z80 * instance, zinteger run_cycles);
21
21
 
22
22
  zusize zemu_debug_step(Z80 * instance);
23
23
 
data/src/io.c.erb CHANGED
@@ -92,6 +92,18 @@ zuint8 zemu_io_in(void * context, zuint16 port)
92
92
  {
93
93
  return zemu_io_serial_slave_gets();
94
94
  }
95
+
96
+ else if (port == <%= device.ready_port %>)
97
+ {
98
+ if (io_serial_buffer_master.head == io_serial_buffer_master.tail)
99
+ {
100
+ return 0;
101
+ }
102
+ else
103
+ {
104
+ return 1;
105
+ }
106
+ }
95
107
  <% end %>
96
108
  <% end %>
97
109
  return 0;
metadata CHANGED
@@ -1,16 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zemu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.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-01-31 00:00:00.000000000 Z
11
+ date: 2020-02-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A configurable Z80 emulator gem
13
+ description: |2
14
+ Zemu is a gem which allows the user to configure a Z80-based system
15
+ and then launch emulation instances of that system.
16
+ These instances can be interacted with programmatically, allowing the
17
+ user to inspect the contents of registers and memory, step, add breakpoints,
18
+ and more.
19
+
20
+ The gem requires the user to install a compatible C compiler.
21
+ Currently the only compatible compiler is clang.
22
+
23
+ Please report any issues on the GitHub page for the gem.
24
+ This is accessible under "Homepage".
14
25
  email: jayv136@gmail.com
15
26
  executables: []
16
27
  extensions: []
@@ -19,6 +30,7 @@ files:
19
30
  - lib/zemu.rb
20
31
  - lib/zemu/config.rb
21
32
  - lib/zemu/instance.rb
33
+ - lib/zemu/interactive.rb
22
34
  - src/debug.c
23
35
  - src/debug.h
24
36
  - src/external/Z/API/Z/ABIs/generic/allocator.h
@@ -325,5 +337,5 @@ rubyforge_project:
325
337
  rubygems_version: 2.7.6
326
338
  signing_key:
327
339
  specification_version: 4
328
- summary: zemu
340
+ summary: A configurable Z80 emulator.
329
341
  test_files: []