diff --git a/.gitignore b/.gitignore index 9d92b2e1..6e20ece5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ /perf.data .eslintcache **/.DS_Store + +coverage \ No newline at end of file diff --git a/.husky/.gitignore b/.husky/.gitignore index 31354ec1..783a8f95 100644 --- a/.husky/.gitignore +++ b/.husky/.gitignore @@ -1 +1,3 @@ _ + +coverage \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore index 697fad70..c1e1219f 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,2 +1,4 @@ workspace.xml .name + +coverage \ No newline at end of file diff --git a/6502.js b/6502.js index 5a92496d..a7b50c95 100644 --- a/6502.js +++ b/6502.js @@ -1,6 +1,5 @@ "use strict"; import * as utils from "./utils.js"; -import * as opcodes from "./6502.opcodes.js"; import * as via from "./via.js"; import { Acia } from "./acia.js"; import { Serial } from "./serial.js"; @@ -107,7 +106,7 @@ class Base6502 { this.a = this.x = this.y = this.s = 0; this.p = new Flags(); this.pc = 0; - this.opcodes = model.nmos ? opcodes.Cpu6502(this) : opcodes.Cpu65c12(this); + this.opcodes = model.opcodesFactory(this); this.disassembler = this.opcodes.disassembler; this.forceTracing = false; this.runner = this.opcodes.runInstruction; @@ -736,12 +735,13 @@ export class Cpu6502 extends Base6502 { } handleEconetStationId() { + if (!this.econet) return 0xff; this.econet.econetNMIEnabled = false; return this.econet.stationId; } handleEconetNMIEnable() { - if (!this.econet.econetNMIEnabled) { + if (this.econet && !this.econet.econetNMIEnabled) { // was off this.econet.econetNMIEnabled = true; if (this.econet.ADLC.status1 & 128) { @@ -1072,34 +1072,24 @@ export class Cpu6502 extends Base6502 { if (/\.zip/i.test(name)) { data = utils.unzipRomImage(data).data; } - const len = data.length; - if (len !== 16384 && len !== 8192) { - throw new Error("Broken rom file"); - } - for (let i = 0; i < len; ++i) { - ramRomOs[offset + i] = data[i]; - } + ramRomOs.set(data, offset); } async loadOs(os) { const extraRoms = Array.prototype.slice.call(arguments, 1).concat(this.config.extraRoms); os = "roms/" + os; - console.log("Loading OS from " + os); + console.log(`Loading OS from ${os}`); const ramRomOs = this.ramRomOs; const data = await utils.loadData(os); const len = data.length; - if (len < 16384 || len & 16383) throw new Error("Broken ROM file (length=" + len + ")"); - for (let i = 0; i < 16384; ++i) { - ramRomOs[this.osOffset + i] = data[i]; - } + if (len < 16384 || len & 16383) throw new Error(`Broken OS ROM file (length=${len})`); + ramRomOs.set(data, this.osOffset); const numExtraBanks = (len - 16384) / 16384; let romIndex = 16 - numExtraBanks; for (let i_1 = 0; i_1 < numExtraBanks; ++i_1) { const srcBase = 16384 + 16384 * i_1; const destBase = this.romOffset + (romIndex + i_1) * 16384; - for (let j = 0; j < 16384; ++j) { - ramRomOs[destBase + j] = data[srcBase + j]; - } + ramRomOs.set(data.subarray(srcBase, srcBase + 16384), destBase); } const awaiting = []; for (let i_2 = 0; i_2 < extraRoms.length; ++i_2) { diff --git a/6502.opcodes.js b/6502.opcodes.js index 92cd3847..3d25cb4a 100644 --- a/6502.opcodes.js +++ b/6502.opcodes.js @@ -469,8 +469,52 @@ function getOp(op, arg) { read: true, write: true, }; + case "RMB0": + case "RMB1": + case "RMB2": + case "RMB3": + case "RMB4": + case "RMB5": + case "RMB6": + case "RMB7": + return { + op: [`REG = REG & (${~(1 << (op[3] - "0"))});`], + read: true, + write: true, + }; + case "SMB0": + case "SMB1": + case "SMB2": + case "SMB3": + case "SMB4": + case "SMB5": + case "SMB6": + case "SMB7": + return { + op: [`REG = REG | ${1 << (op[3] - "0")};`], + read: true, + write: true, + }; + case "BBR0": + case "BBR1": + case "BBR2": + case "BBR3": + case "BBR4": + case "BBR5": + case "BBR6": + case "BBR7": + return { op: `cpu.branch(!(REG & ${1 << (op[3] - "0")}));`, read: true }; + case "BBS0": + case "BBS1": + case "BBS2": + case "BBS3": + case "BBS4": + case "BBS5": + case "BBS6": + case "BBS7": + return { op: `cpu.branch(REG & ${1 << (op[3] - "0")});`, read: true }; } - return null; + throw new Error(`Unrecognised operation '${op}'`); } const opcodes6502 = { @@ -902,6 +946,42 @@ const opcodes65c12 = { 0xfe: "INC abs,x", }; +const opcodes65c02 = { + ...opcodes65c12, + 0x07: "RMB0 zp", + 0x17: "RMB1 zp", + 0x27: "RMB2 zp", + 0x37: "RMB3 zp", + 0x47: "RMB4 zp", + 0x57: "RMB5 zp", + 0x67: "RMB6 zp", + 0x77: "RMB7 zp", + 0x87: "SMB0 zp", + 0x97: "SMB1 zp", + 0xa7: "SMB2 zp", + 0xb7: "SMB3 zp", + 0xc7: "SMB4 zp", + 0xd7: "SMB5 zp", + 0xe7: "SMB6 zp", + 0xf7: "SMB7 zp", + 0x0f: "BBR0 zp,branch", + 0x1f: "BBR1 zp,branch", + 0x2f: "BBR2 zp,branch", + 0x3f: "BBR3 zp,branch", + 0x4f: "BBR4 zp,branch", + 0x5f: "BBR5 zp,branch", + 0x6f: "BBR6 zp,branch", + 0x7f: "BBR7 zp,branch", + 0x8f: "BBS0 zp,branch", + 0x9f: "BBS1 zp,branch", + 0xaf: "BBS2 zp,branch", + 0xbf: "BBS3 zp,branch", + 0xcf: "BBS4 zp,branch", + 0xdf: "BBS5 zp,branch", + 0xef: "BBS6 zp,branch", + 0xff: "BBS7 zp,branch", +}; + class Disassemble6502 { constructor(cpu, opcodes) { this.cpu = cpu; @@ -986,7 +1066,7 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { switch (arg) { case undefined: // Many of these ops need a little special casing. - if (op.read || op.write) throw "Unsupported " + opcodeString; + if (op.read || op.write) throw new Error(`Unsupported ${opcodeString}`); ig.append(op.preop); ig.tick(Math.max(2, 1 + (op.extra || 0))); ig.append(op.op); @@ -995,6 +1075,19 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { case "branch": return [op.op]; // special cased here, would be nice to pull out of cpu + case "zp,branch": + ig.tick(2); + ig.append("const addr = cpu.getb() | 0;"); + if (op.read) { + ig.zpReadOp("addr", "REG"); + if (op.write) { + ig.tick(1); // Spurious write + } + } + ig.append(op.op); + if (op.write) ig.zpWriteOp("addr", "REG"); + return ig.render(); + case "zp": case "zpx": // Seems to be enough to keep tests happy, but needs investigation. case "zp,x": @@ -1082,7 +1175,7 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { case "imm": if (op.write) { - throw "This isn't possible"; + throw new Error("This isn't possible"); } if (op.read) { // NOP imm @@ -1188,7 +1281,7 @@ function makeCpuFunctions(cpu, opcodes, is65c12) { return ig.render(); default: - throw "Unknown arg type " + arg; + throw new Error(`Unknown arg type ${arg}`); } } @@ -1309,3 +1402,7 @@ export function Cpu6502(cpu) { export function Cpu65c12(cpu) { return makeCpuFunctions(cpu, opcodes65c12, true); } + +export function Cpu65c02(cpu) { + return makeCpuFunctions(cpu, opcodes65c02, true); +} diff --git a/README.md b/README.md index 1c89520c..26815f5a 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,66 @@ [![jsbeeb tests](https://github.com/mattgodbolt/jsbeeb/actions/workflows/test-and-deploy.yml/badge.svg)](https://github.com/mattgodbolt/jsbeeb/actions/workflows/test-and-deploy.yml) -## jsbeeb - Javascript BBC Micro emulator +# jsbeeb - JavaScript BBC Micro Emulator -A BBC Micro emulator in Javascript. Runs on Firefox, Chrome and Microsoft Edge. Emulates a 32K BBC B (with sideways RAM) -and a 128K BBC Master. The BBC had a somewhat different-looking keyboard to a modern PC, and so it's useful to -know some of the mappings: +[![jsbeeb](images/jsbeeb-example.png)](https://bbc.xania.org/) -- BBC F0 is F10 -- BBC Break key is F12 -- BBC star is on " (if it doesn't work for you try shift-2) +A BBC Micro emulator written in JavaScript and running in modern browsers. Emulates a 32K BBC B (with sideways RAM) +and a 128K BBC Master, along with a number of different peripherals. -To play right now, visit [http://bbc.xania.org/](http://bbc.xania.org/). To load the default disc image (Elite in -this case), press shift-F12 (which is shift-Break on the BBC). +## Table of Contents -## Getting set up to run locally +- [Keyboard Mappings](#keyboard-mappings) +- [Getting Set Up to Run Locally](#getting-set-up-to-run-locally) +- [URL Parameters](#url-parameters) +- [Patches](#patches) +- [Loading BASIC Files from GitHub Gists](#loading-basic-files-from-github-gists) +- [Things Left to Do](#things-left-to-do) +- [Tests](#tests) +- [Thanks](#thanks) +- [More Information](#more-information) +- [License](#license) +- [Contact](#contact) -jsbeeb needs to be built with webpack. If you're developing locally you'll need a recent `node` and then run `npm install` to install needed packages. `node` can be installed from https://nodejs.org/. +## Keyboard Mappings -You can then run `npm start` to get a local webserver which will autoreload on changes in the code - visit http://localhost:8080/ and off you go. +The BBC had a somewhat different-looking keyboard to a modern PC, and so it's useful to know some of the mappings: -jsbeeb uses node and webpack to afford me simple and standard web development tooling and third party library access without lots of painful copy/paste or wheel-reinventing, as well as the ability to better run tests, and "pack" up the site to make it smaller and faster to load when it's deployed to https://bbc.xania.org. +- BBC `F0` is `F10` +- BBC `Break` key is `F12` +- BBC `*` is on `"` (if it doesn't work for you try shift-2) -## URL parameters +To play right now, visit [https://bbc.xania.org/](https://bbc.xania.org/). To load the default disc image (Elite in this +case), press shift-F12 (which is shift-Break on the BBC). + +## Getting Set Up to Run Locally + +### Prerequisites + +- Node.js (https://nodejs.org/) +- npm (comes with Node.js) + +### Installation + +1. Clone the repository: + ```sh + git clone https://github.com/mattgodbolt/jsbeeb.git + cd jsbeeb + ``` +2. Install dependencies: + ```sh + npm install + ``` +3. Start the local webserver: + ```sh + npm start + ``` +4. Visit `http://localhost:8080/` in your browser. + +jsbeeb uses Node.js and webpack to afford simple and standard web development tooling and third-party library access +without lots of painful copy/paste or wheel-reinventing, as well as the ability to better run tests, and "pack" up the +site to make it smaller and faster to load when it's deployed to [https://bbc.xania.org](https://bbc.xania.org). + +## URL Parameters - `autoboot` - fakes a shift break - `disc1=XXX` - loads disc XXX (from the `discs/` directory) into drive 1 @@ -31,94 +70,124 @@ jsbeeb uses node and webpack to afford me simple and standard web development to - `tape=XXX` - loads tape XXX (from the `tapes/` directory) - `tape=sth:ZZZ` - loads tape ZZZ from the Stairway to Hell archive - `patch=P` - applies a memory patch `P`. See below. -- `loadBasic=X` - loads 'X' (a resource on the webserver) as text, tokenises it and puts it in `PAGE` as if you'd typed it in to the emulator -- `embedBasic=X` - loads 'X' (a URI-encoded string) as text, tokenises it and puts it in `PAGE` as if you'd typed it in to the emulator +- `loadBasic=X` - loads 'X' (a resource on the webserver) as text, tokenises it and puts it in `PAGE` as if you'd typed + it in to the emulator +- `embedBasic=X` - loads 'X' (a URI-encoded string) as text, tokenises it and puts it in `PAGE` as if you'd typed it in + to the emulator - `autorun` - types `*TAPE` then `*/` to run from tape. In conjunction with `loadBasic` it types `RUN`. - `autochain` - types `*TAPE` then `CH.""` to run from tape. - `autotype` - types whatever you put after. e.g. `&autotype=PRINT"Matt is cool"%0a` (return is URI escaped to `%0a`) -- `embed` - Remove the margins around the screen, hide most navigation entries and make the page background transparent (intended for use when running within an iframe in a third-party site). -- `cpuMultiplier=X` speeds up the CPU by a factor of `X`. May be fractional or below one to slow the CPU down. NB disc loads become unreliable with a too-slow CPU, and running too fast might cause the browser to hang. -- `sbLeft` / `sbRight` / `sbBottom` - a URL to place left of, right of, or below the cub monitor. The left and right should be around 648 high and the bottom image should be around 896 wide. Left and right wider than 300 will run into problems on smaller screens; bottom taller than 100 or so similarly. -- `videoCyclesBatch` - the number of video cycles to batch up befofre running the video emulation. Defaults to zero: anything higher leads to emulation inaccuracies. Useful for showing why accuracy is important, even if less efficient. -- `rom` - load the given URL or path as an extra ROM. If a URL is provided, that URL must allow cross-site requests. Doesn't support the sth: pseudo URL unlike `disc` and `tape`, but if given a ZIP file will attempt to use the `.rom` file assumed to be within. +- `embed` - Remove the margins around the screen, hide most navigation entries and make the page background + transparent (intended for use when running within an iframe in a third-party site). +- `cpuMultiplier=X` speeds up the CPU by a factor of `X`. May be fractional or below one to slow the CPU down. NB disc + loads become unreliable with a too-slow CPU, and running too fast might cause the browser to hang. +- `sbLeft` / `sbRight` / `sbBottom` - a URL to place left of, right of, or below the cub monitor. The left and right + should be around 648 high and the bottom image should be around 896 wide. Left and right wider than 300 will run into + problems on smaller screens; bottom taller than 100 or so similarly. +- `videoCyclesBatch` - the number of video cycles to batch up before running the video emulation. Defaults to zero: + anything higher leads to emulation inaccuracies. Useful for showing why accuracy is important, even if less efficient. +- `rom` - load the given URL or path as an extra ROM. If a URL is provided, that URL must allow cross-site requests. + Doesn't support the sth: pseudo URL unlike `disc` and `tape`, but if given a ZIP file will attempt to use the `.rom` + file assumed to be within. - (mostly internal use) `logFdcCommands`, `logFdcStateChanges` - turn on logging in the disc controller. ## Patches -Patches can be applied by making a `patch=P` URL parameter. `P` is a sequence of semicolon separated patches of the form `@XXXX,YYYY:ZZZZZ,...` where the `@XXXX` specifies a PC address to breakpoint, the `YYYY` is the address to patch and the `ZZZZ` is the data to write at address `YYYY`. The `@` part is optional, but is handy to ensure the code you want to patch has actually loaded. For example: `patch=@31a6,0769:6e4c4d48465a` which is a patch for the default Elite image. Once the PC has reached `$31a6`, the bytes at `0769` are replaced with `6e4c4d48465a`. +Patches can be applied by making a `patch=P` URL parameter. `P` is a sequence of semicolon-separated patches of the form +`@XXXX,YYYY:ZZZZZ,...` where the `@XXXX` specifies a PC address to breakpoint, the `YYYY` is the address to patch and +the `ZZZZ` is the data to write at address `YYYY`. The `@` part is optional, but is handy to ensure the code you want to +patch has actually loaded. For example: `patch=@31a6,0769:6e4c4d48465a` which is a patch for the default Elite image. +Once the PC has reached `$31a6`, the bytes at `0769` are replaced with `6e4c4d48465a`. -## Loading BASIC files from GitHub gists +## Loading BASIC Files from GitHub Gists -- Create a gist with your code. https://gist.github.com/ - here's an [example](https://gist.github.com/mattgodbolt/fc8d6f3d6e5e015dce399013719c8341) -- Get the "Raw" link by clicking "raw" and copying the URL . In the case above that's: https://gist.githubusercontent.com/mattgodbolt/fc8d6f3d6e5e015dce399013719c8341/raw/bd5cb4314bfc3ee4330783ecf82cb329a36b915c/foo.bas -- Add that after "https://bbc.xania.org/?autorun&loadBasic=" or similar, for example, [this link](https://bbc.xania.org/?loadBasic=https://gist.githubusercontent.com/mattgodbolt/fc8d6f3d6e5e015dce399013719c8341/raw/bd5cb4314bfc3ee4330783ecf82cb329a36b915c/foo.bas&autorun) +1. Create a gist with your code. https://gist.github.com/ - here's + an [example](https://gist.github.com/mattgodbolt/fc8d6f3d6e5e015dce399013719c8341) +2. Get the "Raw" link by clicking "raw" and copying the URL. In the case above + that's: https://gist.githubusercontent.com/mattgodbolt/fc8d6f3d6e5e015dce399013719c8341/raw/bd5cb4314bfc3ee4330783ecf82cb329a36b915c/foo.bas +3. Add that after "https://bbc.xania.org/?autorun&loadBasic=" or similar, for + example, [this link](https://bbc.xania.org/?loadBasic=https://gist.githubusercontent.com/mattgodbolt/fc8d6f3d6e5e015dce399013719c8341/raw/bd5cb4314bfc3ee4330783ecf82cb329a36b915c/foo.bas&autorun) Note that every update you make means you need to make a new raw link. -## Things left to do +## Things Left to Do If you're looking to help: - Testing - - Play lots of games and report issues either on [github](https://github.com/mattgodbolt/jsbeeb/issues) or by email (matt@godbolt.org). + - Play lots of games and report issues either on [GitHub](https://github.com/mattgodbolt/jsbeeb/issues) or by email ( + matt@godbolt.org). - Core - Save state ability - Once we have this I'd love to get some "reverse step" debugging support - - Get the "boo" of the boot "boo-beep" working (disabled currently as the Javascript startup makes the sound dreadfully - choppy on Chrome at least). + - Get the "boo" of the boot "boo-beep" working (disabled currently as the JavaScript startup makes the sound + dreadfully choppy on Chrome at least). - Save disc support - Local discs need to be made more workable and need an "export" feature - Multiple discs need a UI - `git grep -i todo` - Optimisation - - While every attempt to make things fast has been made, I'm sure there's some more clever things that can be done without - compromising emulation accuracy + - While every attempt to make things fast has been made, I'm sure there's some more clever things that can be done + without compromising emulation accuracy ## Tests -For general correctness there are several tests in the `tests` directory, including: +For general correctness, there are several tests in the `tests` directory, including: -- Klaus Dormann's exhaustive test of all documented opcodes for 6502 and 65C12. - This is brought in as a git submodule from a forked version of Klaus's original as it needed - a few tweaks to get 65C12 working. +- Klaus Dormann's exhaustive [test of all documented opcodes](https://github.com/Klaus2m5/6502_65C02_functional_tests) + for 6502 and 65C12. This is brought in as a git submodule from a forked version of Klaus's original as it needed a few + tweaks to get 65C12 working. - hoglet's Binary Coded Decimal tests. -- A public domain Commodore 64 6502 test suite which tests every 6502 opcode (documented or - otherwise) for every possible input and flags condition. +- @dp111's [timing tests](https://github.com/dp111/6502Timing). Also brought in as a git submodule. +- A public domain Commodore 64 6502 test suite which tests every 6502 opcode (documented or otherwise) for every + possible input and flags condition. +- Some tests by @scarybeasts testing VIA and 65C12 functionality. -For timing correctness we have: +For timing correctness, we have: -- A timing test program written by Rich. It has been run on a real live BBC B and - the results are in the directory. An SSD of the same tests is in the `discs/` directory. -- Some of Kevin Edwards' protection systems (stripped of the games themselves). These are extremely - timing- and correctness-sensitive when it comes to the timers and interrupts of the BBC. +- A timing test program written by Rich. It has been run on a real live BBC B and the results are in the directory. An + SSD of the same tests is in the `discs/` directory. +- Some of Kevin Edwards' protection systems (stripped of the games themselves). These are extremely timing- and + correctness-sensitive when it comes to the timers and interrupts of the BBC. - Some 65C12-specific read-modify-write tests written by Ed Spittles. -Tests can be run automatically if you have `node` installed - just run `make` and it'll ensure the relevant libraries are installed, then it'll run the tests. -Please note it can take a while to run the whole test suite. +Tests can be run automatically if you have `node` installed - just run `make` and it'll ensure the relevant libraries +are installed, then it'll run the tests. Please note it can take a while to run the whole test suite. ## Thanks -jsbeeb was heavily based on Sarah Walker's C [B-Em emulator](https://github.com/stardot/b-em) -- thanks to her for her hard work and for open sourcing her code. B-em is now being maintained by a group of enthusiasts - thanks to them too! +jsbeeb was heavily based on Sarah Walker's C [B-Em emulator](https://github.com/stardot/b-em) -- thanks to her for her +hard work and for open sourcing her code. B-em is now being maintained by a group of enthusiasts - thanks to them too! -Huge thanks to Richard Talbot-Watkins for his advice and help along the way in fathoming out the instruction timings, interrupt fun, -video code rewrite and for being such a good pal all these many years! +Huge thanks to Richard Talbot-Watkins for his advice and help along the way in fathoming out the instruction timings, +interrupt fun, video code rewrite and for being such a good pal all these many years! -Thanks to [Michael Borcherds](https://twitter.com/mike_geogebra) for his help; improving the keyboard layouts and handling in Javascript, reporting issues, chasing down -game bugs and much more. +Thanks to [Michael Borcherds](https://twitter.com/mike_geogebra) for his help; improving the keyboard layouts and +handling in JavaScript, reporting issues, chasing down game bugs and much more. -Thanks to [David Banks](https://github.com/hoglet67) (hoglet) for his help in testing the gnarly BCD flag behaviour on real live BBCs. +Thanks to [David Banks](https://github.com/hoglet67) (hoglet) for his help in testing the gnarly BCD flag behaviour on +real live BBCs. Cheers to [Ed Spittles](https://github.com/BigEd) for testing various interrupt timing code on a real BBC. Thanks to Chris Jordan for his thorough testing, bug reports, ideas and help. -A lot of the early development used the amazing [Visual 6502](http://visual6502.org/) as reference for intra-instruction timings. Amazing stuff. +A lot of the early development used the amazing [Visual 6502](http://visual6502.org/) as reference for intra-instruction +timings. Amazing stuff. + +Special shout out to the users of the [6502 Forums](http://forum.6502.org/) + +## More Information + +I've written a lot about how the innards work on [my blog](http://xania.org) in +the [emulation](http://xania.org/Emulation-archive) section. I gave a presentation on how it all fits together at work, +and posted the [video up on YouTube](https://www.youtube.com/watch?v=37jyHQT7fXQ). I have another presentation at +[ABug](https://www.youtube.com/watch?v=ABmwJXMLzYM). + +## License -Special shout out the users of the [6502 Forums](http://forum.6502.org/) +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## More information +## Contact -I've written a lot of how the innards work on [my blog](http://xania.org) in the [emulation](http://xania.org/Emulation-archive) section. -I gave a presentation on how it all fits together at work, and posted the [video up on YouTube](https://www.youtube.com/watch?v=37jyHQT7fXQ). -I presented again at [GOTO Chicago 2016](http://gotocon.com/chicago-2016/presentation/Emulating%20a%206502%20system%20in%20Javascript), and I'm -hoping they post the video up. +For support or questions, please contact Matt Godbolt at matt@godbolt.org. diff --git a/bitstream.js b/bitstream.js deleted file mode 100644 index e9bb54cc..00000000 --- a/bitstream.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; - -export class BitStream { - constructor(data, numBits) { - if (numBits === undefined) numBits = 8 * data.length; - if (numBits <= 0) { - // no data is an endless stream of zeros - data = [0]; - numBits = 1; - } - this._numBits = numBits; - this._bytes = new Uint8Array(data); - this._index = 0; - } - - position() { - return this._index; - } - - nextBit() { - const byteIndex = this._index >>> 3; - const bitIndex = this._index & 7; - const result = ((this._bytes[byteIndex] >>> bitIndex) & 1) === 1; - if (++this._index === this._numBits) this._index = 0; - return result; - } - - nextBits(numBits) { - let result = 0; - for (let i = 0; i < numBits; ++i) { - result <<= 1; - if (this.nextBit()) result |= 1; - } - return result; - } - - peekBits(numBits) { - const savedIndex = this._index; - const result = this.nextBits(numBits); - this._index = savedIndex; - return result; - } -} diff --git a/disc-drive.js b/disc-drive.js index 423e2ee0..5d905dde 100644 --- a/disc-drive.js +++ b/disc-drive.js @@ -214,10 +214,17 @@ export class DiscDrive extends EventTarget { } /** - * Notify that an overall seek is happening. Purely informational. + * Notify that an overall seek is happening to a particular track. Purely informational. */ notifySeek(newTrack) { - this.dispatchEvent(new StepEvent(newTrack - this._track)); + this.notifySeekAmount(newTrack - this._track); + } + + /** + * Notify that an overall seek is happening by some delta smount. Purely informational. + */ + notifySeekAmount(delta) { + this.dispatchEvent(new StepEvent(delta)); } /** diff --git a/disc.js b/disc.js index 2091d550..27b12024 100644 --- a/disc.js +++ b/disc.js @@ -538,8 +538,6 @@ export function loadSsd(disc, data, isDsd, onChange) { let offset = 0; for (let track = 0; track < SsdFormat.tracksPerDisc; ++track) { - if (offset >= data.length) break; - for (let side = 0; side < numSides; ++side) { const trackBuilder = disc.buildTrack(side === 1, track); // Sync pattern at start of track, as the index pulse starts, aka GAP 5. diff --git a/discs/.gitignore b/discs/.gitignore index c9636bac..d0455f89 100644 --- a/discs/.gitignore +++ b/discs/.gitignore @@ -6,3 +6,5 @@ *.dsd *.hfe *.ssd + +coverage \ No newline at end of file diff --git a/econet.js b/econet.js index aab9d13d..395276bf 100644 --- a/econet.js +++ b/econet.js @@ -25,23 +25,21 @@ class ADLC { } export class EconetPacket { - constructor() { - if (arguments.length > 0) { - this.buffer = new Uint8Array(32 * 1024); - this.bytesInBuffer = arguments.length; - this.pointer = 0; - - // Populate packet contents from arguments - for (let i = 0; i < arguments.length; i++) { - this.buffer[i] = arguments[i]; - } - } else { - this.buffer = new Uint8Array(32 * 1024); - this.bytesInBuffer = 0; - this.pointer = 0; - } + constructor(...values) { + this.buffer = new Uint8Array(32 * 1024); + this.pointer = 0; + this.bytesInBuffer = values.length; this.controlFlag = 0; this.port = 0; + + // Populate packet contents from values + for (let i = 0; i < values.length; i++) { + this.buffer[i] = values[i]; + } + } + + get full() { + return this.pointer >= this.buffer.length; } } @@ -367,7 +365,7 @@ export class Econet { let TXlast = false; if (this.ADLC.txftl & this.powers[this.ADLC.txfptr - 1]) TXlast = true; // TxLast set if ( - this.beebTx.pointer > 32768 || // overflow IP buffer + this.beebTx.full || // overflow IP buffer this.ADLC.txfptr > 4 ) { // overflowed fifo diff --git a/fake6502.js b/fake6502.js index 599d2871..b980d5b4 100644 --- a/fake6502.js +++ b/fake6502.js @@ -1,9 +1,9 @@ -// Fakes out a 6502 +// Fakes out various 6502s for testing purposes. "use strict"; import { FakeVideo } from "./video.js"; import { FakeSoundChip } from "./soundchip.js"; -import { TEST_6502, TEST_65C12 } from "./models.js"; +import { findModel, TEST_6502, TEST_65C02, TEST_65C12 } from "./models.js"; import { FakeDdNoise } from "./ddnoise.js"; import { Cpu6502 } from "./6502.js"; import { Cmos } from "./cmos.js"; @@ -19,9 +19,14 @@ export function fake6502(model, opts) { opts = opts || {}; const video = opts.video || fakeVideo; model = model || TEST_6502; + if (opts.tube) model.tube = findModel("Tube65c02"); return new Cpu6502(model, dbgr, video, soundChip, new FakeDdNoise(), new FakeMusic5000(), new Cmos()); } +export function fake65C02() { + return fake6502(TEST_65C02); +} + export function fake65C12() { return fake6502(TEST_65C12); } diff --git a/images/jsbeeb-example.png b/images/jsbeeb-example.png new file mode 100644 index 00000000..05f47580 Binary files /dev/null and b/images/jsbeeb-example.png differ diff --git a/index.html b/index.html index d680311e..bbc5646a 100644 --- a/index.html +++ b/index.html @@ -442,8 +442,8 @@