zemu 0.1.1 → 0.2.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: 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: []