ruby6502 0.1.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.
@@ -0,0 +1,20 @@
1
+ #ifndef FAKE6502
2
+ #define FAKE6502
3
+
4
+ void reset6502();
5
+ void step6502();
6
+ void exec6502(uint32_t tickcount);
7
+ void nmi6502();
8
+ void irq6502();
9
+ void hookexternal(void *funcptr);
10
+
11
+ uint16_t getPC();
12
+ uint8_t getSP();
13
+ uint8_t getA();
14
+ uint8_t getX();
15
+ uint8_t getY();
16
+ uint8_t getStatus();
17
+ uint32_t getInstructions();
18
+ uint64_t getTicks();
19
+
20
+ #endif
@@ -0,0 +1,253 @@
1
+ #include <stdlib.h>
2
+ #include <time.h>
3
+ #include <stdint.h>
4
+ #include <ruby.h>
5
+ #include "fake6502.h"
6
+
7
+ static VALUE mRuby6502;
8
+ uint8_t has_instruction_hooks = 0;
9
+ uint8_t has_read_write_hooks = 0;
10
+
11
+ uint16_t rng_hook_address = 0;
12
+
13
+ #define MEMSIZE 0x10000
14
+ uint8_t MEMORY[MEMSIZE] = {0};
15
+
16
+ static VALUE program_counter(VALUE self)
17
+ {
18
+ return UINT2NUM(getPC());
19
+ }
20
+
21
+ static VALUE stack_pointer(VALUE self)
22
+ {
23
+ return UINT2NUM(getSP());
24
+ }
25
+
26
+ static VALUE a_register(VALUE self)
27
+ {
28
+ return UINT2NUM(getA());
29
+ }
30
+
31
+ static VALUE x_register(VALUE self)
32
+ {
33
+ return UINT2NUM(getX());
34
+ }
35
+
36
+ static VALUE y_register(VALUE self)
37
+ {
38
+ return UINT2NUM(getY());
39
+ }
40
+
41
+ static VALUE status_flags(VALUE self)
42
+ {
43
+ return UINT2NUM(getStatus());
44
+ }
45
+
46
+ static VALUE instruction_count(VALUE self)
47
+ {
48
+ return ULONG2NUM(getInstructions());
49
+ }
50
+
51
+ static VALUE tick_count(VALUE self)
52
+ {
53
+ return ULL2NUM(getTicks());
54
+ }
55
+
56
+ static VALUE reset(VALUE self)
57
+ {
58
+ reset6502();
59
+ return Qtrue;
60
+ }
61
+
62
+ static VALUE interrupt_request(VALUE self)
63
+ {
64
+ irq6502();
65
+ return Qtrue;
66
+ }
67
+
68
+ static VALUE non_maskable_interrupt(VALUE self)
69
+ {
70
+ nmi6502();
71
+ return Qtrue;
72
+ }
73
+
74
+ static VALUE step(VALUE self)
75
+ {
76
+ step6502();
77
+ return instruction_count(self);
78
+ }
79
+
80
+ static VALUE step_times(VALUE self, VALUE stepCount)
81
+ {
82
+ int steps = NUM2INT(stepCount);
83
+ for(int i = 0; i < steps; ++i) {
84
+ step6502();
85
+ }
86
+
87
+ return instruction_count(self);
88
+ }
89
+
90
+ static VALUE exec(VALUE self, VALUE tickCount)
91
+ {
92
+ exec6502((uint32_t) NUM2ULONG(tickCount));
93
+ return instruction_count(self);
94
+ }
95
+
96
+ static VALUE memory_size(VALUE self)
97
+ {
98
+ return UINT2NUM(MEMSIZE);
99
+ }
100
+
101
+ static uint8_t read_address(uint16_t address)
102
+ {
103
+ if ( address >= 0 && address < MEMSIZE ) {
104
+ return MEMORY[address];
105
+ } else return 0;
106
+ }
107
+
108
+ uint8_t read6502(uint16_t address)
109
+ {
110
+
111
+ if ( has_read_write_hooks ) {
112
+ rb_funcall(mRuby6502, rb_intern("execute_read_write_hook"), 2, UINT2NUM(address), ID2SYM(rb_intern("read")));
113
+ }
114
+
115
+ return read_address(address);
116
+ }
117
+
118
+ static VALUE read_byte(VALUE self, VALUE location)
119
+ {
120
+ uint16_t address;
121
+
122
+ address = (uint16_t) NUM2UINT(location);
123
+
124
+ return UINT2NUM(read_address(address));
125
+ }
126
+
127
+ static void write_address(uint16_t address, uint8_t value)
128
+ {
129
+ if ( address >= 0 && address < MEMSIZE ) {
130
+ MEMORY[address] = value;
131
+ }
132
+ }
133
+
134
+ void write6502(uint16_t address, uint8_t value)
135
+ {
136
+ if ( has_read_write_hooks ) {
137
+ rb_funcall(mRuby6502, rb_intern("execute_read_write_hook"), 2, INT2NUM(address), ID2SYM(rb_intern("write")));
138
+ }
139
+
140
+ write_address(address, value);
141
+ }
142
+
143
+ static VALUE load_byte(VALUE self, VALUE location, VALUE r_value)
144
+ {
145
+ uint16_t address;
146
+ uint8_t value;
147
+
148
+ address = (uint16_t) NUM2UINT(location);
149
+ value = (uint8_t) NUM2UINT(r_value);
150
+
151
+ write_address(address, value);
152
+
153
+ return location;
154
+ }
155
+
156
+ static VALUE set_instruction_hooks(VALUE self)
157
+ {
158
+ has_instruction_hooks = 1;
159
+ return Qtrue;
160
+ }
161
+
162
+ static VALUE unset_instruction_hooks(VALUE self)
163
+ {
164
+ has_instruction_hooks = 0;
165
+ return Qtrue;
166
+ }
167
+
168
+ static VALUE get_has_instruction_hooks(VALUE self)
169
+ {
170
+ if ( has_instruction_hooks ) {
171
+ return Qtrue;
172
+ } else {
173
+ return Qfalse;
174
+ }
175
+ }
176
+
177
+ static VALUE set_read_write_hooks(VALUE self)
178
+ {
179
+ has_read_write_hooks = 1;
180
+ return Qtrue;
181
+ }
182
+
183
+ static VALUE unset_read_write_hooks(VALUE self)
184
+ {
185
+ has_read_write_hooks = 0;
186
+ return Qtrue;
187
+ }
188
+
189
+ static VALUE get_has_read_write_hooks(VALUE self)
190
+ {
191
+ if ( has_read_write_hooks ) {
192
+ return Qtrue;
193
+ } else {
194
+ return Qfalse;
195
+ }
196
+ }
197
+
198
+ static VALUE configure_rng(VALUE self, VALUE location) {
199
+ uint16_t address;
200
+
201
+ address = (uint16_t) NUM2UINT(location);
202
+
203
+ if ( address > 0 && address < MEMSIZE ) {
204
+ rng_hook_address = address;
205
+ }
206
+ }
207
+
208
+ void execute_instruction_hooks() {
209
+ if ( rng_hook_address ) {
210
+ write_address(rng_hook_address, (uint8_t) rand());
211
+ }
212
+
213
+ if ( has_instruction_hooks ) {
214
+ rb_funcall(mRuby6502, rb_intern("execute_instruction_hooks"), 0);
215
+ }
216
+ }
217
+
218
+ void Init_ruby6502()
219
+ {
220
+ mRuby6502 = rb_define_module("Ruby6502");
221
+ rb_define_singleton_method(mRuby6502, "memory_size", memory_size, 0);
222
+ rb_define_singleton_method(mRuby6502, "read_byte", read_byte, 1);
223
+ rb_define_singleton_method(mRuby6502, "load_byte", load_byte, 2);
224
+
225
+ rb_define_singleton_method(mRuby6502, "program_counter", program_counter, 0);
226
+ rb_define_singleton_method(mRuby6502, "stack_pointer", stack_pointer, 0);
227
+ rb_define_singleton_method(mRuby6502, "a_register", a_register, 0);
228
+ rb_define_singleton_method(mRuby6502, "x_register", x_register, 0);
229
+ rb_define_singleton_method(mRuby6502, "y_register", y_register, 0);
230
+ rb_define_singleton_method(mRuby6502, "status_flags", status_flags, 0);
231
+ rb_define_singleton_method(mRuby6502, "instruction_count", instruction_count, 0);
232
+ rb_define_singleton_method(mRuby6502, "tick_count", tick_count, 0);
233
+
234
+ rb_define_singleton_method(mRuby6502, "set_instruction_hooks", set_instruction_hooks, 0);
235
+ rb_define_singleton_method(mRuby6502, "unset_instruction_hooks", unset_instruction_hooks, 0);
236
+ rb_define_singleton_method(mRuby6502, "instruction_hooks?", get_has_instruction_hooks, 0);
237
+ rb_define_singleton_method(mRuby6502, "configure_rng", configure_rng, 1);
238
+
239
+ rb_define_singleton_method(mRuby6502, "set_read_write_hooks", set_read_write_hooks, 0);
240
+ rb_define_singleton_method(mRuby6502, "unset_read_write_hooks", unset_read_write_hooks, 0);
241
+ rb_define_singleton_method(mRuby6502, "read_write_hooks?", get_has_read_write_hooks, 0);
242
+
243
+ rb_define_singleton_method(mRuby6502, "reset", reset, 0);
244
+ rb_define_singleton_method(mRuby6502, "interrupt_request", interrupt_request, 0);
245
+ rb_define_singleton_method(mRuby6502, "non_maskable_interrupt", non_maskable_interrupt, 0);
246
+ rb_define_singleton_method(mRuby6502, "step", step, 0);
247
+ rb_define_singleton_method(mRuby6502, "step_times", step_times, 1);
248
+ rb_define_singleton_method(mRuby6502, "exec", exec, 1);
249
+
250
+ hookexternal(execute_instruction_hooks);
251
+
252
+ srand ((unsigned int) time (NULL));
253
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby6502
4
+ VERSION = "0.1.0"
5
+ end
data/lib/ruby6502.rb ADDED
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby6502/ruby6502"
4
+
5
+ module Ruby6502
6
+ INSTRUCTION_HOOKS = []
7
+ READ_WRITE_HOOKS = {}
8
+
9
+ class << self
10
+ def load(bytearray, location: 0)
11
+ byte_size = bytearray.size
12
+ location = location.to_i
13
+
14
+ if location < 0
15
+ raise "Cannot load to a negative memory location"
16
+ end
17
+
18
+ if memory_size < location + byte_size
19
+ raise "Loading #{byte_size} bytes to #{format("%04x", location)} would overflow memory"
20
+ end
21
+
22
+ bytearray.each do |byte|
23
+ byte_to_load = byte.to_i & 0xff
24
+ load_byte(location, byte_to_load)
25
+ location += 1
26
+ end
27
+ end
28
+
29
+ def read(location:, bytes:)
30
+ location = location.to_i
31
+ bytes = bytes.to_i
32
+
33
+ unless location >= 0 && location < memory_size
34
+ raise "#{location} is outside memory bounds"
35
+ end
36
+
37
+ unless bytes >= 0
38
+ raise "Must read a positive number of bytes"
39
+ end
40
+
41
+ raise "#{format("%04x", location + bytes)} is outside bounds" if location + bytes > memory_size
42
+
43
+ bytes.times.map do |byte|
44
+ read_byte(location + byte)
45
+ end
46
+ end
47
+
48
+ def register_instruction_hook(&hook)
49
+ set_instruction_hooks unless instruction_hooks?
50
+ INSTRUCTION_HOOKS << hook
51
+ end
52
+
53
+ def clear_instruction_hooks
54
+ unset_instruction_hooks
55
+ INSTRUCTION_HOOKS.clear
56
+ end
57
+
58
+ def register_read_write_hook(location, read_or_write, &hook)
59
+ read_or_write = read_or_write.to_sym
60
+ unless [:read, :write, :read_write].include?(read_or_write)
61
+ raise "#{read_or_write} must be one of :read, :write, :read_write"
62
+ end
63
+
64
+ set_read_write_hooks unless read_write_hooks?
65
+
66
+ if read_or_write == :read_write
67
+ READ_WRITE_HOOKS[[location, :read]] = hook
68
+ READ_WRITE_HOOKS[[location, :write]] = hook
69
+ else
70
+ READ_WRITE_HOOKS[[location, read_or_write]] = hook
71
+ end
72
+ end
73
+
74
+ def deregister_read_write_hook(location, read_or_write)
75
+ if read_or_write == :read_write
76
+ READ_WRITE_HOOKS.delete([location, :read])
77
+ READ_WRITE_HOOKS.delete([location, :write])
78
+ else
79
+ READ_WRITE_HOOKS.delete([location, read_or_write])
80
+ end
81
+
82
+ unset_read_write_hooks if READ_WRITE_HOOKS.empty?
83
+ end
84
+
85
+ private :read_byte, :load_byte, :set_instruction_hooks, :unset_instruction_hooks, :instruction_hooks?,
86
+ :set_read_write_hooks, :unset_read_write_hooks, :read_write_hooks?
87
+
88
+ def execute_instruction_hooks
89
+ INSTRUCTION_HOOKS.each(&:call)
90
+ end
91
+
92
+ def execute_read_write_hook(location, read_or_write)
93
+ READ_WRITE_HOOKS[[location, read_or_write]]&.call(read_or_write)
94
+ end
95
+ end
96
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby6502
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Tate
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.31'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.31'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-shopify
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.8'
83
+ description: A Ruby emulator for the 6502. The 6502 is powered by http://rubbermallet.org/fake6502.c
84
+ email: kbt.tate@gmail.com
85
+ executables: []
86
+ extensions:
87
+ - ext/ruby6502/extconf.rb
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ext/ruby6502/extconf.rb
91
+ - ext/ruby6502/fake6502.c
92
+ - ext/ruby6502/fake6502.h
93
+ - ext/ruby6502/ruby6502.c
94
+ - lib/ruby6502.rb
95
+ - lib/ruby6502/version.rb
96
+ homepage: https://github.com/infiton/ruby6502
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '2.7'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.3.7
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Ruby 6502 emulator
119
+ test_files: []