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.
- checksums.yaml +7 -0
- data/ext/ruby6502/extconf.rb +5 -0
- data/ext/ruby6502/fake6502.c +1055 -0
- data/ext/ruby6502/fake6502.h +20 -0
- data/ext/ruby6502/ruby6502.c +253 -0
- data/lib/ruby6502/version.rb +5 -0
- data/lib/ruby6502.rb +96 -0
- metadata +119 -0
@@ -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
|
+
}
|
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: []
|