ruby6502 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|