├── .gitignore
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── package.json
├── src
├── assembler.js
├── clock.js
├── cpu.js
├── mmc.js
├── nes.js
├── ppu.js
└── version.js.rep
└── test
├── assembler.js
├── clock.js
├── cpu.js
├── index.html
├── mmc.js
├── ppu.js
└── support.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /node_modules/
3 | /dist/
4 | /tmp/
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | before_install:
5 | - npm update -g npm
6 | - npm install -g grunt-cli
7 | install: npm install
8 | before_script:
9 | grunt
10 | after_script:
11 | npm run-script coveralls
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | // Project configuration.
3 | grunt.initConfig({
4 | pkg: grunt.file.readJSON('package.json'),
5 | qunit: {
6 | files: ['test/index.html'],
7 | options: {
8 | timeout: 180000, // 3 mins
9 | "--web-security": "no",
10 | coverage: {
11 | src: [ "src/*.js" ],
12 | instrumentedFiles: "temp/",
13 | coberturaReport: "dist/report/",
14 | htmlReport: "dist/report/coverage",
15 | lcovReport: "dist/report/lcov",
16 | linesThresholdPct: 70
17 | }
18 | }
19 | },
20 | 'string-replace': {
21 | version: {
22 | files: {
23 | 'tmp/version.js': 'src/version.js.rep'
24 | },
25 | options: {
26 | replacements: [{
27 | pattern: /{{ VERSION }}/g,
28 | replacement: '<%= pkg.version %>'
29 | }]
30 | }
31 | }
32 | },
33 | jshint: {
34 | files: ['Gruntfile.js', 'src/*.js', 'test/*.js']
35 | },
36 | concat: {
37 | options: {
38 | // define a string to put between each file in the concatenated output
39 | separator: ';'
40 | },
41 | dist: {
42 | // the files to concatenate
43 | src: ['src/*.js', 'tmp/version.js'],
44 | // the location of the resulting JS file
45 | dest: 'dist/<%= pkg.name %>.js'
46 | }
47 | },
48 | uglify: {
49 | options: {
50 | // the banner is inserted at the top of the output
51 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
52 | },
53 | dist: {
54 | files: {
55 | 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
56 | }
57 | }
58 | },
59 | coveralls: {
60 | options: {
61 | // LCOV coverage file relevant to every target
62 | src: 'dist/report/lcov/lcov.info',
63 |
64 | // When true, grunt-coveralls will only print a warning rather than
65 | // an error, to prevent CI builds from failing unnecessarily (e.g. if
66 | // coveralls.io is down). Optional, defaults to false.
67 | force: true
68 | },
69 | ci: {
70 | // Target-specific LCOV coverage file
71 | src: 'dist/report/lcov/lcov.info'
72 | }
73 | },
74 | watch: {
75 | files: ['<%= jshint.files %>'],
76 | tasks: ['jshint', 'qunit']
77 | }
78 | });
79 |
80 | grunt.loadNpmTasks('grunt-qunit-istanbul');
81 | grunt.loadNpmTasks('grunt-string-replace');
82 | grunt.loadNpmTasks('grunt-contrib-jshint');
83 | grunt.loadNpmTasks('grunt-contrib-concat');
84 | grunt.loadNpmTasks('grunt-contrib-uglify');
85 | grunt.loadNpmTasks('grunt-coveralls');
86 |
87 | grunt.registerTask('default', ['jshint', 'string-replace', 'concat', 'uglify']);
88 | grunt.registerTask('test', ['jshint', 'qunit']);
89 | grunt.registerTask('coverage', ['coveralls']);
90 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-nes-emulator
2 |
3 | [](https://travis-ci.org/liamg/js-nes-emulator) [](https://coveralls.io/r/liamg/js-nes-emulator?branch=master)
4 |
5 | NES emulator implemented in JavaScript.
6 |
7 | Everything is NTSC unless specified.
8 |
9 | ## Build
10 |
11 | [Grunt](http://gruntjs.com) is used to build the project:
12 |
13 | $ npm install
14 | $ grunt
15 |
16 | You will then find ``js-nes-emulator.min.js`` in ``dist/``.
17 |
18 | ## Test
19 |
20 | You can run tests with grunt too, via QUnit:
21 |
22 | $ grunt test
23 |
24 | ## Usage
25 |
26 | ```
27 | var nes = new JNE.NES();
28 | nes.loadRom(romData);
29 | nes.start();
30 | ```
31 |
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-nes-emulator",
3 | "version": "0.0.0",
4 | "description": "NES emulator in javascript",
5 | "repository": {
6 | "type": "git",
7 | "url": "git://github.com/liamg/js-nes-emulator.git"
8 | },
9 | "license": "GPL-3.0",
10 | "scripts": {
11 | "test": "grunt --verbose test && grunt --verbose coverage"
12 | },
13 | "devDependencies": {
14 | "grunt": "~0.4.5",
15 | "grunt-cli": "~0.1.13",
16 | "grunt-contrib-qunit": "~0.2.1",
17 | "grunt-contrib-concat": "~0.3.0",
18 | "grunt-contrib-uglify": "~0.2.1",
19 | "grunt-contrib-jshint": "~0.5.4",
20 | "qunit": "~0.7.6",
21 | "qunitjs": "~1.18.0",
22 | "blanket": "~1.1.6",
23 | "coveralls": "~2.11.2",
24 | "grunt-coveralls": "~1.0.0",
25 | "mocha": "~2.2.4",
26 | "grunt-qunit-istanbul": "~0.5.0",
27 | "grunt-string-replace": "~1.2.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/assembler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An assembler for the 6502.
3 | *
4 | * Kind of hacked together, and not as carefully unit tested as the actual NES components.
5 | *
6 | * This is really only here as a debugging tool.
7 | *
8 | */
9 | (function (w) {
10 | "use strict";
11 |
12 | w.JNE = w.JNE || {};
13 |
14 | /**
15 | * @param cpu JNE.CPU The 6502 CPU
16 | * @constructor
17 | */
18 | var Assembler = function (cpu) {
19 | this.cpu = cpu;
20 | };
21 |
22 | Assembler.prototype.AssemblerError = function (message) {
23 | this.name = 'AssemblerError';
24 | this.message = message;
25 | this.stack = (new Error()).stack;
26 | };
27 | Assembler.prototype.AssemblerError.prototype = new Error();
28 | Assembler.prototype.AssemblerError.prototype.constructor = Assembler.prototype.AssemblerError;
29 |
30 | Assembler.prototype.resolveLabels = function(segmentGroups){
31 |
32 | var segmentGroup;
33 | var changes = 0;
34 | var value;
35 | var oldSize;
36 | var newAddressMode;
37 |
38 | var newIns;
39 | var lastGroup = null;
40 |
41 | for(var i =0; i < segmentGroups.length; i++) {
42 |
43 | segmentGroup = segmentGroups[i];
44 |
45 | if(lastGroup !== null) {
46 | segmentGroup.offset = lastGroup.offset;
47 | segmentGroup.offset += lastGroup.size;
48 | }
49 |
50 | if (segmentGroup.memorySegment.type === this.segmentTypes.LABEL) {
51 | value = this.getLabelValue(segmentGroup.memorySegment.labelName, segmentGroups);
52 |
53 | oldSize = segmentGroup.memorySegment.size;
54 |
55 | if(segmentGroup.instructionSegment.isBranch){
56 |
57 | value = value - (segmentGroup.offset + segmentGroup.size);
58 |
59 | // -128 to 127
60 | if(value < -128 || value > 127){
61 | //throw new this.AssemblerError("Attempted to branch to label " + segmentGroup.memorySegment.labelName + ", but it is too far.");
62 | }
63 |
64 | value = value & 0xff;
65 |
66 | oldSize = 1;
67 | }
68 |
69 | var hiByte = (value >> 8) & 0xff;
70 | var loByte = value & 0xff;
71 |
72 | segmentGroup.memorySegment.size = hiByte > 0 ? 2 : 1;
73 |
74 | segmentGroup.memorySegment.value = [];
75 |
76 | segmentGroup.memorySegment.value.push(loByte);
77 | if(segmentGroup.memorySegment.size > 1){
78 | segmentGroup.memorySegment.value.push(hiByte);
79 | }
80 |
81 |
82 |
83 | if(segmentGroup.memorySegment.size !== oldSize){
84 |
85 | //console.log('Adjusted size of memory segment for ' + segmentGroup.raw.trim() + ' to ' + segmentGroup.memorySegment.size + ' because ' + segmentGroup.memorySegment.labelName + '=' + value);
86 |
87 | //Switch to correct opcode
88 | if('addressModeOptions' in segmentGroup.memorySegment){
89 |
90 | segmentGroup.memorySegment.value = [];
91 |
92 | newAddressMode = segmentGroup.memorySegment.addressModeOptions[segmentGroup.memorySegment.size];
93 | newIns = this.buildInstructionSegment(segmentGroup.instructionSegment.operator, newAddressMode);
94 | if(newIns !== null){
95 |
96 | segmentGroup.memorySegment.value.push(loByte);
97 | if(segmentGroup.memorySegment.size > 1){
98 | segmentGroup.memorySegment.value.push(hiByte);
99 | }
100 |
101 | segmentGroup.instructionSegment = newIns;
102 | changes++;
103 | }else{
104 | //we failed to swap opcodes so put everything back as it was
105 | segmentGroup.memorySegment.size = oldSize;
106 |
107 | segmentGroup.memorySegment.value.push(loByte);
108 | if(oldSize > 1){
109 | segmentGroup.memorySegment.value.push(hiByte);
110 | }
111 |
112 | }
113 | }
114 | }
115 | }
116 |
117 | segmentGroup.size = segmentGroup.instructionSegment.size + segmentGroup.memorySegment.size;
118 |
119 | lastGroup = segmentGroups[i];
120 | }
121 |
122 | if(changes > 0){ //call recursively until we've shuffled everything enough!
123 | return this.resolveLabels(segmentGroups);
124 | }else{
125 |
126 | // mark labels as resolved
127 | for(i =0; i < segmentGroups.length; i++) {
128 | segmentGroup = segmentGroups[i];
129 |
130 | if (segmentGroup.memorySegment.type === this.segmentTypes.LABEL) {
131 | segmentGroup.memorySegment.type = this.segmentTypes.MEMORY;
132 | }
133 | }
134 |
135 | return segmentGroups;
136 | }
137 |
138 | };
139 |
140 | Assembler.prototype.getLabelValue = function(labelName, segmentGroups){
141 |
142 | var segmentGroup;
143 |
144 | for(var i =0; i < segmentGroups.length; i++) {
145 |
146 | segmentGroup = segmentGroups[i];
147 |
148 | if (segmentGroup.instructionSegment.type === this.segmentTypes.LABEL) {
149 | if (segmentGroup.instructionSegment.labelName === labelName) {
150 | return segmentGroup.offset;
151 | }
152 | }else if (segmentGroup.instructionSegment.type === this.segmentTypes.VARIABLE) {
153 | if (segmentGroup.instructionSegment.labelName === labelName) {
154 | return segmentGroup.instructionSegment.value;
155 | }
156 | }
157 | }
158 |
159 | throw new this.AssemblerError("Missing declaration for label: " + labelName);
160 |
161 | };
162 |
163 | Assembler.prototype.assembleLine = function (code, lineNo, offset) {
164 | code = code.replace(/;.*/, '');
165 | code = code.replace(/[\r\n]*/, '');
166 | code = code.replace(/^\s+|\s+$/gm,''); //trim
167 | code = code.toUpperCase();
168 |
169 | if(code === '') return null;
170 |
171 | var parts = code.split(/\s+/);
172 | var operator = parts[0];
173 |
174 | if(!(operator in this.cpu.opcodes)){
175 | if(operator.substring(operator.length-1,operator.length) === ':'){
176 | var labelName = operator.substring(0,operator.length-1);
177 | return {
178 | offset: offset,
179 | size: 0,
180 | type: this.segmentGroupTypes.GHOST,
181 | instructionSegment: {
182 | type: this.segmentTypes.LABEL,
183 | labelName: labelName,
184 | size: 0
185 | },
186 | memorySegment: {size: 0}
187 | };
188 | }else if(operator === 'DEFINE'){
189 | if(parts.length < 3){
190 | throw new this.AssemblerError('Missing parameter in define on line ' + lineNo);
191 | }
192 |
193 | return {
194 | offset: offset,
195 | size: 0,
196 | type: this.segmentGroupTypes.GHOST,
197 | instructionSegment: {
198 | size: 0,
199 | type: this.segmentTypes.VARIABLE,
200 | labelName: parts[1],
201 | value: this.parseStringToByteArray(parts[2], lineNo)[0] // @todo verify whether this will only ever be one byte - i.e. no define XYZ $1010
202 | },
203 | memorySegment: {size: 0}
204 | };
205 | }else {
206 | throw new this.AssemblerError('Unknown operator "' + operator + '" on line ' + lineNo);
207 | }
208 | }
209 |
210 | var addressData = this.getAddressData(parts, lineNo);
211 |
212 | var segment = this.buildInstructionSegment(operator, addressData.addressMode);
213 |
214 | if(segment === null){
215 | throw new this.AssemblerError('Invalid memory address mode (' + this.cpu.getAddressModeText(addressData.addressMode) + ') for operator ' + operator + ' on line ' + lineNo);
216 | }
217 |
218 | return {
219 | size: 1 + addressData.memoryAddressSegment.size,
220 | offset: offset,
221 | type: this.segmentGroupTypes.NORMAL,
222 | instructionSegment: segment,
223 | memorySegment: addressData.memoryAddressSegment
224 | };
225 | };
226 |
227 | Assembler.prototype.isBranchOperator = function(op){
228 | var bops = ['BEQ', 'BNE', 'BCS', 'BCC', 'BMI', 'BPL', 'BVC', 'BVS'];
229 | return bops.indexOf(op) !== -1;
230 | };
231 |
232 | Assembler.prototype.buildInstructionSegment = function(operator, addressMode){
233 |
234 | for(var opcode in this.cpu.instruction_table){
235 | if(addressMode instanceof Array){
236 | for(var i = 0; i < addressMode.length; i++){
237 | if (this.cpu.instruction_table[opcode][0] === this.cpu.opcodes[operator] && this.cpu.instruction_table[opcode][1] === addressMode[i]) {
238 |
239 | return {
240 | value: parseInt(opcode, 10),
241 | type: this.segmentTypes.INSTRUCTION,
242 | operator: operator,
243 | isBranch: this.isBranchOperator(operator),
244 | size: 1
245 | };
246 | }
247 | }
248 | }else {
249 | if (this.cpu.instruction_table[opcode][0] === this.cpu.opcodes[operator] && this.cpu.instruction_table[opcode][1] === addressMode) {
250 |
251 | return {
252 | value: parseInt(opcode, 10),
253 | type: this.segmentTypes.INSTRUCTION,
254 | operator: operator,
255 | isBranch: this.isBranchOperator(operator),
256 | size: 1
257 | };
258 | }
259 | }
260 | }
261 |
262 | return null;
263 | };
264 |
265 | Assembler.prototype.segmentTypes = {
266 | INSTRUCTION: 1,
267 | MEMORY: 2,
268 | LABEL: 3,
269 | VARIABLE: 4
270 | };
271 |
272 | Assembler.prototype.segmentGroupTypes = {
273 | NORMAL: 1,
274 | GHOST: 2
275 | };
276 |
277 | Assembler.prototype.getAddressData = function(parts, lineNo){
278 |
279 | // http://www.emulator101.com.s3-website-us-east-1.amazonaws.com/6502-addressing-modes/
280 |
281 | var data = {};
282 |
283 | var matches;
284 |
285 | var immediateRegex = /^#(\$?[0-9A-F]+)$/;
286 | var immediateLabelRegex = /^#([A-Z_]+)$/;
287 | var indirectRegex = /^\(\s*(\$[0-9A-F]+)\s*\)$/;
288 | var indirectLabelRegex = /^\(\s*([A-Z][A-Z_]*)\s*\)$/;
289 | var indexedRegex = /^(\$[0-9A-F]+)\s*,\s*(X|Y)$/;
290 | var indexedLabelRegex = /^([A-Z][A-Z_]*)\s*,\s*(X|Y)$/;
291 | var indexedIndirectRegex = /^\(\s*(\$[0-9A-F]+)\s*,\s*(X)\s*\)$/;
292 | var indexedIndirectLabelRegex = /^\(\s*([A-Z][A-Z_]*)\s*,\s*(X)\s*\)$/;
293 | var indirectIndexedRegex = /^\(\s*(\$[0-9A-F]+)\s*\)\s*,\s*(Y)$/;
294 | var indirectIndexedLabelRegex = /^\(\s*([A-Z][A-Z_]*)\s*\)\s*,\s*(Y)$/;
295 | var labelRegex = /^([A-Z_]+)$/;
296 |
297 | var indexed, offset;
298 |
299 | // @todo separate label/literal regexes so we can behave differently for later replacements
300 |
301 | if(parts.length === 1) {
302 | data.addressMode = [this.cpu.addressModes.IMPLICIT, this.cpu.addressModes.ACCUMULATOR];
303 | data.memoryAddressSegment = {
304 | size: 0,
305 | value: [],
306 | type: this.segmentTypes.MEMORY
307 | };
308 | }else if((matches = parts[1].match(immediateRegex)) !== null){
309 | data.addressMode = this.cpu.addressModes.IMMEDIATE;
310 | data.memoryAddressSegment = {
311 | size: 1,
312 | value: this.parseStringToByteArray(matches[1], lineNo),
313 | type: this.segmentTypes.MEMORY
314 | };
315 | }else if((matches = parts[1].match(immediateLabelRegex)) !== null){
316 | data.addressMode = this.cpu.addressModes.IMMEDIATE;
317 | data.memoryAddressSegment = {
318 | size: 1,
319 | type: this.segmentTypes.LABEL,
320 | labelName: matches[1]
321 | };
322 | }else if((matches = parts[1].match(labelRegex)) !== null){
323 |
324 | // this is sneaky. we set a "byte" value as a complete string: the label's name.
325 | // Once we have built the rest of the code, we can find the location of the label definition and swap
326 | // this original label name out for the offset value as a byte.
327 | var label = matches[1];
328 | data.addressMode = [this.cpu.addressModes.RELATIVE, this.cpu.addressModes.ZERO_PAGE, this.cpu.addressModes.ABSOLUTE];
329 | data.memoryAddressSegment = {
330 | size: 1,
331 | labelName: label,
332 | type: this.segmentTypes.LABEL,
333 | addressModeOptions: {
334 | 0: [],
335 | 1: [this.cpu.addressModes.RELATIVE, this.cpu.addressModes.ZERO_PAGE],
336 | 2: this.cpu.addressModes.ABSOLUTE
337 | }
338 | };
339 |
340 | }else if((matches = parts[1].match(indirectRegex)) !== null){
341 | data.addressMode = this.cpu.addressModes.INDIRECT_ABSOLUTE;
342 | data.memoryAddressSegment = {
343 | size: 2,
344 | value: this.parseStringToByteArray(matches[1], lineNo),
345 | type: this.segmentTypes.MEMORY
346 | };
347 | }else if((matches = parts[1].match(indirectLabelRegex)) !== null){
348 | data.addressMode = this.cpu.addressModes.INDIRECT_ABSOLUTE;
349 | data.memoryAddressSegment = {
350 | size: 2,
351 | type: this.segmentTypes.LABEL,
352 | labelName: matches[1]
353 | };
354 | }else if((matches = parts[1].match(indirectIndexedRegex)) !== null){
355 | data.addressMode = this.cpu.addressModes.INDIRECT_INDEXED;
356 | data.memoryAddressSegment = {
357 | size: 2,
358 | value: this.parseStringToByteArray(matches[1], lineNo),
359 | type: this.segmentTypes.MEMORY
360 | };
361 | }else if((matches = parts[1].match(indirectIndexedLabelRegex)) !== null){
362 | data.addressMode = this.cpu.addressModes.INDIRECT_INDEXED;
363 | data.memoryAddressSegment = {
364 | size: 2,
365 | type: this.segmentTypes.LABEL,
366 | labelName: matches[1]
367 | };
368 | }else if((matches = parts[1].match(indexedIndirectRegex)) !== null){
369 | data.addressMode = this.cpu.addressModes.INDEXED_INDIRECT;
370 | data.memoryAddressSegment = {
371 | size: 2,
372 | value: this.parseStringToByteArray(matches[1], lineNo),
373 | type: this.segmentTypes.MEMORY
374 | };
375 | }else if((matches = parts[1].match(indexedIndirectLabelRegex)) !== null){
376 | data.addressMode = this.cpu.addressModes.INDEXED_INDIRECT;
377 | data.memoryAddressSegment = {
378 | size: 2,
379 | type: this.segmentTypes.LABEL,
380 | labelName: matches[1]
381 | };
382 | }else if((matches = parts[1].match(indexedRegex)) !== null){ // (ABSOLUTE|ZEROPAGE) INDEXED
383 | indexed = matches[1];
384 | offset = matches[2];
385 |
386 | data.memoryAddressSegment = {
387 | size: 2,
388 | value: this.parseStringToByteArray(indexed, lineNo),
389 | type: this.segmentTypes.MEMORY
390 | };
391 |
392 | if(data.memoryAddressSegment.value.length === 1){
393 | data.memoryAddressSegment.size = 1;
394 | if(offset === 'X'){
395 | data.addressMode = this.cpu.addressModes.ZERO_PAGE_X;
396 | }else if(offset === 'Y'){
397 | data.addressMode = this.cpu.addressModes.ZERO_PAGE_Y;
398 | }else{
399 | throw new this.AssemblerError('Invalid zero page memory addressing on line ' + lineNo + '. Must use X or Y - found ' + offset);
400 | }
401 | }else if(data.memoryAddressSegment.value.length !== 1){ //assume absolute for unresolved labels
402 | if(offset === 'X') {
403 | data.addressMode = this.cpu.addressModes.ABSOLUTE_X;
404 | }else if(offset === 'Y'){
405 | data.addressMode = this.cpu.addressModes.ABSOLUTE_Y;
406 | }else{
407 | throw new this.AssemblerError('Invalid absolute memory addressing on line ' + lineNo + '. Must use X or Y - found ' + offset);
408 | }
409 | }else{
410 | throw new this.AssemblerError('Invalid memory addressing on line ' + lineNo);
411 | }
412 |
413 | }else if((matches = parts[1].match(indexedLabelRegex)) !== null){ // (ABSOLUTE|ZEROPAGE) INDEXED
414 | indexed = matches[1];
415 | offset = matches[2];
416 |
417 | data.memoryAddressSegment = {
418 | size: 1,
419 | labelName: indexed,
420 | type: this.segmentTypes.LABEL
421 | };
422 |
423 |
424 | if(offset === 'X'){
425 | data.addressMode = this.cpu.addressModes.ZERO_PAGE_X;
426 | data.memoryAddressSegment.addressModeOptions = {
427 | 0: [],
428 | 1: this.cpu.addressModes.ZERO_PAGE_X,
429 | 2: this.cpu.addressModes.ABSOLUTE_X
430 | };
431 | }else if(offset === 'Y'){
432 | data.addressMode = this.cpu.addressModes.ZERO_PAGE_Y;
433 | data.memoryAddressSegment.addressModeOptions = {
434 | 0: [],
435 | 1: this.cpu.addressModes.ZERO_PAGE_Y,
436 | 2: this.cpu.addressModes.ABSOLUTE_Y
437 | };
438 | }else{
439 | throw new this.AssemblerError('Invalid zero page memory addressing on line ' + lineNo + '. Must use X or Y - found ' + offset);
440 | }
441 |
442 |
443 | }else if(parts[1].substring(0,1) === '$'){
444 |
445 |
446 | data.memoryAddressSegment = {
447 | value: this.parseStringToByteArray(parts[1], lineNo),
448 | type: this.segmentTypes.MEMORY,
449 | size: 1
450 | };
451 |
452 | data.memoryAddressSegment.size = data.memoryAddressSegment.value.length;
453 |
454 | if(data.memoryAddressSegment.value.length === 1){
455 | data.addressMode = this.cpu.addressModes.ZERO_PAGE;
456 | }else if(data.memoryAddressSegment.value.length === 2){
457 | data.addressMode = this.cpu.addressModes.ABSOLUTE;
458 | }else{
459 | throw new this.AssemblerError('Invalid memory addressing on line ' + lineNo);
460 | }
461 | }else{
462 | throw new this.AssemblerError('Invalid memory addressing on line ' + lineNo);
463 | }
464 |
465 | return data;
466 | };
467 |
468 | Assembler.prototype.hexDumpSegmentGroup = function(segmentGroup){
469 |
470 | if(segmentGroup.type === this.segmentGroupTypes.GHOST) {
471 | return '';
472 | }
473 |
474 | var hex = '0x' + segmentGroup.instructionSegment.value.toString(16);
475 |
476 | if(typeof segmentGroup.memorySegment.value !== 'undefined') {
477 | for (var i = 0; i < segmentGroup.memorySegment.value.length; i++) {
478 | hex += ' 0x' + segmentGroup.memorySegment.value[i].toString(16);
479 | }
480 | }
481 |
482 | return hex;
483 | };
484 |
485 | Assembler.prototype.assemble = function (code) {
486 | this.labels = [];
487 | var lines = code.split(/\n/);
488 | var assembled = [];
489 | var segmentGroup;
490 | var offset = 0x600; // change this?
491 | for(var i =0; i < lines.length; i++){
492 | segmentGroup = this.assembleLine(lines[i], i+1, offset);
493 | if(segmentGroup !== null) {
494 | segmentGroup.raw = lines[i];
495 | if(segmentGroup.type !== this.segmentGroupTypes.GHOST) {
496 | offset += (1 + segmentGroup.memorySegment.size);
497 | }
498 | assembled.push(segmentGroup);
499 | }
500 | }
501 | assembled = this.resolveLabels(assembled);
502 |
503 | return this.flatten(assembled);
504 | };
505 |
506 | Assembler.prototype.flatten = function(segmentGroups){
507 |
508 | var bytes = [];
509 |
510 | for(var i = 0; i < segmentGroups.length; i++){
511 |
512 | if(segmentGroups[i].type === this.segmentGroupTypes.GHOST) {
513 | continue;
514 | }
515 |
516 | //console.log(this.hexDumpSegmentGroup(segmentGroups[i]) + ' <-- ' + segmentGroups[i].raw);
517 |
518 | //Add byte for instruction
519 | bytes.push(segmentGroups[i].instructionSegment.value);
520 |
521 | // Add byte(s) for memory
522 | if(segmentGroups[i].memorySegment.type === this.segmentTypes.MEMORY) {
523 | bytes.push.apply(bytes, segmentGroups[i].memorySegment.value);
524 | }else if(segmentGroups[i].memorySegment.type === this.segmentTypes.LABEL) {
525 | throw new this.AssemblerError('Invalid segment type - label "' + segmentGroups[i].memorySegment.labelName + '" was not resolved');
526 | }else if(segmentGroups[i].memorySegment.type === this.segmentTypes.VARIABLE) {
527 | throw new this.AssemblerError('Invalid segment type - variable "' + segmentGroups[i].memorySegment.labelName + '" was not resolved');
528 | }else{
529 | throw new this.AssemblerError('Invalid segment type. Fatal error.');
530 | }
531 | }
532 |
533 | return bytes;
534 | };
535 |
536 | Assembler.prototype.parseStringToByteArray = function(num, lineNo){
537 |
538 | var bytes = [];
539 |
540 | var base = 16;
541 |
542 | if(num.substring(0,1) === '$') {
543 | num = num.substring(1, num.length);
544 | }else if(num.match(/^[0-9]+$/)){
545 | base = 10;
546 | }else{
547 | return [];
548 | }
549 |
550 | num = num.replace(/^0+/, '');
551 |
552 | if(num.length % 2 === 1 || num.length === 0){
553 | num = '0' + num;
554 | }
555 |
556 | var segments = num.match(/.{1,2}/g);
557 |
558 | if(segments !== null){
559 | for(var i = segments.length - 1; i >= 0; i--){
560 | bytes.push(parseInt(segments[i], base));
561 | }
562 | }else{
563 | throw new this.AssemblerError("Cannot parse non-numeric value on line " + lineNo + ": " + num);
564 | }
565 |
566 |
567 |
568 | return bytes;
569 | };
570 |
571 | w.JNE.Assembler = Assembler;
572 |
573 | })(window);
574 |
--------------------------------------------------------------------------------
/src/clock.js:
--------------------------------------------------------------------------------
1 | (function (w) {
2 | "use strict";
3 |
4 | w.JNE = w.JNE || {};
5 |
6 | var Clock = function () {
7 |
8 | this.clockSpeed = 21477272; // Hz (NTSC)
9 | this.cpuDivisor = 12;
10 |
11 | this.cpuClockSpeed = this.clockSpeed / this.cpuDivisor; // Hz
12 | this.tickCallback = function(){};
13 | this.setTickInterval(20);
14 | };
15 |
16 | /**
17 | *
18 | * @param tickInterval Tick interval (ms)
19 | */
20 | Clock.prototype.setTickInterval = function(tickInterval){
21 | this.tickInterval = tickInterval;
22 | var cyclesPerMilliSecond = this.cpuClockSpeed / 1000;
23 | this.cpuCyclesPerTick = cyclesPerMilliSecond * tickInterval;
24 | };
25 |
26 | Clock.prototype.onTick = function(callback){
27 | this.tickCallback = callback;
28 | };
29 |
30 | Clock.prototype.start = function(){
31 | this.tickInterval = setInterval(this.tickCallback, this.tickInterval, this.cpuCyclesPerTick);
32 | };
33 |
34 | Clock.prototype.stop = function(){
35 | clearInterval(this.tickInterval);
36 | };
37 |
38 | Clock.prototype.reset = function(){
39 | clearInterval(this.tickInterval);
40 | };
41 |
42 | w.JNE.Clock = Clock;
43 |
44 | })(window);
45 |
--------------------------------------------------------------------------------
/src/cpu.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A simulation of the 6502 CPU (without decimal support).
3 | *
4 | * Useful links:
5 | *
6 | * 6502 Overview
7 | * http://en.wikipedia.org/wiki/MOS_Technology_6502
8 | *
9 | * Online emulator with debug mode
10 | * http://skilldrick.github.io/easy6502/
11 | *
12 | * CPU Docs
13 | * http://www.llx.com/~nparker/a2/opcodes.html
14 | * http://www.obelisk.demon.co.uk/6502/reference.html#LDA
15 | * http://www.6502.org/tutorials/65c02opcodes.html
16 | */
17 | (function (w) {
18 | "use strict";
19 |
20 | w.JNE = w.JNE || {};
21 |
22 | /**
23 | * @param mmc JNE.MMC Memory controller
24 | * @constructor
25 | */
26 | var CPU = function (mmc) {
27 |
28 | this.mmc = mmc;
29 |
30 | this.registers = {
31 | A: 0, //accumulator
32 | X: 0,
33 | Y: 0,
34 | SP: 0, //stack pointer - stack is at $0100 - $01ff
35 | PC: 0, //program counter
36 | P: 0 //status
37 | };
38 |
39 | this.flags = {
40 | carry: 0,
41 | zero: 0,
42 | negative: 0,
43 | interruptDisable: 0,
44 | decimal: 0, //not supported by this version
45 | brk: 0,
46 | overflow: 0,
47 | unused: 0
48 | };
49 |
50 | this.reset();
51 |
52 | };
53 |
54 | CPU.prototype.CPUError = function (message) {
55 | this.name = 'CPUError';
56 | this.message = message;
57 | this.stack = (new Error()).stack;
58 | };
59 | CPU.prototype.CPUError.prototype = new Error();
60 | CPU.prototype.CPUError.prototype.constructor = CPU.prototype.CPUError;
61 |
62 | /**
63 | * Enumeration of interrupt request types
64 | */
65 | CPU.prototype.IRQ = {
66 | NORMAL: 0,
67 | NMI: 1,
68 | RESET: 2
69 | };
70 |
71 | /**
72 | * Reset the CPU
73 | */
74 | CPU.prototype.reset = function () {
75 |
76 | this.registers.A = 0;
77 | this.registers.X = 0;
78 | this.registers.Y = 0;
79 | this.registers.SP = 0x1FF;
80 | this.registers.PC = 0x07FF;
81 | this.registers.P = 0;
82 |
83 | this.setZeroFlag();
84 | this.clearCarryFlag();
85 | this.clearNegativeFlag();
86 | this.setInterruptDisableFlag();
87 | this.clearDecimalFlag();
88 | this.setBrkFlag();
89 | this.clearOverflowFlag();
90 | this.setUnusedFlag();
91 |
92 | this.setPFromFlags();
93 |
94 | this.mmc.reset();
95 |
96 | /**
97 | $0000-$07FF $800 2KB of work RAM
98 | $0800-$0FFF $800 Mirror of $000-$7FF
99 | $1000-$17FF $800 Mirror of $000-$7FF
100 | $1800-$1FFF $800 Mirror of $000-$7FF
101 | $2000-$2007 $8 PPU Ctrl Registers
102 | $2008-$3FFF $1FF8 *Mirror of $2000-$2007
103 | $4000-$4020 $20 Registers (Mostly APU)
104 | $4020-$5FFF $1FDF Cartridge Expansion ROM
105 | $6000-$7FFF $2000 SRAM
106 | $8000-$BFFF $4000 PRG-ROM
107 | $C000-$FFFF $4000 PRG-ROM
108 |
109 | http://wiki.nesdev.com/w/index.php/CPU_power_up_state
110 |
111 | */
112 |
113 | for (var i = 0; i < 0x2000; i++) {
114 | this.mmc.store(i, 0xFF);
115 | }
116 |
117 | this.mmc.store(0x008, 0xF7);
118 | this.mmc.store(0x009, 0xEF);
119 | this.mmc.store(0x00A, 0xDF);
120 | this.mmc.store(0x00F, 0xBF);
121 | this.mmc.store(0x808, 0xF7);
122 | this.mmc.store(0x809, 0xEF);
123 | this.mmc.store(0x80A, 0xDF);
124 | this.mmc.store(0x80F, 0xBF);
125 | this.mmc.store(0x1008, 0xF7);
126 | this.mmc.store(0x1009, 0xEF);
127 | this.mmc.store(0x100A, 0xDF);
128 | this.mmc.store(0x100F, 0xBF);
129 | this.mmc.store(0x1808, 0xF7);
130 | this.mmc.store(0x1809, 0xEF);
131 | this.mmc.store(0x180A, 0xDF);
132 | this.mmc.store(0x180F, 0xBF);
133 |
134 | for (i = 0x2000; i < this.mmc.size; i++) {
135 | this.mmc.store(i, 0);
136 | }
137 | };
138 |
139 | /**
140 | * Determine and set the CPU status (P register) from the individual bit flags
141 | */
142 | CPU.prototype.setPFromFlags = function () {
143 | this.registers.P = (this.flags.carry |
144 | (this.flags.zero << 1) |
145 | (this.flags.interruptDisable << 2) |
146 | (this.flags.decimal << 3) |
147 | (this.flags.brk << 4) |
148 | (this.flags.unused << 5) |
149 | (this.flags.overflow << 6) |
150 | (this.flags.negative << 7)) & 0xff;
151 | }; // 00110110
152 |
153 | CPU.prototype.setFlagsFromP = function () {
154 | this.flags.carry = this.registers.P & 0x1;
155 | this.flags.zero = (this.registers.P >> 1) & 0x1;
156 | this.flags.interruptDisable = (this.registers.P >> 2) & 0x1;
157 | this.flags.decimal = (this.registers.P >> 3) & 0x1;
158 | this.flags.brk = (this.registers.P >> 4) & 0x1;
159 | this.flags.unused = (this.registers.P >> 5) & 0x1;
160 | this.flags.overflow = (this.registers.P >> 6) & 0x1;
161 | this.flags.negative = (this.registers.P >> 7) & 0x1;
162 | };
163 |
164 | CPU.prototype.checkCarryFlag = function (value) {
165 | if (value > 0xff) {
166 | this.setCarryFlag();
167 | } else {
168 | this.clearCarryFlag();
169 | }
170 | };
171 |
172 | CPU.prototype.setCarryFlag = function () {
173 | this.flags.carry = 1;
174 | this.setPFromFlags();
175 | };
176 |
177 | CPU.prototype.clearCarryFlag = function () {
178 | this.flags.carry = 0;
179 | this.setPFromFlags();
180 | };
181 |
182 | CPU.prototype.setUnusedFlag = function () {
183 | this.flags.unused = 1;
184 | this.setPFromFlags();
185 | };
186 |
187 | CPU.prototype.clearUnusedFlag = function () {
188 | this.flags.unused = 0;
189 | this.setPFromFlags();
190 | };
191 |
192 | CPU.prototype.checkZeroFlag = function (value) {
193 | if (value === 0) {
194 | this.setZeroFlag();
195 | } else {
196 | this.clearZeroFlag();
197 | }
198 | };
199 |
200 | CPU.prototype.setZeroFlag = function () {
201 | this.flags.zero = 1;
202 | this.setPFromFlags();
203 | };
204 |
205 | CPU.prototype.clearZeroFlag = function () {
206 | this.flags.zero = 0;
207 | this.setPFromFlags();
208 | };
209 |
210 | CPU.prototype.setInterruptDisableFlag = function () {
211 | this.flags.interruptDisable = 1;
212 | this.setPFromFlags();
213 | };
214 |
215 | CPU.prototype.clearInterruptDisableFlag = function () {
216 | this.flags.interruptDisable = 0;
217 | this.setPFromFlags();
218 | };
219 |
220 | CPU.prototype.setDecimalFlag = function () {
221 | this.flags.decimal = 1;
222 | this.setPFromFlags();
223 | };
224 |
225 | CPU.prototype.clearDecimalFlag = function () {
226 | this.flags.decimal = 0;
227 | this.setPFromFlags();
228 | };
229 |
230 | CPU.prototype.setBrkFlag = function () {
231 | this.flags.brk = 1;
232 | this.setPFromFlags();
233 | };
234 |
235 | CPU.prototype.clearBrkFlag = function () {
236 | this.flags.brk = 0;
237 | this.setPFromFlags();
238 | };
239 |
240 | CPU.prototype.checkOverflowFlag = function (a, b, total, adc) {
241 | /**
242 | * http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
243 | */
244 | if(adc) {
245 | if ((((a ^ b) & 0x80) === 0) && ((a ^ total) & 0x80) !== 0) {
246 | this.setOverflowFlag();
247 | } else {
248 | this.clearOverflowFlag();
249 | }
250 | }else{
251 | if (((a ^ b) & 0x80) !== 0 && ((a ^ total) & 0x80) !== 0) {
252 | this.setOverflowFlag();
253 | } else {
254 | this.clearOverflowFlag();
255 | }
256 | }
257 | };
258 |
259 | CPU.prototype.setOverflowFlag = function () {
260 | this.flags.overflow = 1;
261 | this.setPFromFlags();
262 | };
263 |
264 | CPU.prototype.clearOverflowFlag = function () {
265 | this.flags.overflow = 0;
266 | this.setPFromFlags();
267 | };
268 |
269 | CPU.prototype.checkNegativeFlag = function (value) {
270 | if (value & 0x80) {
271 | this.setNegativeFlag();
272 | } else {
273 | this.clearNegativeFlag();
274 | }
275 | };
276 |
277 | CPU.prototype.setNegativeFlag = function () {
278 | this.flags.negative = 1;
279 | this.setPFromFlags();
280 | };
281 |
282 | CPU.prototype.clearNegativeFlag = function () {
283 | this.flags.negative = 0;
284 | this.setPFromFlags();
285 | };
286 |
287 | /**
288 | * Enumeration of address modes
289 | */
290 | CPU.prototype.addressModes = {
291 | IMPLICIT: 0, //
292 | IMMEDIATE: 1, // #
293 | ZERO_PAGE: 2, // d
294 | ZERO_PAGE_X: 3, // d, X
295 | ZERO_PAGE_Y: 4, // d,Y
296 | INDEXED_INDIRECT: 5, // (d, X)
297 | INDIRECT_INDEXED: 6, // (d),Y
298 | ACCUMULATOR: 7, // A
299 | RELATIVE: 8, // r
300 | ABSOLUTE: 9, // a
301 | ABSOLUTE_X: 10, // a, X
302 | ABSOLUTE_Y: 11, // a, Y,
303 | INDIRECT_ABSOLUTE: 12 // ? - only used for JMP?
304 | };
305 |
306 | CPU.prototype.getOpcodeText = function (opcode) {
307 | for (var i in this.opcodes) {
308 | if (this.opcodes[i] == opcode) return i;
309 | }
310 | };
311 |
312 | CPU.prototype.getAddressModeText = function (address_mode) {
313 | for (var i in this.addressModes) {
314 | if (this.addressModes[i] == address_mode) return i;
315 | }
316 | };
317 |
318 | /**
319 | * List of opcodes and their operations
320 | */
321 | CPU.prototype.opcodes = {
322 | ORA: 0,
323 | AND: 1,
324 | EOR: 2,
325 | ADC: 3,
326 | STA: 4,
327 | LDA: 5,
328 | CMP: 6,
329 | SBC: 7,
330 | BIT: 8,
331 | JMP: 9,
332 | JMP_ABS: 10,
333 | STY: 11,
334 | LDY: 12,
335 | CPY: 13,
336 | CPX: 14,
337 | BRK: 15,
338 | ASL: 16,
339 | PHP: 17,
340 | BPL: 18,
341 | CLC: 19,
342 | JSR: 20,
343 | ROL: 21,
344 | PLP: 22,
345 | BMI: 23,
346 | LSR: 24,
347 | LDX: 25,
348 | ROR: 26,
349 | STX: 27,
350 | DEC: 28,
351 | INC: 29,
352 | SEC: 30,
353 | PHA: 31,
354 | CLI: 32,
355 | PLA: 33,
356 | SEI: 34,
357 | DEY: 35,
358 | TYA: 36,
359 | TAY: 37,
360 | CLV: 38,
361 | INY: 39,
362 | CLD: 40,
363 | INX: 41,
364 | SED: 42,
365 | RTI: 43,
366 | RTS: 44,
367 | BCS: 45,
368 | BNE: 46,
369 | BEQ: 47,
370 | BVC: 48,
371 | BVS: 49,
372 | BCC: 50,
373 | TXA: 51,
374 | TXS: 52,
375 | TAX: 53,
376 | TSX: 54,
377 | DEX: 55,
378 | NOP: 56
379 | };
380 |
381 | CPU.prototype.instruction_table = [];
382 | CPU.prototype.instruction_table[0x00] = [CPU.prototype.opcodes.BRK, CPU.prototype.addressModes.IMPLICIT, 7];
383 | CPU.prototype.instruction_table[0x01] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
384 | CPU.prototype.instruction_table[0x05] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.ZERO_PAGE, 3];
385 | CPU.prototype.instruction_table[0x06] = [CPU.prototype.opcodes.ASL, CPU.prototype.addressModes.ZERO_PAGE, 5];
386 | CPU.prototype.instruction_table[0x08] = [CPU.prototype.opcodes.PHP, CPU.prototype.addressModes.IMPLICIT, 3];
387 | CPU.prototype.instruction_table[0x09] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.IMMEDIATE, 2];
388 | CPU.prototype.instruction_table[0x0a] = [CPU.prototype.opcodes.ASL, CPU.prototype.addressModes.ACCUMULATOR, 2];
389 | CPU.prototype.instruction_table[0x0d] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.ABSOLUTE, 4];
390 | CPU.prototype.instruction_table[0x0e] = [CPU.prototype.opcodes.ASL, CPU.prototype.addressModes.ABSOLUTE, 6];
391 | CPU.prototype.instruction_table[0x10] = [CPU.prototype.opcodes.BPL, CPU.prototype.addressModes.RELATIVE, 2];
392 | CPU.prototype.instruction_table[0x11] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
393 | CPU.prototype.instruction_table[0x15] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
394 | CPU.prototype.instruction_table[0x16] = [CPU.prototype.opcodes.ASL, CPU.prototype.addressModes.ZERO_PAGE_X, 6];
395 | CPU.prototype.instruction_table[0x18] = [CPU.prototype.opcodes.CLC, CPU.prototype.addressModes.IMPLICIT, 2];
396 | CPU.prototype.instruction_table[0x19] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
397 | CPU.prototype.instruction_table[0x1d] = [CPU.prototype.opcodes.ORA, CPU.prototype.addressModes.ABSOLUTE_X, 4];
398 | CPU.prototype.instruction_table[0x1e] = [CPU.prototype.opcodes.ASL, CPU.prototype.addressModes.ABSOLUTE_X, 7];
399 | CPU.prototype.instruction_table[0x20] = [CPU.prototype.opcodes.JSR, CPU.prototype.addressModes.ABSOLUTE, 6];
400 | CPU.prototype.instruction_table[0x21] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
401 | CPU.prototype.instruction_table[0x24] = [CPU.prototype.opcodes.BIT, CPU.prototype.addressModes.ZERO_PAGE, 3];
402 | CPU.prototype.instruction_table[0x25] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.ZERO_PAGE, 3];
403 | CPU.prototype.instruction_table[0x26] = [CPU.prototype.opcodes.ROL, CPU.prototype.addressModes.ZERO_PAGE, 5];
404 | CPU.prototype.instruction_table[0x28] = [CPU.prototype.opcodes.PLP, CPU.prototype.addressModes.IMPLICIT, 4];
405 | CPU.prototype.instruction_table[0x29] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.IMMEDIATE, 2];
406 | CPU.prototype.instruction_table[0x2a] = [CPU.prototype.opcodes.ROL, CPU.prototype.addressModes.ACCUMULATOR, 2];
407 | CPU.prototype.instruction_table[0x2c] = [CPU.prototype.opcodes.BIT, CPU.prototype.addressModes.ABSOLUTE, 4];
408 | CPU.prototype.instruction_table[0x2d] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.ABSOLUTE, 4];
409 | CPU.prototype.instruction_table[0x2e] = [CPU.prototype.opcodes.ROL, CPU.prototype.addressModes.ABSOLUTE, 6];
410 | CPU.prototype.instruction_table[0x30] = [CPU.prototype.opcodes.BMI, CPU.prototype.addressModes.RELATIVE, 2];
411 | CPU.prototype.instruction_table[0x31] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
412 | CPU.prototype.instruction_table[0x35] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
413 | CPU.prototype.instruction_table[0x36] = [CPU.prototype.opcodes.ROL, CPU.prototype.addressModes.ZERO_PAGE_X, 6];
414 | CPU.prototype.instruction_table[0x38] = [CPU.prototype.opcodes.SEC, CPU.prototype.addressModes.IMPLICIT, 2];
415 | CPU.prototype.instruction_table[0x39] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
416 | CPU.prototype.instruction_table[0x3c] = [CPU.prototype.opcodes.BIT, CPU.prototype.addressModes.ABSOLUTE_X, 4];
417 | CPU.prototype.instruction_table[0x3d] = [CPU.prototype.opcodes.AND, CPU.prototype.addressModes.ABSOLUTE_X, 4];
418 | CPU.prototype.instruction_table[0x3e] = [CPU.prototype.opcodes.ROL, CPU.prototype.addressModes.ABSOLUTE_X, 7];
419 | CPU.prototype.instruction_table[0x40] = [CPU.prototype.opcodes.RTI, CPU.prototype.addressModes.IMPLICIT, 6];
420 | CPU.prototype.instruction_table[0x41] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
421 | CPU.prototype.instruction_table[0x45] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.ZERO_PAGE, 3];
422 | CPU.prototype.instruction_table[0x46] = [CPU.prototype.opcodes.LSR, CPU.prototype.addressModes.ZERO_PAGE, 5];
423 | CPU.prototype.instruction_table[0x48] = [CPU.prototype.opcodes.PHA, CPU.prototype.addressModes.IMPLICIT,3];
424 | CPU.prototype.instruction_table[0x49] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.IMMEDIATE, 2];
425 | CPU.prototype.instruction_table[0x4a] = [CPU.prototype.opcodes.LSR, CPU.prototype.addressModes.ACCUMULATOR, 2];
426 | CPU.prototype.instruction_table[0x4c] = [CPU.prototype.opcodes.JMP, CPU.prototype.addressModes.ABSOLUTE, 3];
427 | CPU.prototype.instruction_table[0x4d] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.ABSOLUTE, 4];
428 | CPU.prototype.instruction_table[0x4e] = [CPU.prototype.opcodes.LSR, CPU.prototype.addressModes.ABSOLUTE, 6];
429 | CPU.prototype.instruction_table[0x50] = [CPU.prototype.opcodes.BVC, CPU.prototype.addressModes.RELATIVE, 2];
430 | CPU.prototype.instruction_table[0x51] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
431 | CPU.prototype.instruction_table[0x55] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
432 | CPU.prototype.instruction_table[0x56] = [CPU.prototype.opcodes.LSR, CPU.prototype.addressModes.ZERO_PAGE_X, 6];
433 | CPU.prototype.instruction_table[0x58] = [CPU.prototype.opcodes.CLI, CPU.prototype.addressModes.IMPLICIT, 2];
434 | CPU.prototype.instruction_table[0x59] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
435 | CPU.prototype.instruction_table[0x5d] = [CPU.prototype.opcodes.EOR, CPU.prototype.addressModes.ABSOLUTE_X, 4];
436 | CPU.prototype.instruction_table[0x5e] = [CPU.prototype.opcodes.LSR, CPU.prototype.addressModes.ABSOLUTE_X, 7];
437 | CPU.prototype.instruction_table[0x60] = [CPU.prototype.opcodes.RTS, CPU.prototype.addressModes.IMPLICIT, 6];
438 | CPU.prototype.instruction_table[0x61] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
439 | CPU.prototype.instruction_table[0x65] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.ZERO_PAGE, 3];
440 | CPU.prototype.instruction_table[0x66] = [CPU.prototype.opcodes.ROR, CPU.prototype.addressModes.ZERO_PAGE, 5];
441 | CPU.prototype.instruction_table[0x68] = [CPU.prototype.opcodes.PLA, CPU.prototype.addressModes.IMPLICIT, 4];
442 | CPU.prototype.instruction_table[0x69] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.IMMEDIATE, 2];
443 | CPU.prototype.instruction_table[0x6a] = [CPU.prototype.opcodes.ROR, CPU.prototype.addressModes.ACCUMULATOR, 2];
444 | CPU.prototype.instruction_table[0x6c] = [CPU.prototype.opcodes.JMP, CPU.prototype.addressModes.ABSOLUTE, 5];
445 | CPU.prototype.instruction_table[0x6d] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.ABSOLUTE, 4];
446 | CPU.prototype.instruction_table[0x6e] = [CPU.prototype.opcodes.ROR, CPU.prototype.addressModes.ABSOLUTE, 6];
447 | CPU.prototype.instruction_table[0x70] = [CPU.prototype.opcodes.BVS, CPU.prototype.addressModes.RELATIVE, 2];
448 | CPU.prototype.instruction_table[0x71] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
449 | CPU.prototype.instruction_table[0x75] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
450 | CPU.prototype.instruction_table[0x76] = [CPU.prototype.opcodes.ROR, CPU.prototype.addressModes.ZERO_PAGE_X, 6];
451 | CPU.prototype.instruction_table[0x78] = [CPU.prototype.opcodes.SEI, CPU.prototype.addressModes.IMPLICIT, 2];
452 | CPU.prototype.instruction_table[0x79] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
453 | CPU.prototype.instruction_table[0x7d] = [CPU.prototype.opcodes.ADC, CPU.prototype.addressModes.ABSOLUTE_X, 4];
454 | CPU.prototype.instruction_table[0x7e] = [CPU.prototype.opcodes.ROR, CPU.prototype.addressModes.ABSOLUTE_X, 7];
455 | CPU.prototype.instruction_table[0x81] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
456 | CPU.prototype.instruction_table[0x84] = [CPU.prototype.opcodes.STY, CPU.prototype.addressModes.ZERO_PAGE, 3];
457 | CPU.prototype.instruction_table[0x85] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.ZERO_PAGE, 3];
458 | CPU.prototype.instruction_table[0x86] = [CPU.prototype.opcodes.STX, CPU.prototype.addressModes.ZERO_PAGE, 3];
459 | CPU.prototype.instruction_table[0x88] = [CPU.prototype.opcodes.DEY, CPU.prototype.addressModes.IMPLICIT, 2];
460 | CPU.prototype.instruction_table[0x8a] = [CPU.prototype.opcodes.TXA, CPU.prototype.addressModes.IMPLICIT, 2];
461 | CPU.prototype.instruction_table[0x8c] = [CPU.prototype.opcodes.STY, CPU.prototype.addressModes.ABSOLUTE, 4];
462 | CPU.prototype.instruction_table[0x8d] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.ABSOLUTE, 4];
463 | CPU.prototype.instruction_table[0x8e] = [CPU.prototype.opcodes.STX, CPU.prototype.addressModes.ABSOLUTE, 4];
464 | CPU.prototype.instruction_table[0x90] = [CPU.prototype.opcodes.BCC, CPU.prototype.addressModes.RELATIVE, 2];
465 | CPU.prototype.instruction_table[0x91] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.INDIRECT_INDEXED, 6];
466 | CPU.prototype.instruction_table[0x94] = [CPU.prototype.opcodes.STY, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
467 | CPU.prototype.instruction_table[0x95] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
468 | CPU.prototype.instruction_table[0x96] = [CPU.prototype.opcodes.STX, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
469 | CPU.prototype.instruction_table[0x98] = [CPU.prototype.opcodes.TYA, CPU.prototype.addressModes.IMPLICIT, 2];
470 | CPU.prototype.instruction_table[0x99] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.ABSOLUTE_Y, 5];
471 | CPU.prototype.instruction_table[0x9a] = [CPU.prototype.opcodes.TXS, CPU.prototype.addressModes.IMPLICIT, 2];
472 | CPU.prototype.instruction_table[0x9d] = [CPU.prototype.opcodes.STA, CPU.prototype.addressModes.ABSOLUTE_X, 5];
473 | CPU.prototype.instruction_table[0xa0] = [CPU.prototype.opcodes.LDY, CPU.prototype.addressModes.IMMEDIATE, 2];
474 | CPU.prototype.instruction_table[0xa1] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
475 | CPU.prototype.instruction_table[0xa2] = [CPU.prototype.opcodes.LDX, CPU.prototype.addressModes.IMMEDIATE, 2];
476 | CPU.prototype.instruction_table[0xa4] = [CPU.prototype.opcodes.LDY, CPU.prototype.addressModes.ZERO_PAGE, 3];
477 | CPU.prototype.instruction_table[0xa5] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.ZERO_PAGE, 3];
478 | CPU.prototype.instruction_table[0xa6] = [CPU.prototype.opcodes.LDX, CPU.prototype.addressModes.ZERO_PAGE, 3];
479 | CPU.prototype.instruction_table[0xa8] = [CPU.prototype.opcodes.TAY, CPU.prototype.addressModes.IMPLICIT, 2];
480 | CPU.prototype.instruction_table[0xa9] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.IMMEDIATE, 2];
481 | CPU.prototype.instruction_table[0xaa] = [CPU.prototype.opcodes.TAX, CPU.prototype.addressModes.IMPLICIT, 2];
482 | CPU.prototype.instruction_table[0xac] = [CPU.prototype.opcodes.LDY, CPU.prototype.addressModes.ABSOLUTE, 4];
483 | CPU.prototype.instruction_table[0xad] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.ABSOLUTE, 4];
484 | CPU.prototype.instruction_table[0xae] = [CPU.prototype.opcodes.LDX, CPU.prototype.addressModes.ABSOLUTE, 4];
485 | CPU.prototype.instruction_table[0xb0] = [CPU.prototype.opcodes.BCS, CPU.prototype.addressModes.RELATIVE, 2];
486 | CPU.prototype.instruction_table[0xb1] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
487 | CPU.prototype.instruction_table[0xb4] = [CPU.prototype.opcodes.LDY, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
488 | CPU.prototype.instruction_table[0xb5] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
489 | CPU.prototype.instruction_table[0xb6] = [CPU.prototype.opcodes.LDX, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
490 | CPU.prototype.instruction_table[0xb8] = [CPU.prototype.opcodes.CLV, CPU.prototype.addressModes.IMPLICIT, 2];
491 | CPU.prototype.instruction_table[0xb9] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
492 | CPU.prototype.instruction_table[0xba] = [CPU.prototype.opcodes.TSX, CPU.prototype.addressModes.IMPLICIT, 2];
493 | CPU.prototype.instruction_table[0xbc] = [CPU.prototype.opcodes.LDY, CPU.prototype.addressModes.ABSOLUTE_X, 4];
494 | CPU.prototype.instruction_table[0xbd] = [CPU.prototype.opcodes.LDA, CPU.prototype.addressModes.ABSOLUTE_X, 4];
495 | CPU.prototype.instruction_table[0xbe] = [CPU.prototype.opcodes.LDX, CPU.prototype.addressModes.ABSOLUTE_X, 4];
496 | CPU.prototype.instruction_table[0xc0] = [CPU.prototype.opcodes.CPY, CPU.prototype.addressModes.IMMEDIATE, 2];
497 | CPU.prototype.instruction_table[0xc1] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
498 | CPU.prototype.instruction_table[0xc4] = [CPU.prototype.opcodes.CPY, CPU.prototype.addressModes.ZERO_PAGE, 3];
499 | CPU.prototype.instruction_table[0xc5] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.ZERO_PAGE, 3];
500 | CPU.prototype.instruction_table[0xc6] = [CPU.prototype.opcodes.DEC, CPU.prototype.addressModes.ZERO_PAGE, 5];
501 | CPU.prototype.instruction_table[0xc8] = [CPU.prototype.opcodes.INY, CPU.prototype.addressModes.IMPLICIT, 2];
502 | CPU.prototype.instruction_table[0xc9] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.IMMEDIATE, 2];
503 | CPU.prototype.instruction_table[0xca] = [CPU.prototype.opcodes.DEX, CPU.prototype.addressModes.IMPLICIT, 2];
504 | CPU.prototype.instruction_table[0xcc] = [CPU.prototype.opcodes.CPY, CPU.prototype.addressModes.ABSOLUTE, 4];
505 | CPU.prototype.instruction_table[0xcd] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.ABSOLUTE, 4];
506 | CPU.prototype.instruction_table[0xce] = [CPU.prototype.opcodes.DEC, CPU.prototype.addressModes.ABSOLUTE, 6];
507 | CPU.prototype.instruction_table[0xd0] = [CPU.prototype.opcodes.BNE, CPU.prototype.addressModes.RELATIVE, 2];
508 | CPU.prototype.instruction_table[0xd1] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
509 | CPU.prototype.instruction_table[0xd5] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
510 | CPU.prototype.instruction_table[0xd6] = [CPU.prototype.opcodes.DEC, CPU.prototype.addressModes.ZERO_PAGE_X, 6];
511 | CPU.prototype.instruction_table[0xd8] = [CPU.prototype.opcodes.CLD, CPU.prototype.addressModes.IMPLICIT, 2];
512 | CPU.prototype.instruction_table[0xd9] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
513 | CPU.prototype.instruction_table[0xdd] = [CPU.prototype.opcodes.CMP, CPU.prototype.addressModes.ABSOLUTE_X, 4];
514 | CPU.prototype.instruction_table[0xde] = [CPU.prototype.opcodes.DEC, CPU.prototype.addressModes.ABSOLUTE_X, 7];
515 | CPU.prototype.instruction_table[0xe0] = [CPU.prototype.opcodes.CPX, CPU.prototype.addressModes.IMMEDIATE, 2];
516 | CPU.prototype.instruction_table[0xe1] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.INDEXED_INDIRECT, 6];
517 | CPU.prototype.instruction_table[0xe4] = [CPU.prototype.opcodes.CPX, CPU.prototype.addressModes.ZERO_PAGE, 3];
518 | CPU.prototype.instruction_table[0xe5] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.ZERO_PAGE, 3];
519 | CPU.prototype.instruction_table[0xe6] = [CPU.prototype.opcodes.INC, CPU.prototype.addressModes.ZERO_PAGE, 5];
520 | CPU.prototype.instruction_table[0xe8] = [CPU.prototype.opcodes.INX, CPU.prototype.addressModes.IMPLICIT, 2];
521 | CPU.prototype.instruction_table[0xe9] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.IMMEDIATE, 2];
522 | CPU.prototype.instruction_table[0xea] = [CPU.prototype.opcodes.NOP, CPU.prototype.addressModes.IMPLICIT, 2];
523 | CPU.prototype.instruction_table[0xec] = [CPU.prototype.opcodes.CPX, CPU.prototype.addressModes.ABSOLUTE, 4];
524 | CPU.prototype.instruction_table[0xed] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.ABSOLUTE, 4];
525 | CPU.prototype.instruction_table[0xee] = [CPU.prototype.opcodes.INC, CPU.prototype.addressModes.ABSOLUTE, 6];
526 | CPU.prototype.instruction_table[0xf0] = [CPU.prototype.opcodes.BEQ, CPU.prototype.addressModes.RELATIVE, 2];
527 | CPU.prototype.instruction_table[0xf1] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.INDIRECT_INDEXED, 5];
528 | CPU.prototype.instruction_table[0xf5] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.ZERO_PAGE_X, 4];
529 | CPU.prototype.instruction_table[0xf6] = [CPU.prototype.opcodes.INC, CPU.prototype.addressModes.ZERO_PAGE_X, 6];
530 | CPU.prototype.instruction_table[0xf8] = [CPU.prototype.opcodes.SED, CPU.prototype.addressModes.IMPLICIT, 2];
531 | CPU.prototype.instruction_table[0xf9] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.ABSOLUTE_Y, 4];
532 | CPU.prototype.instruction_table[0xfd] = [CPU.prototype.opcodes.SBC, CPU.prototype.addressModes.ABSOLUTE_X, 4];
533 | CPU.prototype.instruction_table[0xfe] = [CPU.prototype.opcodes.INC, CPU.prototype.addressModes.ABSOLUTE_X, 7];
534 |
535 | /**
536 | * Reads the appropriate memory location and returns the value, depending on the address mode
537 | * @param addressMode number The address mode of the current operation.
538 | * @returns object
539 | */
540 | CPU.prototype.readMemory = function (addressMode) {
541 |
542 | var address;
543 | var value;
544 |
545 | switch (addressMode) {
546 | case this.addressModes.IMMEDIATE:
547 | // 8 bit
548 | address = this.registers.PC++;
549 | value = this.mmc.fetch(address);
550 | break;
551 | case this.addressModes.ACCUMULATOR:
552 | address = null;
553 | value = this.registers.A;
554 | break;
555 | case this.addressModes.ZERO_PAGE:
556 | // 8 bit
557 | address = this.registers.PC++ & 0xff;
558 | value = this.mmc.fetch(address);
559 | break;
560 | case this.addressModes.ZERO_PAGE_X:
561 | // 8 bit
562 | address = ((this.registers.PC++ & 0xff) + this.registers.X) & 0xff;
563 | value = this.mmc.fetch(address);
564 | break;
565 | case this.addressModes.ZERO_PAGE_Y:
566 | // 8 bit
567 | address = ((this.registers.PC++ & 0xff) + this.registers.Y) & 0xff;
568 | value = this.mmc.fetch(address);
569 | break;
570 | case this.addressModes.ABSOLUTE:
571 | // 16 bit
572 | address = this.registers.PC;
573 | this.registers.PC += 2;
574 | address = this.mmc.fetch(address) | (this.mmc.fetch(address + 1) << 8);
575 | value = this.mmc.fetch(address);
576 | break;
577 | case this.addressModes.ABSOLUTE_X:
578 | //16 bit
579 | address = this.registers.PC;
580 | this.registers.PC += 2;
581 | address = (this.mmc.fetch(address) | (this.mmc.fetch(address + 1) << 8)) & 0xFFFF;
582 | if ((address & 0xFF00) != ((address + this.registers.X) & 0xFF00)) {
583 | this.memoryCycles += 1;
584 | }
585 | address = address + this.registers.X;
586 | value = this.mmc.fetch(address);
587 | break;
588 | case this.addressModes.ABSOLUTE_Y:
589 | //16 bit
590 | address = this.registers.PC;
591 | this.registers.PC += 2;
592 | address = (this.mmc.fetch(address) | (this.mmc.fetch(address + 1) << 8)) & 0xFFFF;
593 | if ((address & 0xFF00) != ((address + this.registers.Y) & 0xFF00)) {
594 | this.memoryCycles += 1;
595 | }
596 | address = address + this.registers.Y;
597 | value = this.mmc.fetch(address);
598 | break;
599 | case this.addressModes.RELATIVE:
600 |
601 | address = this.mmc.fetch(this.registers.PC++);
602 |
603 | if (address < 0x80) {
604 | address = this.registers.PC + address;
605 | } else {
606 | address = this.registers.PC - (address - 0x80);
607 | }
608 |
609 | value = this.mmc.fetch(address);
610 | break;
611 | case this.addressModes.INDEXED_INDIRECT:
612 |
613 | address = this.mmc.fetch(this.registers.PC++);
614 | address = (address + this.registers.X) & 0xff;
615 | address = this.mmc.fetch(address) | (this.mmc.fetch(address + 1) << 8);
616 | value = this.mmc.fetch(address);
617 | break;
618 | case this.addressModes.INDIRECT_INDEXED:
619 |
620 | address = this.mmc.fetch(this.registers.PC++);
621 | address = this.mmc.fetch(address) | (this.mmc.fetch(address + 1) << 8);
622 | address = (address + this.registers.Y) & 0xffff;
623 | value = this.mmc.fetch(address);
624 | break;
625 | case this.addressModes.IMPLICIT:
626 | throw new this.CPUError("Cannot read memory for an implicit addressing mode operation");
627 | default:
628 | throw new this.CPUError("Cannot read memory: Unsupported addressing mode");
629 | }
630 |
631 | return {
632 | address: address,
633 | value: value
634 | };
635 |
636 | };
637 |
638 | CPU.prototype.push = function (value) {
639 | // Stack lives at $0100 - $01ff. First value lives at 0x1ff
640 | this.mmc.store(this.registers.SP, value);
641 | this.registers.SP = (--this.registers.SP & 0xFF) + 0x100; // wrap around stack for mario hax
642 | };
643 |
644 | CPU.prototype.pop = function () {
645 | this.registers.SP = (++this.registers.SP & 0xFF) + 0x100; // wrap around stack
646 | return this.mmc.fetch(this.registers.SP);
647 | };
648 |
649 | CPU.prototype.peek = function () {
650 | return this.mmc.fetch(((this.registers.SP + 1) & 0xFF) + 0x100);
651 | };
652 |
653 | CPU.prototype.emulate = function () {
654 |
655 | this.memoryCycles = 0;
656 | this.extraCycles = 0;
657 |
658 | var opcode = this.mmc.fetch(this.registers.PC++);
659 |
660 | if (typeof this.instruction_table[opcode] === 'undefined') {
661 | throw new this.CPUError("Invalid opcode: 0x" + opcode.toString(16));
662 | }
663 |
664 | var instruction = this.instruction_table[opcode];
665 |
666 | this.operations[instruction[0]].apply(this, [instruction[1]]);
667 |
668 | return instruction[2] + this.memoryCycles + this.extraCycles;
669 | };
670 |
671 | // http://www.obelisk.demon.co.uk/6502/reference.html#LDA
672 | CPU.prototype.operations = [];
673 |
674 | CPU.prototype.operations[CPU.prototype.opcodes.ADC] = function (addressMode) {
675 |
676 | var mem = this.readMemory(addressMode).value;
677 |
678 | var tmp = this.registers.A + mem + this.flags.carry;
679 |
680 | this.checkOverflowFlag(this.registers.A, mem, tmp, true);
681 |
682 | this.registers.A = tmp & 0xff;
683 |
684 | this.checkCarryFlag(tmp);
685 | this.checkNegativeFlag(tmp);
686 | this.checkZeroFlag(this.registers.A);
687 | };
688 |
689 | CPU.prototype.operations[CPU.prototype.opcodes.AND] = function (addressMode) {
690 |
691 | this.registers.A = this.readMemory(addressMode).value & this.registers.A;
692 |
693 | this.checkNegativeFlag(this.registers.A);
694 | this.checkZeroFlag(this.registers.A);
695 | };
696 |
697 | CPU.prototype.operations[CPU.prototype.opcodes.ASL] = function (addressMode) {
698 |
699 | var tmp = 0;
700 |
701 | if (addressMode === this.addressModes.ACCUMULATOR) {
702 | tmp = this.registers.A << 1;
703 | this.registers.A = tmp & 0xff;
704 | } else {
705 | var mem = this.readMemory(addressMode);
706 | tmp = mem.value << 1;
707 | this.mmc.store(mem.address, tmp & 0xff);
708 | }
709 |
710 | this.checkCarryFlag(tmp);
711 | this.checkNegativeFlag(tmp);
712 | this.checkZeroFlag(this.registers.A);
713 |
714 | };
715 |
716 | CPU.prototype.operations[CPU.prototype.opcodes.BCC] = function (addressMode) {
717 |
718 | if (this.flags.carry === 0) {
719 | this.extraCycles++;
720 | var old_pc = this.registers.PC;
721 | var inc = this.readMemory(addressMode).value;
722 |
723 | this.registers.PC += inc;
724 |
725 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
726 | this.extraCycles++;
727 | }
728 | }
729 | };
730 |
731 | CPU.prototype.operations[CPU.prototype.opcodes.BCS] = function (addressMode) {
732 |
733 | if (this.flags.carry === 1) {
734 | this.extraCycles++;
735 | var old_pc = this.registers.PC;
736 | var inc = this.readMemory(addressMode).value;
737 |
738 | this.registers.PC += inc;
739 |
740 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
741 | this.extraCycles++;
742 | }
743 | }
744 | };
745 |
746 | CPU.prototype.operations[CPU.prototype.opcodes.BEQ] = function (addressMode) {
747 |
748 | if (this.flags.zero === 1) {
749 | this.extraCycles++;
750 | var old_pc = this.registers.PC;
751 | var inc = this.readMemory(addressMode).value;
752 |
753 | this.registers.PC += inc;
754 |
755 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
756 | this.extraCycles++;
757 | }
758 | }
759 | };
760 |
761 | CPU.prototype.operations[CPU.prototype.opcodes.BIT] = function (addressMode) {
762 |
763 | var mem = this.readMemory(addressMode);
764 |
765 | this.checkNegativeFlag(mem.value);
766 |
767 | if ((mem.value >> 6) & 1) {
768 | this.setOverflowFlag();
769 | } else {
770 | this.clearOverflowFlag();
771 | }
772 |
773 | this.checkZeroFlag(this.registers.A & mem.value);
774 | };
775 |
776 | CPU.prototype.operations[CPU.prototype.opcodes.BMI] = function (addressMode) {
777 |
778 | if (this.flags.negative === 1) {
779 | this.extraCycles++;
780 | var old_pc = this.registers.PC;
781 | var inc = this.readMemory(addressMode).value;
782 |
783 | this.registers.PC += inc;
784 |
785 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
786 | this.extraCycles++;
787 | }
788 | }
789 | };
790 |
791 | CPU.prototype.operations[CPU.prototype.opcodes.BNE] = function (addressMode) {
792 |
793 | if (this.flags.zero === 0) {
794 | this.extraCycles++;
795 | var old_pc = this.registers.PC;
796 | var inc = this.readMemory(addressMode).value;
797 |
798 | this.registers.PC += inc;
799 |
800 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
801 | this.extraCycles++;
802 | }
803 | }
804 | };
805 |
806 | CPU.prototype.operations[CPU.prototype.opcodes.BPL] = function (addressMode) {
807 |
808 | if (this.flags.negative === 0) {
809 | this.extraCycles++;
810 | var old_pc = this.registers.PC;
811 | var inc = this.readMemory(addressMode).value;
812 |
813 | this.registers.PC += inc;
814 |
815 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
816 | this.extraCycles++;
817 | }
818 | }
819 | };
820 |
821 | /**
822 | * The BRK instruction forces the generation of an interrupt request.
823 | * The program counter and processor status are pushed on the stack then the IRQ interrupt vector at $FFFE/F is
824 | * loaded into the PC and the break flag in the status set to one.
825 | *
826 | * @param addressMode Memory addressing mode
827 | */
828 | CPU.prototype.operations[CPU.prototype.opcodes.BRK] = function (addressMode) {
829 |
830 | this.registers.PC++;
831 |
832 | this.push((this.registers.PC >> 8) & 0xff);
833 | this.push(this.registers.PC & 0xff);
834 |
835 | this.push(this.registers.P);
836 |
837 | this.setBrkFlag();
838 |
839 | this.registers.PC = this.mmc.fetch(0xFFFE) | (this.mmc.fetch(0xFFFF) << 8);
840 | };
841 |
842 | CPU.prototype.operations[CPU.prototype.opcodes.BVC] = function (addressMode) {
843 |
844 | if (this.flags.overflow === 0) {
845 | this.extraCycles++;
846 | var old_pc = this.registers.PC;
847 | var inc = this.readMemory(addressMode).value;
848 |
849 | this.registers.PC += inc;
850 |
851 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
852 | this.extraCycles++;
853 | }
854 | }
855 | };
856 |
857 | CPU.prototype.operations[CPU.prototype.opcodes.BVS] = function (addressMode) {
858 |
859 | if (this.flags.overflow === 1) {
860 | this.extraCycles++;
861 | var old_pc = this.registers.PC;
862 | var inc = this.readMemory(addressMode).value;
863 |
864 | this.registers.PC += inc;
865 |
866 | if ((this.registers.PC & 0xFF00) !== (old_pc & 0xFF00)) {
867 | this.extraCycles++;
868 | }
869 | }
870 | };
871 |
872 | CPU.prototype.operations[CPU.prototype.opcodes.CLC] = function (addressMode) {
873 | this.clearCarryFlag();
874 | };
875 |
876 | CPU.prototype.operations[CPU.prototype.opcodes.CLD] = function (addressMode) {
877 | this.clearDecimalFlag();
878 | };
879 |
880 | CPU.prototype.operations[CPU.prototype.opcodes.CLI] = function (addressMode) {
881 | this.clearInterruptDisableFlag();
882 | };
883 |
884 | CPU.prototype.operations[CPU.prototype.opcodes.CLV] = function (addressMode) {
885 | this.clearOverflowFlag();
886 | };
887 |
888 | CPU.prototype.operations[CPU.prototype.opcodes.CMP] = function (addressMode) {
889 |
890 | var val = this.registers.A - this.readMemory(addressMode).value;
891 |
892 | if (val >= 0) {
893 | this.setCarryFlag();
894 | } else {
895 | this.clearCarryFlag();
896 | }
897 |
898 | this.checkZeroFlag(val);
899 | this.checkNegativeFlag(val);
900 | };
901 |
902 | CPU.prototype.operations[CPU.prototype.opcodes.CPX] = function (addressMode) {
903 |
904 | var val = this.registers.X - this.readMemory(addressMode).value;
905 |
906 | if (val >= 0) {
907 | this.setCarryFlag();
908 | } else {
909 | this.clearCarryFlag();
910 | }
911 |
912 | this.checkZeroFlag(val);
913 | this.checkNegativeFlag(val);
914 | };
915 |
916 | CPU.prototype.operations[CPU.prototype.opcodes.CPY] = function (addressMode) {
917 |
918 | var val = this.registers.Y - this.readMemory(addressMode).value;
919 |
920 | if (val >= 0) {
921 | this.setCarryFlag();
922 | } else {
923 | this.clearCarryFlag();
924 | }
925 |
926 | this.checkZeroFlag(val);
927 | this.checkNegativeFlag(val);
928 | };
929 |
930 | CPU.prototype.operations[CPU.prototype.opcodes.DEC] = function (addressMode) {
931 |
932 | var m = this.readMemory(addressMode);
933 |
934 | var r = (m.value - 1) & 0xff;
935 |
936 | this.checkZeroFlag(r);
937 | this.checkNegativeFlag(r);
938 |
939 | this.mmc.store(m.address, r);
940 | };
941 |
942 | CPU.prototype.operations[CPU.prototype.opcodes.INC] = function (addressMode) {
943 |
944 | var m = this.readMemory(addressMode);
945 |
946 | var r = (m.value + 1) & 0xff;
947 |
948 | this.checkZeroFlag(r);
949 | this.checkNegativeFlag(r);
950 |
951 | this.mmc.store(m.address, r);
952 | };
953 |
954 | CPU.prototype.operations[CPU.prototype.opcodes.DEX] = function (addressMode) {
955 |
956 | this.registers.X = (this.registers.X - 1) & 0xff;
957 |
958 | this.checkZeroFlag(this.registers.X);
959 | this.checkNegativeFlag(this.registers.X);
960 | };
961 |
962 | CPU.prototype.operations[CPU.prototype.opcodes.DEY] = function (addressMode) {
963 |
964 | this.registers.Y = (this.registers.Y - 1) & 0xff;
965 |
966 | this.checkZeroFlag(this.registers.Y);
967 | this.checkNegativeFlag(this.registers.Y);
968 | };
969 |
970 | CPU.prototype.operations[CPU.prototype.opcodes.EOR] = function (addressMode) {
971 |
972 | this.registers.A = this.registers.A ^ this.readMemory(addressMode).value;
973 |
974 | this.checkZeroFlag(this.registers.A);
975 | this.checkNegativeFlag(this.registers.A);
976 | };
977 |
978 | CPU.prototype.operations[CPU.prototype.opcodes.INX] = function (addressMode) {
979 |
980 | this.registers.X = (this.registers.X + 1) & 0xff;
981 |
982 | this.checkZeroFlag(this.registers.X);
983 | this.checkNegativeFlag(this.registers.X);
984 | };
985 |
986 | CPU.prototype.operations[CPU.prototype.opcodes.INY] = function (addressMode) {
987 |
988 | this.registers.Y = (this.registers.Y + 1) & 0xff;
989 |
990 | this.checkZeroFlag(this.registers.Y);
991 | this.checkNegativeFlag(this.registers.Y);
992 | };
993 |
994 | CPU.prototype.operations[CPU.prototype.opcodes.JMP] = function (addressMode) {
995 | this.registers.PC = this.readMemory(addressMode).address;
996 | };
997 |
998 | CPU.prototype.operations[CPU.prototype.opcodes.JSR] = function (addressMode) {
999 | var mem = this.readMemory(addressMode);
1000 | this.push( (this.registers.PC >> 8) & 0xff );
1001 | this.push( this.registers.PC & 0xff );
1002 | this.registers.PC = mem.address;
1003 | };
1004 |
1005 | CPU.prototype.operations[CPU.prototype.opcodes.LDA] = function (addressMode) {
1006 |
1007 | var value = this.readMemory(addressMode).value;
1008 | this.registers.A = value;
1009 |
1010 | this.checkZeroFlag(this.registers.A);
1011 | this.checkNegativeFlag(this.registers.A);
1012 |
1013 | };
1014 |
1015 | CPU.prototype.operations[CPU.prototype.opcodes.LDX] = function (addressMode) {
1016 |
1017 | var value = this.readMemory(addressMode).value;
1018 | this.registers.X = value;
1019 |
1020 | this.checkZeroFlag(this.registers.X);
1021 | this.checkNegativeFlag(this.registers.X);
1022 |
1023 | };
1024 |
1025 | CPU.prototype.operations[CPU.prototype.opcodes.LDY] = function (addressMode) {
1026 |
1027 | var value = this.readMemory(addressMode).value;
1028 | this.registers.Y = value;
1029 |
1030 | this.checkZeroFlag(this.registers.Y);
1031 | this.checkNegativeFlag(this.registers.Y);
1032 |
1033 | };
1034 |
1035 | CPU.prototype.operations[CPU.prototype.opcodes.LSR] = function (addressMode) {
1036 |
1037 | if(addressMode === this.addressModes.ACCUMULATOR){
1038 | this.checkCarryFlag(0xff + (this.registers.A & 0x1));
1039 | this.registers.A = this.registers.A >> 1;
1040 | this.checkNegativeFlag(this.registers.A);
1041 | this.checkZeroFlag(this.registers.A);
1042 | }else{
1043 | var mem = this.readMemory(addressMode);
1044 | this.checkCarryFlag(0xff + (mem.value & 0x1));
1045 | mem.value = mem.value >> 1;
1046 | this.checkNegativeFlag(mem.value);
1047 | this.checkZeroFlag(mem.value);
1048 | this.mmc.store(mem.address, mem.value);
1049 | }
1050 |
1051 | };
1052 |
1053 | CPU.prototype.operations[CPU.prototype.opcodes.NOP] = function (addressMode) {
1054 | //nothing!
1055 | };
1056 |
1057 | CPU.prototype.operations[CPU.prototype.opcodes.ORA] = function (addressMode) {
1058 |
1059 | this.registers.A = this.registers.A | this.readMemory(addressMode).value;
1060 |
1061 | this.checkZeroFlag(this.registers.A);
1062 | this.checkNegativeFlag(this.registers.A);
1063 | };
1064 |
1065 | CPU.prototype.operations[CPU.prototype.opcodes.PHA] = function (addressMode) {
1066 | this.push(this.registers.A);
1067 | };
1068 |
1069 | CPU.prototype.operations[CPU.prototype.opcodes.PHP] = function (addressMode) {
1070 | this.push(this.registers.P);
1071 | };
1072 |
1073 | CPU.prototype.operations[CPU.prototype.opcodes.PLA] = function (addressMode) {
1074 | this.registers.A = this.pop();
1075 | this.checkZeroFlag(this.registers.A);
1076 | this.checkNegativeFlag(this.registers.A);
1077 | };
1078 |
1079 | CPU.prototype.operations[CPU.prototype.opcodes.PLP] = function (addressMode) {
1080 | this.registers.P = this.pop();
1081 | this.setFlagsFromP();
1082 | };
1083 |
1084 | CPU.prototype.operations[CPU.prototype.opcodes.ROL] = function (addressMode) {
1085 |
1086 | var carry = this.flags.carry;
1087 |
1088 | if(addressMode === this.addressModes.ACCUMULATOR){
1089 | this.checkCarryFlag(0xff + (this.registers.A & 0x80));
1090 | this.registers.A = ((this.registers.A << 1) & 0xff) | carry;
1091 | this.checkNegativeFlag(this.registers.A);
1092 | this.checkZeroFlag(this.registers.A);
1093 | }else{
1094 | var mem = this.readMemory(addressMode);
1095 | this.checkCarryFlag(0xff + (mem.value & 0x80));
1096 | mem.value = ((mem.value << 1) & 0xff) | carry;
1097 | this.checkNegativeFlag(mem.value);
1098 | this.checkZeroFlag(mem.value);
1099 | this.mmc.store(mem.address, mem.value);
1100 | }
1101 | };
1102 |
1103 | CPU.prototype.operations[CPU.prototype.opcodes.ROR] = function (addressMode) {
1104 |
1105 | var carry = this.flags.carry;
1106 |
1107 | if(addressMode === this.addressModes.ACCUMULATOR){
1108 | this.checkCarryFlag(0xff + (this.registers.A & 0x1));
1109 | this.registers.A = ((this.registers.A >> 1) & 0xff) | (carry << 7);
1110 | this.checkNegativeFlag(this.registers.A);
1111 | this.checkZeroFlag(this.registers.A);
1112 | }else{
1113 | var mem = this.readMemory(addressMode);
1114 | this.checkCarryFlag(0xff + (mem.value & 0x1));
1115 | mem.value = ((mem.value >> 1) & 0xff) | (carry << 7);
1116 | this.checkNegativeFlag(mem.value);
1117 | this.checkZeroFlag(mem.value);
1118 | this.mmc.store(mem.address, mem.value);
1119 | }
1120 | };
1121 |
1122 | CPU.prototype.operations[CPU.prototype.opcodes.RTI] = function (addressMode) {
1123 |
1124 | this.registers.P = this.pop();
1125 | this.setFlagsFromP();
1126 | var hiPC = this.pop();
1127 | var loPC = this.pop();
1128 | this.registers.PC = (hiPC << 8) | loPC;
1129 | };
1130 |
1131 | CPU.prototype.operations[CPU.prototype.opcodes.RTS] = function (addressMode) {
1132 | var hiPC = this.pop();
1133 | var loPC = this.pop();
1134 | this.registers.PC = (hiPC << 8) | loPC;
1135 | };
1136 |
1137 | CPU.prototype.operations[CPU.prototype.opcodes.SBC] = function (addressMode) {
1138 |
1139 | var mem = this.readMemory(addressMode).value;
1140 |
1141 | var tmp = this.registers.A - mem - (1 - this.flags.carry);
1142 |
1143 | this.checkOverflowFlag(this.registers.A, mem, tmp, false);
1144 |
1145 | this.registers.A = tmp & 0xff;
1146 |
1147 | this.checkCarryFlag(tmp);
1148 | this.checkNegativeFlag(tmp);
1149 | this.checkZeroFlag(this.registers.A);
1150 | };
1151 |
1152 | CPU.prototype.operations[CPU.prototype.opcodes.SEC] = function (addressMode) {
1153 | this.setCarryFlag();
1154 | };
1155 |
1156 | CPU.prototype.operations[CPU.prototype.opcodes.SED] = function (addressMode) {
1157 | this.setDecimalFlag();
1158 | };
1159 |
1160 | CPU.prototype.operations[CPU.prototype.opcodes.SEI] = function (addressMode) {
1161 | this.setInterruptDisableFlag();
1162 | };
1163 |
1164 | CPU.prototype.operations[CPU.prototype.opcodes.STA] = function (addressMode) {
1165 | var mem = this.readMemory(addressMode);
1166 | this.mmc.store(mem.address, this.registers.A);
1167 | };
1168 |
1169 | CPU.prototype.operations[CPU.prototype.opcodes.STX] = function (addressMode) {
1170 | var mem = this.readMemory(addressMode);
1171 | this.mmc.store(mem.address, this.registers.X);
1172 | };
1173 |
1174 | CPU.prototype.operations[CPU.prototype.opcodes.STY] = function (addressMode) {
1175 | var mem = this.readMemory(addressMode);
1176 | this.mmc.store(mem.address, this.registers.Y);
1177 | };
1178 |
1179 | CPU.prototype.operations[CPU.prototype.opcodes.TAX] = function (addressMode) {
1180 | this.registers.X = this.registers.A;
1181 | this.checkZeroFlag(this.registers.X);
1182 | this.checkNegativeFlag(this.registers.X);
1183 | };
1184 |
1185 | CPU.prototype.operations[CPU.prototype.opcodes.TAY] = function (addressMode) {
1186 | this.registers.Y = this.registers.A;
1187 | this.checkZeroFlag(this.registers.Y);
1188 | this.checkNegativeFlag(this.registers.Y);
1189 | };
1190 |
1191 | CPU.prototype.operations[CPU.prototype.opcodes.TSX] = function (addressMode) {
1192 | this.registers.X = this.registers.SP;
1193 | this.checkZeroFlag(this.registers.X);
1194 | this.checkNegativeFlag(this.registers.X);
1195 | };
1196 |
1197 | CPU.prototype.operations[CPU.prototype.opcodes.TXA] = function (addressMode) {
1198 | this.registers.A = this.registers.X;
1199 | this.checkZeroFlag(this.registers.A);
1200 | this.checkNegativeFlag(this.registers.A);
1201 | };
1202 |
1203 | CPU.prototype.operations[CPU.prototype.opcodes.TXS] = function (addressMode) {
1204 | this.registers.SP = this.registers.X;
1205 | };
1206 |
1207 | CPU.prototype.operations[CPU.prototype.opcodes.TYA] = function (addressMode) {
1208 | this.registers.A = this.registers.Y;
1209 | this.checkZeroFlag(this.registers.A);
1210 | this.checkNegativeFlag(this.registers.A);
1211 | };
1212 |
1213 | w.JNE.CPU = CPU;
1214 |
1215 | })(window);
1216 |
--------------------------------------------------------------------------------
/src/mmc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A basic emulation of the MMC and associated memory.
3 | *
4 | * $0000 - $00ff - Used by zero page addressing instructions. Instructions using zero page addressing only require an 8-bit address parameter. The most-signficant 8-bits of the address are assumed to be $00. This is done to save memory since the address requires half the space.
5 | * $0100 - $01ff - Reserved for the system stack.
6 | * $0200 - $fff9 - Unspecified
7 | * $fffa - $fffb - Contains address of non-maskable interrupt (NMI) handler
8 | * $fffc - $fffd - Contains address of reset location
9 | * $fffe - $ffff - Contains address of BRK/IRQ handler
10 | *
11 | */
12 |
13 | (function () {
14 | "use strict";
15 |
16 | window.JNE = window.JNE || {};
17 |
18 | /**
19 | * @constructor
20 | */
21 | window.JNE.MMC = function () {
22 | this.debug = false;
23 | this.size = 0x10000; // default to 65536 bytes
24 | this.memory = new Array(this.size);
25 | this.reset();
26 | };
27 |
28 | /**
29 | * Validate an address in memory. Throws an exception if address is invalid.
30 | * @param address Address in memory to test.
31 | */
32 | window.JNE.MMC.prototype.validateAddress = function (address) {
33 | if (typeof address !== 'number' || address >= this.size || address < 0) {
34 | throw "Invalid memory address: " + address;
35 | }
36 | };
37 |
38 | /**
39 | * Fetch the value at a given address in memory.
40 | * @param address number Address in memory to retrieve value from.
41 | * @returns number
42 | */
43 | window.JNE.MMC.prototype.fetch = function (address) {
44 | this.validateAddress(address);
45 | //if(this.debug) console.log('Reading address 0x' + address.toString(16) + ' - value is 0x' + this.memory[address].toString(16));
46 | return this.memory[this.translateAddress(address)];
47 | };
48 |
49 | /**
50 | * Store a given value at the given address in memory
51 | * @param address number Address in memory to store value at.
52 | * @param value number The value to store.
53 | */
54 | window.JNE.MMC.prototype.store = function (address, value) {
55 | this.validateAddress(address);
56 | //if(this.debug) console.log('Writing value ' + value.toString(16) + ' to address 0x' + address.toString(16));
57 | this.memory[this.translateAddress(address)] = value & 255;
58 | };
59 |
60 | /**
61 | * Reset the memory to all zeroes.
62 | */
63 | window.JNE.MMC.prototype.reset = function () {
64 | var freshMemory = new Array(this.memory.length);
65 | freshMemory.fill(0);
66 | this.memory = freshMemory;
67 | };
68 |
69 | /**
70 | * Translate an address - used for "mirrored" addresses
71 | * @param address
72 | * @returns int
73 | */
74 | window.JNE.MMC.prototype.translateAddress = function (address) {
75 | switch (true) {
76 | case address >= 0x2008 && address <= 0x3FFF:
77 | address = ((address - 0x2000) % 8) + 0x2000;
78 | break;
79 | }
80 | return address;
81 | };
82 |
83 | })();
--------------------------------------------------------------------------------
/src/nes.js:
--------------------------------------------------------------------------------
1 | (function (w) {
2 | "use strict";
3 |
4 | w.JNE = w.JNE || {};
5 |
6 | var NES = function () {
7 | this.mmc = new JNE.MMC();
8 | this.cpu = new JNE.CPU(this.mmc);
9 | this.ppu = new JNE.PPU(this.mmc);
10 | this.clock = new JNE.Clock();
11 | this.locked = false;
12 | this.clock.onTick(this.tick.bind(this));
13 | this.clock.setTickMultiplier(10000); // ticks happen 10,000 times less often than a "real" clock
14 | this.availableCycles = 0;
15 | };
16 |
17 | NES.prototype.loadROM = function () {
18 |
19 | };
20 |
21 | // @todo READ THIS https://wiki.nesdev.com/w/index.php/PPU_rendering
22 |
23 | NES.prototype.tick = function (cycles) {
24 |
25 | this.availableCycles += cycles;
26 |
27 | if (this.isLocked()) {
28 | return;
29 | }
30 |
31 | this.lock();
32 |
33 | var cpuCycles = 0;
34 | var i;
35 |
36 | while (this.availableCycles >= 0) {
37 |
38 | try {
39 | cpuCycles = this.cpu.emulate();
40 | } catch (e) {
41 | this.stop();
42 | throw e;
43 | }
44 |
45 | // The NTSC PPU runs at 3 times the CPU clock rate
46 | for (i = 0; i < cpuCycles * 3; i++) {
47 |
48 | // PPU operations
49 | this.ppu.emulate();
50 |
51 | }
52 |
53 | this.availableCycles -= cpuCycles;
54 | }
55 |
56 | this.unlock();
57 | };
58 |
59 | NES.prototype.start = function () {
60 | this.unlock();
61 | this.clock.start();
62 | };
63 |
64 | NES.prototype.stop = function () {
65 | this.clock.stop();
66 | this.unlock();
67 | };
68 |
69 | NES.prototype.lock = function () {
70 | this.locked = true;
71 | };
72 |
73 | NES.prototype.unlock = function () {
74 | this.locked = false;
75 | };
76 |
77 | NES.prototype.isLocked = function () {
78 | return this.locked;
79 | };
80 |
81 | NES.prototype.reset = function () {
82 | this.clock.stop();
83 | this.cpu.reset();
84 | };
85 |
86 | NES.prototype.NESError = function (message) {
87 | this.name = 'NESError';
88 | this.message = message;
89 | this.stack = (new Error()).stack;
90 | };
91 | NES.prototype.NESError.prototype = new Error();
92 | NES.prototype.NESError.prototype.constructor = NES.prototype.NESError;
93 |
94 | w.JNE.NES = NES;
95 |
96 | })(window);
97 |
--------------------------------------------------------------------------------
/src/ppu.js:
--------------------------------------------------------------------------------
1 | (function (w) {
2 | "use strict";
3 |
4 | w.JNE = w.JNE || {};
5 |
6 | /**
7 | *
8 | * @param w.JNE.MMC
9 | * @constructor
10 | */
11 | var PPU = function (mmc) {
12 | this.mmc = mmc;
13 | // also 8kb of memory on the gamepak
14 | this.memorySize = 0x800;
15 | // @todo replace this with a mmc style memory object with mirroring support
16 | this.memory = new Array(this.memorySize); // https://wiki.nesdev.com/w/index.php/PPU_memory_map
17 | this.palette = [];
18 | this.oam = [];
19 | this.reset();
20 | };
21 |
22 | PPU.prototype.registerLocation = {
23 | PPUCTRL: 0x2000,
24 | PPUMASK: 0x2001,
25 | PPUSTATUS: 0x2002,
26 | OAMADDR: 0x2003,
27 | OAMDATA: 0x2004,
28 | PPUSCROLL: 0x2005,
29 | PPUADDR: 0x2006,
30 | PPUDATA: 0x2007,
31 | OAMDMA: 0x4014
32 | };
33 |
34 | PPU.prototype.mirroringType = {
35 | VERTICAL: 0,
36 | HORIZONTAL: 1,
37 | FOUR_SCREEN: 2,
38 | SINGLE_SCREEN_1: 3,
39 | SINGLE_SCREEN_3: 4
40 | };
41 |
42 | /**
43 |
44 |
45 | PPUCTRL
46 |
47 | 7 bit 0
48 | ---- ----
49 | VPHB SINN
50 | |||| ||||
51 | |||| ||++- Base nametable address
52 | |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
53 | |||| |+--- VRAM address increment per CPU read/write of PPUDATA
54 | |||| | (0: add 1, going across; 1: add 32, going down)
55 | |||| +---- Sprite pattern table address for 8x8 sprites
56 | |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
57 | |||+------ Background pattern table address (0: $0000; 1: $1000)
58 | ||+------- Sprite size (0: 8x8; 1: 8x16)
59 | |+-------- PPU master/slave select
60 | | (0: read backdrop from EXT pins; 1: output color on EXT pins)
61 | +--------- Generate an NMI at the start of the vertical blanking interval (0: off; 1: on)
62 |
63 |
64 | Status
65 |
66 | 7 bit 0
67 | ---- ----
68 | VSO. ....
69 | |||| ||||
70 | |||+-++++- Least significant bits previously written into a PPU register
71 | ||| (due to register not being updated for this address)
72 | ||+------- Sprite overflow. The intent was for this flag to be set
73 | || whenever more than eight sprites appear on a scanline, but a
74 | || hardware bug causes the actual behavior to be more complicated
75 | || and generate false positives as well as false negatives; see
76 | || PPU sprite evaluation. This flag is set during sprite
77 | || evaluation and cleared at dot 1 (the second dot) of the
78 | || pre-render line.
79 | |+-------- Sprite 0 Hit. Set when a nonzero pixel of sprite 0 overlaps
80 | | a nonzero background pixel; cleared at dot 1 of the pre-render
81 | | line. Used for raster timing.
82 | +--------- Vertical blank has started (0: not in vblank; 1: in vblank).
83 | Set at dot 1 of line 241 (the line *after* the post-render
84 | line); cleared after reading $2002 and at dot 1 of the
85 | pre-render line.
86 |
87 | */
88 |
89 |
90 | PPU.prototype.PPUError = function (message) {
91 | this.name = 'PPUError';
92 | this.message = message;
93 | this.stack = (new Error()).stack;
94 | };
95 | PPU.prototype.PPUError.prototype = new Error();
96 | PPU.prototype.PPUError.prototype.constructor = PPU.prototype.PPUError;
97 |
98 | PPU.prototype.setRegister = function (registerName, value) {
99 | if (!this.registerLocations.hasOwnProperty(registerName)) {
100 | throw new this.PPUError("Unknown register: " + registerName);
101 | }
102 | this.mmc.store(this.registerLocations[registerName], value);
103 | };
104 |
105 | PPU.prototype.reset = function () {
106 | this.oam = new Array(256); // Object Attribute Memory a.k.a. sprite mem
107 | this.oam.fill(0);
108 |
109 | var width = 256, height = 240;
110 | this.canvas = document.createElement('canvas');
111 | this.canvas.width = width;
112 | this.canvas.height = height;
113 | this.canvasContext = this.canvas.getContext('2d');
114 | this.frameBuffer = this.canvasContext.createImageData(width, height);
115 |
116 | this.latch = 0;
117 | this.horizontalPos = 0;
118 | this.scanline = 0;
119 | this.nmiCounter = 0;
120 | this.frameComplete = false;
121 | };
122 |
123 | PPU.prototype.bufferScanline = function () {
124 |
125 |
126 | this.scanline++;
127 |
128 | if (this.scanline > 261) {
129 | this.scanline = 0;
130 | this.nmiCounter = 9;
131 | this.frameComplete = true;
132 | }
133 | };
134 |
135 | PPU.prototype.writePixelToBuffer = function (pixel, colour) {
136 |
137 | // 0x40 0x30 0x20 0x10
138 | // AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR
139 | // Alpha Blue Green Red
140 |
141 | this.frameBuffer.data[pixel * 4] = colour & 0xFF;
142 | this.frameBuffer.data[pixel * 4 + 1] = (colour >> 8) & 0xFF;
143 | this.frameBuffer.data[pixel * 4 + 2] = (colour >> 16) & 0xFF;
144 | this.frameBuffer.data[pixel * 4 + 3] = 255; // (colour >> 24) & 0xFF; // alpha not used
145 | };
146 |
147 | PPU.prototype.checkSprite0Hit = function () {
148 | //Check for a sprite 0 hit.
149 | if (false) { //this.ppu.curX === this.ppu.spr0HitX && this.ppu.f_spVisibility === 1 && this.ppu.scanline-21 === this.ppu.spr0HitY){
150 | //Set the sprite 0 hit flag.
151 | var status = this.mmc.fetch(this.registerLocation.PPUSTATUS);
152 | status |= 64; // 01000000 - bit 6 of PPUSTATUS = sprite hit flag
153 | this.mmc.store(this.registerLocation.PPUSTATUS, status);
154 | }
155 | };
156 |
157 | PPU.prototype.drawFrame = function () {
158 |
159 | };
160 |
161 | PPU.prototype.emulate = function () {
162 |
163 | //Check if the ppu is done rendering.
164 | if (this.frameComplete) {
165 |
166 | //Decrement the non-maskable interrupt counter.
167 | this.nmiCounter--;
168 |
169 | //???
170 | if (this.nmiCounter === 0) {
171 | this.frameComplete = false;
172 | //Draw the frame and start the vBlank period.
173 | this.drawFrame();
174 | }
175 | }
176 |
177 | // move across 1px each cycle
178 | this.ppu.horizontalPos++;
179 |
180 | // are we at the end of the scanline?
181 | // each scanline lasts for 341 PPU clock cycles (113.667 CPU clock cycles; 1 CPU cycle = 3 PPU cycles),
182 | if (this.ppu.horizontalPos === 341) {
183 | this.ppu.horizontalPos = 0;
184 | this.ppu.bufferScanline();
185 | }
186 | };
187 |
188 | w.JNE.PPU = PPU;
189 |
190 | })(window);
191 |
--------------------------------------------------------------------------------
/src/version.js.rep:
--------------------------------------------------------------------------------
1 | (function (w) {
2 | "use strict";
3 |
4 | w.JNE = w.JNE || {};
5 |
6 | w.JNE.VERSION = '{{ VERSION }}';
7 |
8 | })(window);
9 |
--------------------------------------------------------------------------------
/test/assembler.js:
--------------------------------------------------------------------------------
1 |
2 | QUnit.module("Assembler", {
3 | setup: function () {
4 | var mmc = new JNE.MMC();
5 | var cpu = new JNE.CPU(mmc);
6 | window.assembler = new JNE.Assembler(cpu);
7 | },
8 | teardown: function () {
9 | window.assembler = null;
10 | }
11 | });
12 |
13 | QUnit.test("ADC", function (assert) {
14 |
15 | // IMMEDIATE
16 | var bytes = assembler.assemble('ADC #$42');
17 | var expected = [0x69, 0x42];
18 | assert.deepEqual(bytes, expected, 'ADC #$42 assembles');
19 |
20 | // ZERO PAGE
21 | bytes = assembler.assemble('ADC $10');
22 | expected = [0x65, 0x10];
23 | assert.deepEqual(bytes, expected, 'ADC $10 assembles');
24 |
25 | // ABSOLUTE
26 | bytes = assembler.assemble('ADC $1020');
27 | expected = [0x6D, 0x20, 0x10];
28 | assert.deepEqual(bytes, expected, 'ADC $1020 assembles');
29 |
30 | // INDEXED INDIRECT
31 | bytes = assembler.assemble('ADC ($10,X)');
32 | expected = [0x61, 0x10];
33 | assert.deepEqual(bytes, expected, 'ADC ($10,X) assembles');
34 |
35 | // INDIRECT INDEXED
36 | bytes = assembler.assemble('ADC ($10),Y');
37 | expected = [0x71, 0x10];
38 | assert.deepEqual(bytes, expected, 'ADC ($10),Y assembles');
39 |
40 | //IMPLICIT
41 | assert.throws(
42 | function(){
43 | assembler.assemble('ADC');
44 | },
45 | /Invalid memory address mode/,
46 | 'Implicit memory address with ADC throws error'
47 | );
48 |
49 | //ACCUMULATOR
50 | assert.throws(
51 | function(){
52 | assembler.assemble('ADC');
53 | },
54 | /Invalid memory address mode/,
55 | 'Accumulator memory address with ADC throws error'
56 | );
57 |
58 | // INDIRECT (JMP only)
59 | assert.throws(
60 | function(){
61 | assembler.assemble('ADC ($1000)');
62 | },
63 | /Invalid memory address mode/,
64 | 'Indirect memory address with ADC throws error'
65 | );
66 | });
67 |
68 | QUnit.test("Label resolution", function(assert){
69 |
70 | var code = 'INX\n' +
71 | 'BPL test\n' +
72 | 'INX\n' +
73 | 'test:\n' +
74 | 'TXA\n';
75 |
76 | var bytes = assembler.assemble(code);
77 |
78 | var expected = [0xe8, 0x10, 0x01, 0xe8, 0x8a];
79 | assert.deepEqual(bytes, expected, 'Label resolution test with BPL');
80 |
81 | });
82 |
83 | QUnit.test("Example programs", function(assert){
84 |
85 |
86 | code = [
87 | 'init:',
88 | 'JSR init'
89 | ].join('\n');
90 |
91 | expected = [
92 | 0x20, 0x00, 0x06
93 | ];
94 |
95 | assert.deepEqual(assembler.assemble(code), expected, 'JSR to label');
96 |
97 |
98 |
99 | // Testing some example programs from http://skilldrick.github.io/easy6502/#intro are assembled correctly
100 |
101 | var code = [
102 | 'LDA #$01',
103 | 'STA $0200',
104 | 'LDA #$05',
105 | 'STA $0201',
106 | 'LDA #$08',
107 | 'STA $0202'
108 | ].join('\n');
109 |
110 | var expected = [
111 | 0xa9, 0x01,
112 | 0x8d, 0x00, 0x02,
113 | 0xa9, 0x05,
114 | 0x8d, 0x01, 0x02,
115 | 0xa9, 0x08,
116 | 0x8d, 0x02, 0x02
117 | ];
118 |
119 | assert.deepEqual(assembler.assemble(code), expected, 'Example program assembly test #1');
120 |
121 |
122 | // example #2
123 |
124 | code = [
125 | 'LDA #$c0 ;Load the hex value $c0 into the A register',
126 | 'TAX ;Transfer the value in the A register to X',
127 | 'INX ;Increment the value in the X register',
128 | 'ADC #$c4 ;Add the hex value $c4 to the A register',
129 | 'BRK ;Break - we are done'
130 | ].join('\n');
131 |
132 | expected = [
133 | 0xa9, 0xc0, 0xaa, 0xe8, 0x69, 0xc4, 0x00
134 | ];
135 |
136 | assert.deepEqual(assembler.assemble(code), expected, 'Example program assembly test #2');
137 |
138 |
139 | // Snake game from same site as above
140 |
141 | code = [
142 | ' ; Change direction: W A S D',
143 | '',
144 | ' define appleL $00 ; screen location of apple, low byte',
145 | ' define appleH $01 ; screen location of apple, high byte',
146 | ' define snakeHeadL $10 ; screen location of snake head, low byte',
147 | ' define snakeHeadH $11 ; screen location of snake head, high byte',
148 | ' define snakeBodyStart $12 ; start of snake body byte pairs',
149 | ' define snakeDirection $02 ; direction (possible values are below)',
150 | ' define snakeLength $03 ; snake length, in bytes',
151 | '',
152 | ' ; Directions (each using a separate bit)',
153 | ' define movingUp 1',
154 | ' define movingRight 2',
155 | ' define movingDown 4',
156 | ' define movingLeft 8',
157 | '',
158 | ' ; ASCII values of keys controlling the snake',
159 | ' define ASCII_w $77',
160 | ' define ASCII_a $61',
161 | ' define ASCII_s $73',
162 | ' define ASCII_d $64',
163 | '',
164 | ' ; System variables',
165 | ' define sysRandom $fe',
166 | ' define sysLastKey $ff',
167 | '',
168 | '',
169 | ' jsr init',
170 | ' jsr loop',
171 | '',
172 | ' init:',
173 | ' jsr initSnake',
174 | ' jsr generateApplePosition',
175 | ' rts',
176 | '',
177 | '',
178 | ' initSnake:',
179 | ' lda #movingRight ;start direction',
180 | ' sta snakeDirection',
181 | '',
182 | ' lda #4 ;start length (2 segments)',
183 | ' sta snakeLength',
184 | '',
185 | ' lda #$11',
186 | ' sta snakeHeadL',
187 | '',
188 | ' lda #$10',
189 | ' sta snakeBodyStart',
190 | '',
191 | ' lda #$0f',
192 | ' sta $14 ; body segment 1',
193 | '',
194 | ' lda #$04',
195 | ' sta snakeHeadH',
196 | ' sta $13 ; body segment 1',
197 | ' sta $15 ; body segment 2',
198 | ' rts',
199 | '',
200 | '',
201 | ' generateApplePosition:',
202 | ' ;load a new random byte into $00',
203 | ' lda sysRandom',
204 | ' sta appleL',
205 | '',
206 | ' ;load a new random number from 2 to 5 into $01',
207 | ' lda sysRandom',
208 | ' and #$03 ;mask out lowest 2 bits',
209 | ' clc',
210 | ' adc #2',
211 | ' sta appleH',
212 | '',
213 | ' rts',
214 | '',
215 | '',
216 | ' loop:',
217 | ' jsr readKeys',
218 | ' jsr checkCollision',
219 | ' jsr updateSnake',
220 | ' jsr drawApple',
221 | ' jsr drawSnake',
222 | ' jsr spinWheels',
223 | ' jmp loop',
224 | '',
225 | '',
226 | ' readKeys:',
227 | ' lda sysLastKey',
228 | ' cmp #ASCII_w',
229 | ' beq upKey',
230 | ' cmp #ASCII_d',
231 | ' beq rightKey',
232 | ' cmp #ASCII_s',
233 | ' beq downKey',
234 | ' cmp #ASCII_a',
235 | ' beq leftKey',
236 | ' rts',
237 | ' upKey:',
238 | ' lda #movingDown',
239 | ' bit snakeDirection',
240 | ' bne illegalMove',
241 | '',
242 | ' lda #movingUp',
243 | ' sta snakeDirection',
244 | ' rts',
245 | ' rightKey:',
246 | ' lda #movingLeft',
247 | ' bit snakeDirection',
248 | ' bne illegalMove',
249 | '',
250 | ' lda #movingRight',
251 | ' sta snakeDirection',
252 | ' rts',
253 | ' downKey:',
254 | ' lda #movingUp',
255 | ' bit snakeDirection',
256 | ' bne illegalMove',
257 | '',
258 | ' lda #movingDown',
259 | ' sta snakeDirection',
260 | ' rts',
261 | ' leftKey:',
262 | ' lda #movingRight',
263 | ' bit snakeDirection',
264 | ' bne illegalMove',
265 | '',
266 | ' lda #movingLeft',
267 | ' sta snakeDirection',
268 | ' rts',
269 | ' illegalMove:',
270 | ' rts',
271 | '',
272 | '',
273 | ' checkCollision:',
274 | ' jsr checkAppleCollision',
275 | ' jsr checkSnakeCollision',
276 | ' rts',
277 | '',
278 | '',
279 | ' checkAppleCollision:',
280 | ' lda appleL',
281 | ' cmp snakeHeadL',
282 | ' bne doneCheckingAppleCollision',
283 | ' lda appleH',
284 | ' cmp snakeHeadH',
285 | ' bne doneCheckingAppleCollision',
286 | '',
287 | ' ;eat apple',
288 | ' inc snakeLength',
289 | ' inc snakeLength ;increase length',
290 | ' jsr generateApplePosition',
291 | ' doneCheckingAppleCollision:',
292 | ' rts',
293 | '',
294 | '',
295 | ' checkSnakeCollision:',
296 | ' ldx #2 ;start with second segment',
297 | ' snakeCollisionLoop:',
298 | ' lda snakeHeadL,x',
299 | ' cmp snakeHeadL',
300 | ' bne continueCollisionLoop',
301 | '',
302 | ' maybeCollided:',
303 | ' lda snakeHeadH,x',
304 | ' cmp snakeHeadH',
305 | ' beq didCollide',
306 | '',
307 | ' continueCollisionLoop:',
308 | ' inx',
309 | ' inx',
310 | ' cpx snakeLength ;got to last section with no collision',
311 | ' beq didntCollide',
312 | ' jmp snakeCollisionLoop',
313 | '',
314 | ' didCollide:',
315 | ' jmp gameOver',
316 | ' didntCollide:',
317 | ' rts',
318 | '',
319 | '',
320 | ' updateSnake:',
321 | ' ldx snakeLength',
322 | ' dex',
323 | ' txa',
324 | ' updateloop:',
325 | ' lda snakeHeadL,x',
326 | ' sta snakeBodyStart,x',
327 | ' dex',
328 | ' bpl updateloop',
329 | '',
330 | ' lda snakeDirection',
331 | ' lsr',
332 | ' bcs up',
333 | ' lsr',
334 | ' bcs right',
335 | ' lsr',
336 | ' bcs down',
337 | ' lsr',
338 | ' bcs left',
339 | ' up:',
340 | ' lda snakeHeadL',
341 | ' sec',
342 | ' sbc #$20',
343 | ' sta snakeHeadL',
344 | ' bcc upup',
345 | ' rts',
346 | ' upup:',
347 | ' dec snakeHeadH',
348 | ' lda #$1',
349 | ' cmp snakeHeadH',
350 | ' beq collision',
351 | ' rts',
352 | ' right:',
353 | ' inc snakeHeadL',
354 | ' lda #$1f',
355 | ' bit snakeHeadL',
356 | ' beq collision',
357 | ' rts',
358 | ' down:',
359 | ' lda snakeHeadL',
360 | ' clc',
361 | ' adc #$20',
362 | ' sta snakeHeadL',
363 | ' bcs downdown',
364 | ' rts',
365 | ' downdown:',
366 | ' inc snakeHeadH',
367 | ' lda #$6',
368 | ' cmp snakeHeadH',
369 | ' beq collision',
370 | ' rts',
371 | ' left:',
372 | ' dec snakeHeadL',
373 | ' lda snakeHeadL',
374 | ' and #$1f',
375 | ' cmp #$1f',
376 | ' beq collision',
377 | ' rts',
378 | ' collision:',
379 | ' jmp gameOver',
380 | '',
381 | '',
382 | ' drawApple:',
383 | ' ldy #0',
384 | ' lda sysRandom',
385 | ' sta (appleL),y',
386 | ' rts',
387 | '',
388 | '',
389 | ' drawSnake:',
390 | ' ldx #0',
391 | ' lda #1',
392 | ' sta (snakeHeadL,x) ; paint head',
393 | '',
394 | ' ldx snakeLength',
395 | ' lda #0',
396 | ' sta (snakeHeadL,x) ; erase end of tail',
397 | ' rts',
398 | '',
399 | '',
400 | ' spinWheels:',
401 | ' ldx #0',
402 | ' spinloop:',
403 | ' nop',
404 | ' nop',
405 | ' dex',
406 | ' bne spinloop',
407 | ' rts',
408 | '',
409 | '',
410 | ' gameOver:'
411 | ].join('\n');
412 |
413 | expected = [
414 | 0x20, 0x06, 0x06, 0x20, 0x38, 0x06, 0x20, 0x0d, 0x06, 0x20, 0x2a, 0x06, 0x60, 0xa9, 0x02, 0x85, 0x02,
415 | 0xa9, 0x04, 0x85, 0x03, 0xa9, 0x11, 0x85, 0x10, 0xa9, 0x10, 0x85, 0x12, 0xa9, 0x0f, 0x85, 0x14, 0xa9,
416 | 0x04, 0x85, 0x11, 0x85, 0x13, 0x85, 0x15, 0x60, 0xa5, 0xfe, 0x85, 0x00, 0xa5, 0xfe, 0x29, 0x03, 0x18,
417 | 0x69, 0x02, 0x85, 0x01, 0x60, 0x20, 0x4d, 0x06, 0x20, 0x8d, 0x06, 0x20, 0xc3, 0x06, 0x20, 0x19, 0x07,
418 | 0x20, 0x20, 0x07, 0x20, 0x2d, 0x07, 0x4c, 0x38, 0x06, 0xa5, 0xff, 0xc9, 0x77, 0xf0, 0x0d, 0xc9, 0x64,
419 | 0xf0, 0x14, 0xc9, 0x73, 0xf0, 0x1b, 0xc9, 0x61, 0xf0, 0x22, 0x60, 0xa9, 0x04, 0x24, 0x02, 0xd0, 0x26,
420 | 0xa9, 0x01, 0x85, 0x02, 0x60, 0xa9, 0x08, 0x24, 0x02, 0xd0, 0x1b, 0xa9, 0x02, 0x85, 0x02, 0x60, 0xa9,
421 | 0x01, 0x24, 0x02, 0xd0, 0x10, 0xa9, 0x04, 0x85, 0x02, 0x60, 0xa9, 0x02, 0x24, 0x02, 0xd0, 0x05, 0xa9,
422 | 0x08, 0x85, 0x02, 0x60, 0x60, 0x20, 0x94, 0x06, 0x20, 0xa8, 0x06, 0x60, 0xa5, 0x00, 0xc5, 0x10, 0xd0,
423 | 0x0d, 0xa5, 0x01, 0xc5, 0x11, 0xd0, 0x07, 0xe6, 0x03, 0xe6, 0x03, 0x20, 0x2a, 0x06, 0x60, 0xa2, 0x02,
424 | 0xb5, 0x10, 0xc5, 0x10, 0xd0, 0x06, 0xb5, 0x11, 0xc5, 0x11, 0xf0, 0x09, 0xe8, 0xe8, 0xe4, 0x03, 0xf0,
425 | 0x06, 0x4c, 0xaa, 0x06, 0x4c, 0x35, 0x07, 0x60, 0xa6, 0x03, 0xca, 0x8a, 0xb5, 0x10, 0x95, 0x12, 0xca,
426 | 0x10, 0xf9, 0xa5, 0x02, 0x4a, 0xb0, 0x09, 0x4a, 0xb0, 0x19, 0x4a, 0xb0, 0x1f, 0x4a, 0xb0, 0x2f, 0xa5,
427 | 0x10, 0x38, 0xe9, 0x20, 0x85, 0x10, 0x90, 0x01, 0x60, 0xc6, 0x11, 0xa9, 0x01, 0xc5, 0x11, 0xf0, 0x28,
428 | 0x60, 0xe6, 0x10, 0xa9, 0x1f, 0x24, 0x10, 0xf0, 0x1f, 0x60, 0xa5, 0x10, 0x18, 0x69, 0x20, 0x85, 0x10,
429 | 0xb0, 0x01, 0x60, 0xe6, 0x11, 0xa9, 0x06, 0xc5, 0x11, 0xf0, 0x0c, 0x60, 0xc6, 0x10, 0xa5, 0x10, 0x29,
430 | 0x1f, 0xc9, 0x1f, 0xf0, 0x01, 0x60, 0x4c, 0x35, 0x07, 0xa0, 0x00, 0xa5, 0xfe, 0x91, 0x00, 0x60, 0xa2,
431 | 0x00, 0xa9, 0x01, 0x81, 0x10, 0xa6, 0x03, 0xa9, 0x00, 0x81, 0x10, 0x60, 0xa2, 0x00, 0xea, 0xea, 0xca,
432 | 0xd0, 0xfb, 0x60
433 | ];
434 |
435 | assert.deepEqual(assembler.assemble(code), expected, 'Assemble complete snake game from http://skilldrick.github.io/easy6502/#snake');
436 |
437 | });
438 |
--------------------------------------------------------------------------------
/test/clock.js:
--------------------------------------------------------------------------------
1 |
2 | QUnit.module("Clock", {
3 | setup: function () {
4 | window.clock = new JNE.Clock();
5 | },
6 | teardown: function () {
7 | window.clock = null;
8 | }
9 | });
10 |
11 | QUnit.asyncTest("Clock frequency is within 1% of bare metal", function(assert){
12 |
13 | var testLength = 5; // seconds
14 |
15 | function endClockTest() {
16 |
17 | clock.stop();
18 |
19 | var marginOfError = 0.01;
20 |
21 | var cps = window.cycleCount / testLength;
22 |
23 | assert.ok(cps <= (clock.cpuClockSpeed * (1 + marginOfError)) && cps >= (clock.cpuClockSpeed * (1 - marginOfError)), 'Clock speed falls within acceptable bounds. Result ' + (((cps / clock.cpuClockSpeed) * 100).toFixed(2)) + '% of expected (' + cps + 'Hz)');
24 |
25 | QUnit.start();
26 | }
27 |
28 | function tick(cycles){
29 | window.cycleCount += cycles;
30 | }
31 |
32 | window.cycleCount = 0;
33 |
34 | clock.onTick(tick);
35 |
36 | clock.start();
37 |
38 | setTimeout(endClockTest, (testLength * 1000));
39 | });
40 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | QUnit tests: js-nes-emulator
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/mmc.js:
--------------------------------------------------------------------------------
1 |
2 | QUnit.module("MMC", {
3 | setup: function () {
4 | window.mmc = new JNE.MMC();
5 | },
6 | teardown: function () {
7 | window.mmc = null;
8 | }
9 | });
10 |
11 | QUnit.test("MMC initialises memory", function (assert) {
12 |
13 | assert.equal(mmc.size, 0x10000, 'MMC is configured to initialise 65536 bytes of memory.');
14 |
15 | assert.equal(mmc.memory.length, 0x10000, 'Memory initialised is of size 65536 bytes.');
16 |
17 | assert.equal(mmc.fetch(0), 0, 'Memory at zero offset has zero value.');
18 | assert.equal(mmc.fetch(0x01), 0, 'Memory at 0x01 offset has zero value.');
19 | assert.equal(mmc.fetch(0xFF), 0, 'Memory at 0xFF offset has zero value.');
20 | assert.equal(mmc.fetch(mmc.size - 1), 0, 'Memory at last offset has zero value.');
21 |
22 | });
23 |
24 | QUnit.test("MMC resets memory to zero byte values", function (assert) {
25 |
26 | mmc.store(0x00, 0x01);
27 | mmc.store(0x01, 0x01);
28 | mmc.store(0xFF, 0x01);
29 | mmc.store(mmc.size - 1, 0x01);
30 |
31 | mmc.reset();
32 |
33 | assert.equal(mmc.memory.length, 0x10000, 'Memory is still of size 65536 bytes after reset.');
34 |
35 | assert.equal(mmc.fetch(0), 0, 'Memory at zero offset has zero value.');
36 | assert.equal(mmc.fetch(0x01), 0, 'Memory at 0x01 offset has zero value.');
37 | assert.equal(mmc.fetch(0xFF), 0, 'Memory at 0xFF offset has zero value.');
38 | assert.equal(mmc.fetch(mmc.size - 1), 0, 'Memory at last offset has zero value.');
39 |
40 | });
41 |
42 | QUnit.test("MMC stores values in memory", function (assert) {
43 |
44 | function testStore(address, value) {
45 | mmc.store(address, value);
46 | assert.equal(mmc.memory[address], value, 'Value ' + value + ' is stored at location 0x' + address.toString(16));
47 | }
48 |
49 | testStore(0x00, 0x01);
50 | testStore(0x00, 0xFF);
51 | testStore(0x00, 0x00);
52 | testStore(0x00, 0x07);
53 |
54 | testStore(0x01, 0x01);
55 | testStore(0x01, 0xFF);
56 | testStore(0x01, 0x00);
57 | testStore(0x01, 0x07);
58 |
59 | testStore(mmc.memory.length - 1, 0x01);
60 | testStore(mmc.memory.length - 1, 0xFF);
61 | testStore(mmc.memory.length - 1, 0x00);
62 | testStore(mmc.memory.length - 1, 0x07);
63 |
64 | mmc.store(0x200, 0x1001);
65 | assert.equal(mmc.memory[0x200], 0x1, 'Values greater than 0xff are ANDed to a single byte');
66 |
67 | assert.throws(
68 | function () {
69 | mmc.store(mmc.memory.length, 0x00);
70 | },
71 | /Invalid memory address/,
72 | 'Exception is thrown when attempting to write to invalid location in memory (>= size).'
73 | );
74 |
75 | assert.throws(
76 | function () {
77 | mmc.store(-1, 0x00);
78 | },
79 | /Invalid memory address/,
80 | 'Exception is thrown when attempting to write to invalid location in memory (-1).'
81 | );
82 |
83 | assert.throws(
84 | function () {
85 | mmc.store(null, 0x00);
86 | },
87 | /Invalid memory address/,
88 | 'Exception is thrown when attempting to write to invalid location in memory (null).'
89 | );
90 |
91 | assert.throws(
92 | function () {
93 | mmc.store(undefined, 0x00);
94 | },
95 | /Invalid memory address/,
96 | 'Exception is thrown when attempting to write to invalid location in memory (undefined).'
97 | );
98 |
99 | });
100 |
101 | QUnit.test("MMC retrieves values from memory", function (assert) {
102 |
103 | function testFetch(address, value) {
104 | mmc.store(address, value);
105 | assert.equal(mmc.fetch(address), value, 'Value ' + value + ' can be retrieved from location 0x' + address.toString(16));
106 | }
107 |
108 | testFetch(0x00, 0x01);
109 | testFetch(0x00, 0xFF);
110 | testFetch(0x00, 0x00);
111 | testFetch(0x00, 0x01);
112 |
113 | testFetch(0xF, 0x01);
114 | testFetch(0xF, 0xFF);
115 | testFetch(0xF, 0x00);
116 | testFetch(0xF, 0x01);
117 |
118 | testFetch(mmc.memory.length - 1, 0x01);
119 | testFetch(mmc.memory.length - 1, 0xFF);
120 | testFetch(mmc.memory.length - 1, 0x00);
121 | testFetch(mmc.memory.length - 1, 0x01);
122 |
123 | assert.throws(
124 | function () {
125 | mmc.fetch(mmc.memory.length);
126 | },
127 | /Invalid memory address/,
128 | 'Exception is thrown when attempting to fetch from invalid location in memory (>= size).'
129 | );
130 |
131 | assert.throws(
132 | function () {
133 | mmc.fetch(-1);
134 | },
135 | /Invalid memory address/,
136 | 'Exception is thrown when attempting to fetch from invalid location in memory (-1).'
137 | );
138 |
139 | assert.throws(
140 | function () {
141 | mmc.fetch(null);
142 | },
143 | /Invalid memory address/,
144 | 'Exception is thrown when attempting to fetch from invalid location in memory (null).'
145 | );
146 |
147 | assert.throws(
148 | function () {
149 | mmc.fetch(undefined);
150 | },
151 | /Invalid memory address/,
152 | 'Exception is thrown when attempting to fetch from invalid location in memory (undefined).'
153 | );
154 |
155 | });
156 |
157 | /**
158 | * The PPU exposes eight memory-mapped registers to the CPU. These nominally sit at $2000 through $2007 in the CPU's
159 | * address space, but because they're incompletely decoded, they're mirrored in every 8 bytes from $2008 through $3FFF,
160 | * so a write to $3456 is the same as a write to $2006.
161 | */
162 | QUnit.test("MMC translates mirrored memory addresses correctly for the PPU", function (assert) {
163 | mmc.store(0x2001, 7);
164 | mmc.store(0x2006, 123);
165 | assert.equal(mmc.fetch(0x2001), 7);
166 | assert.equal(mmc.fetch(0x3451), 7);
167 | assert.equal(mmc.fetch(0x2006), 123);
168 | assert.equal(mmc.fetch(0x3456), 123);
169 | });
--------------------------------------------------------------------------------
/test/ppu.js:
--------------------------------------------------------------------------------
1 |
2 | QUnit.module("PPU", {
3 | setup: function () {
4 | window.mmc = new JNE.MMC();
5 | window.ppu = new JNE.PPU(window.mmc);
6 | },
7 | teardown: function () {
8 | window.ppu = null;
9 | window.mmc = null;
10 | }
11 | });
12 |
13 | /**
14 | QUnit.test("PPU", function (assert) {
15 |
16 | //assert.equal(
17 |
18 | });
19 | */
--------------------------------------------------------------------------------
/test/support.js:
--------------------------------------------------------------------------------
1 | if (!Array.prototype.fill) {
2 | Object.defineProperty(Array.prototype, 'fill', {
3 | value: function(value) {
4 |
5 | // Steps 1-2.
6 | if (this === null) {
7 | throw new TypeError('this is null or not defined');
8 | }
9 |
10 | var O = Object(this);
11 |
12 | // Steps 3-5.
13 | var len = O.length >>> 0;
14 |
15 | // Steps 6-7.
16 | var start = arguments[1];
17 | var relativeStart = start >> 0;
18 |
19 | // Step 8.
20 | var k = relativeStart < 0 ?
21 | Math.max(len + relativeStart, 0) :
22 | Math.min(relativeStart, len);
23 |
24 | // Steps 9-10.
25 | var end = arguments[2];
26 | var relativeEnd = end === undefined ?
27 | len : end >> 0;
28 |
29 | // Step 11.
30 | var final = relativeEnd < 0 ?
31 | Math.max(len + relativeEnd, 0) :
32 | Math.min(relativeEnd, len);
33 |
34 | // Step 12.
35 | while (k < final) {
36 | O[k] = value;
37 | k++;
38 | }
39 |
40 | // Step 13.
41 | return O;
42 | }
43 | });
44 | }
--------------------------------------------------------------------------------