TMS9918A and BIOS 1.6 — for the COB, DEV, KIM, VCS, PicoCalc and ACEs still on the original video card editionCurrent docs →
Skip to content

The emulator

The whole machine, in software. Same ROM, same BASIC, same everything — it just runs on a laptop instead of on a board.

Use it to try the machine before you build one, to work on a program when the real one's in another room, or to test something without hunting for a CompactFlash card.

Here's one now, running in this page. Click it once to give it the keyboard, then type. Open the full emulator

In the browser

https://acwright.github.io/6502-EMULATOR/v2/

Nothing to install. Open it, and you get the splash and the OK prompt in a tab. Everything in this guide works there.

It works on a phone too. The picture keeps its 4:3 shape whichever way you turn the phone, the controls rearrange themselves to fit, and the button puts the ACE's keyboard on the screen. Turned sideways, the keyboard moves to sit beside the picture instead of under it.

On a phone, tap before you try to type

The app starts with the keyboard down whatever you opened it on, so on a phone there is nothing to type into BASIC with until you tap that button. The machines running inside these pages are the other way around: they check what they are being read on, and a touch screen with no mouse gets the board without asking, because a frame you can't type into is a picture rather than an emulator.

On your desktop

There's a proper application for macOS, Windows and Linux, and it's the better option if you're going to use it much: it can attach a CompactFlash image, connect to a real serial port, keep a debug server running, and remember your settings.

Grab release 2.7.0 from the 6502-EMULATOR releases page. That's the release everything in this guide was checked on.

On a handheld

There's a third place this machine runs, and it isn't a window on a computer at all: a ClockworkPi PicoCalc — a pocket-sized keyboard and screen — will run the whole ACE as its firmware. One file onto the Pico inside it and the handheld boots to the OK prompt with nothing else involved: no host computer, no application, and a battery.

It has a launcher and an SD card of its own, so it gets its own chapter.

The KIM has its own

Fit the KIM keypad and you aren't running an ACE any more: the Keypad Card's ROM replaces BASIC and the Monitor, and the video and keyboard give way to a two-line display and twenty-four keys. That's a different machine, so it gets a different emulator — the KIMulator, at https://acwright.github.io/6502-KIMULATOR/, with desktop builds on its releases page.

Everything below is about this one. The KIMulator has the same shape — a browser build, a desktop app, a command line, and the same trick for putting a machine on a page of your own — with its own set of controls, because the hardware it stands for is different. The KIM chapter has one running on the page.

The toolbar

Everything happens from the row of buttons under the picture:

ButtonWhat it does
CPU chipLoad a ROM, in place of the built-in BIOS
Document+Load a cartridge
Document$Load a program (.prg / .bas) into memory
▶ / ■Run or stop the machine
Reset — exactly like the button on a real ACE. Memory survives.
Power cycle — the cold start. Memory is cleared.
1 MHz / 2 MHzSwitch CPU speed
SpeakerMute and unmute
Put the ACE's keyboard on the screen
ClipboardPaste text in as keystrokes
Settings

The speaker button tells you whether there is sound right now, not what the setting says — so it shows muted, dimmed, until you click it. Browsers won't let a page make noise until you've interacted with it, and that click is the interaction.

Settings has the same file rows, plus what's currently loaded and an to unload it again.

Pasting a program in

Ordinary ⌘V / Ctrl+V won't work, because the emulator sends every keystroke straight to the machine. Use the Clipboard button instead: paste your listing into the box it opens, and the emulator types it in for you. It's the quickest way to try anything longer than a couple of lines.

The keyboard on the screen

The button raises the ACE's own keyboard under the picture: the same 67 keys, in the same places, at the same widths as the ones soldered to the board.

It is the way in on a phone or a tablet, which have no keyboard to type on. It is also the quickest way to reach Fn, Ins or the arrows on a laptop that has put them somewhere else, so it is there at every size.

Every machine on this site has the same button. In a page like this one it opens itself on a device with no keyboard of its own; in the app it waits to be asked.

What it is not is a phone keyboard. There is no autocorrect and there are no symbol layers, because the board has neither, and it types in capitals like the board does — Shift gives you the symbol on a number key and leaves letters alone. The four keys that send nothing on the hardware send nothing here either, and like the hardware's they are still switches in the grid, so a program reading the matrix itself sees them held.

One finger, two keys

Shift, Ctrl, Alt and Fn latch rather than needing to be held: tap one to arm it for the next key you press, tap again to lock it down for a run of them, and a third time to let go.

Attaching a card

Settings → CF Card. In the desktop app, Select… picks a .img file — the sort cffs builds — and it stays attached across restarts. In the browser, Load uploads one and Export downloads the current card so you can keep it.

Once it's attached, DIR, LOAD and SAVE work exactly as they do on hardware, and what you save is written back to the image. The goes back to the emulator's own blank card.

The NVRAM row works the same way, for the 256 battery-backed bytes.

Playing without a joystick

A gamepad plugged into the computer needs no setting at all: the first one is JOY(1), the second is JOY(2). With no gamepad, the keyboard stands in for one, which is how most people end up playing a cartridge on a machine that lives in a window.

The first stick always has a keyboard behind it, and Settings → Joystick decides which:

Keyboard for JOY(1)UpDownLeftRightABXY
Numpad82460.5Enter
Arrows + SpaceSpace/.,

The numeric keypad is the default, and it's the one that stays out of your way: the ACE has no keypad of its own, so nothing you press there can arrive at the OK prompt as a typed character. You can leave a program listed on the screen and play with the keypad without disturbing a line of it.

Arrows + Space is for a laptop, where there is no keypad to press. It is not free: while it's selected the cursor keys drive the stick, so they stop moving around the line you're editing in BASIC or the Monitor. Set it back to Numpad — or to Off, which unbinds the keyboard entirely and leaves the stick to a gamepad — when you want to go back to typing.

The second stick is off until you ask for it. WASD keyboard for JOY(2) arms it: W A S D to move, Space for A, E for B, Q for X, R for Y. Every one of those is a letter you would otherwise be typing, which is why it isn't on by default — arm it for a two-player game, turn it off to write one.

The toolbar keeps a small readout of what is driving each stick right now — JOY1: NUM, JOY1: ARROW, JOY1: PAD, or off — so a stick that isn't answering is a glance rather than a guess.

Nothing changes for the program

The keyboard is standing in for a joystick, not pretending to be one somewhere else: JOY(1) reads the same port and returns the same bits either way. A game written against a real stick plays on the keypad, and a game written at the keypad plays on a real stick, with nothing to change in the listing.

Talking to real hardware

The desktop app can open one of your computer's serial ports. Pick the port, set it to 19200 8-N-1, connect — and now the emulated ACE is on the other end of a real serial cable. Which means you can use it as the terminal for a real ACE, or move files between the two with XModem. See Serial and a terminal.

Fullscreen

F11, or +Return on a Mac. The picture keeps its 4:3 shape whatever the window is doing.

Putting your program on the web

You've written a game. You want to show it to somebody.

Posting the .prg won't do it — nobody is going to download a file and then go looking for an emulator to open it in. What you want is a link that just plays. The emulator has a second page for exactly that, embed.html, meant to sit inside a page of yours:

html
<iframe
  src="https://acwright.github.io/6502-EMULATOR/v2/embed.html?prg=https://your-site.example/game.prg&autostart=1&autotype=RUN%5Cr"
  width="640" height="520"
  allow="autoplay; gamepad; fullscreen"
  style="border: 0"
></iframe>

That's the whole integration. prg is your program; autostart boots the machine as the page opens; autotype types RUN once BASIC is up, so a visitor gets a game rather than an OK prompt and a puzzle. %5Cr is how a \r — the Enter key — survives being written in a URL.

640 × 520 is the video output doubled, plus the emulator's control bar. Add &controls=none and 640 × 480 fits the picture exactly.

prg needs the program's full address

It's tempting to write prg=game.prg and drop the file next to your page. That doesn't work, and the way it fails is confusing: the emulator is what fetches the file, and the emulator is on its site, not yours — so game.prg means game.prg next to the emulator, which isn't there, and you get a working BASIC prompt and a 404 in a corner.

Give it the whole https://… address. The next section shows how to work that out when you don't know it yet.

On itch.io

itch.io will host a page like that for you, free. What it wants is a zip containing an index.html, uploaded as an HTML project, with the viewport set to 640 × 520.

The index.html is where the warning above bites. Itch decides your address when you upload, so you can't type it into the page beforehand — but the page can ask the browser for its own address and work the rest out from there. That's all the script below does:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Treasure Grid</title>
    <style>
      html,
      body {
        height: 100%;
        margin: 0;
        background: #000;
      }
      body {
        display: flex;
        align-items: center;
        justify-content: center;
      }
      iframe {
        width: 640px;
        height: 520px;
        max-width: 100vw;
        max-height: 100vh;
        border: 0;
      }
    </style>
  </head>
  <body>
    <iframe id="machine" title="Treasure Grid" allow="autoplay; gamepad; fullscreen"></iframe>

    <script>
      // The address of the program, worked out from the address of this page.
      //
      // This is the one line that cannot be written by hand. `prg` is fetched by
      // the emulator, which lives on its own site — so a relative `prg=game.prg`
      // is relative to *the emulator*, not to you, and comes back 404. The
      // program needs its full address, and you don't know what that is until
      // this page has been uploaded. So ask the browser at the last moment.
      const program = new URL('game.prg', location.href).href

      // `autostart=1` boots the machine as the page opens, and `autotype=RUN\r`
      // types RUN once BASIC is up, so a visitor gets a game rather than an OK
      // prompt and a puzzle. Drop `autotype` if your program starts itself.
      const machine = new URL('https://acwright.github.io/6502-EMULATOR/v2/embed.html')
      machine.searchParams.set('prg', program)
      machine.searchParams.set('autostart', '1')
      machine.searchParams.set('autotype', 'RUN\r')

      document.getElementById('machine').src = machine
    </script>
  </body>
</html>

Put your .prg in the zip beside it, name it game.prg or change the line that names it, and you're done. This works on itch because its CDN sends the Access-Control-Allow-Origin header, which is what lets the emulator fetch a file from a site that isn't its own. Not every host does — if the machine boots but your program never appears, that header is the first thing to check.

Test it on itch, not by double-clicking

Opening index.html from your own disk will boot the emulator but won't load your program: the frame fetches over https: only, so a file:// address is refused. It isn't broken. Upload it as a draft project — drafts are private — and play it there, or carry the program in the URL as below.

On a blog, a forum, or anywhere you can't upload a file

Put the program in the link. Every parameter that names a file has a twin ending in 64 that takes the bytes themselves, base64-encoded:

sh
base64 < game.prg | tr '+/' '-_' | tr -d '=\n'

Paste the result in place of the whole prg=…:

…/embed.html?prg64=AQgLCAoAmSJIRUxMTyIAAAA&autostart=1&autotype=RUN%5Cr

Now the link is the game. Nothing is fetched, so it works from anywhere — a comment box, a gist, a file on your desktop. The limit is URL length: a few tens of kilobytes is comfortable, a card image is not.

It's what every machine on this site uses, which is why the pages still run with no network.

On a phone

Somebody will open your link on a phone, and a phone has no keyboard to give the machine. The frame works that out for itself: a touch screen with no mouse gets the board's keyboard drawn under the picture, a laptop gets the picture and nothing else, and a in the control bar changes its mind either way.

keyboard=1 opens it whatever the device, keyboard=0 never opens it, and the default is keyboard=auto, which is the checking above. controls=none has no bar to put the toggle in, so there keyboard=1 is the only way to have one.

Leave it room. The keyboard takes about a third of the frame's height, so a page that will be read on a phone wants at least 320 × 380 — give the frame the width of the column it sits in and let the height follow the 640 × 520 shape. Turned sideways the keys move beside the picture rather than under it, where a short wide frame has the room to spare.

Sound

Embeds start silent, and there's nothing you can pass to change that. Browsers refuse to let a framed page make noise until somebody has clicked inside it — muted=0 only means "unmute at the first opportunity", and the first opportunity is that click.

So if your game opens on a tune, the tune starts when the player clicks, not when the page loads. Worth a line of "click to start" on the page around it.

A game that loads or saves

cf= attaches a CompactFlash image — the kind cffs builds — so DIR, LOAD and SAVE work inside the frame. It's a fetched file like prg, so it wants the same full https://… address, for the same reason.

Anything saved is thrown away when the page closes, unless you add persist=1, which keeps the card and the clock card's memory in the browser's storage between visits. That's what you want for a game with a high-score table.

Be deliberate about it, though: that storage is one record per site, shared with everything else on the same address — including the full emulator. This guide never uses it for exactly that reason. On your own page, where yours is the only machine, it's the right switch.

Fullscreen

allow="fullscreen" on the frame. Leave it off and the button is still there, the browser refuses it, and the emulator tells the player why — which is a worse first impression than not offering it.

Where the .prg comes from

Whichever way you got here:

  • SAVE "GAME" on the machine, then copy it off the card.
  • bastok turns a BASIC listing into one.
  • make in either project template does it for assembly.

Driving the machine from the page around it

Everything above happens as the frame loads. It can also be driven afterwards — a Run this button next to a listing, a reset button, keystrokes sent on demand — over postMessage. That's a web developer's job rather than a BASIC programmer's, and it's documented in EMBEDDING.md along with every parameter above.

Running it from the command line

The desktop app installs a 6502 command (Settings → Command Line → Install). It's how you'd fold the emulator into a build:

6502 run --headless mygame.prg

--headless runs with no window at all, wired to your terminal. Useful flags:

FlagWhat it does
--cf disk.imgAttach a card image
--console videoUse the video screen instead of the serial console
--freq 2Run the CPU at 2 MHz
--timeout 30sStop after a while, whatever happens

There's a debugger in there too — breakpoints, single-stepping, memory watching, screenshots — which the assembly chapters of this guide use heavily.

Written for BIOS v1.6. Released under the MIT License.