An open API service indexing awesome lists of open source software.

https://github.com/ncw/gbpi

This calculates the digits of π on a 1989 Nintendo Game Boy, using nothing but its 8-bit Sharp LR35902 CPU and a hand-written spigot algorithm in Z80-ish assembly.
https://github.com/ncw/gbpi

gameboy pi

Last synced: 6 days ago
JSON representation

This calculates the digits of π on a 1989 Nintendo Game Boy, using nothing but its 8-bit Sharp LR35902 CPU and a hand-written spigot algorithm in Z80-ish assembly.

Awesome Lists containing this project

README

          

# gbpi - Pi on the Game Boy

This calculates the digits of π on a 1989 Nintendo Game Boy, using nothing but
its 8-bit Sharp LR35902 CPU and a hand-written spigot algorithm in Z80-ish
assembly.

![Screenshot of gbpi calculating Pi](pi.png)

## What it does

`gbpi` computes the decimal digits of π one screenful at a time and prints them
to the Game Boy's LCD as they are produced. It uses the **Rabinowitz and Wagon
spigot algorithm**, which emits digits of π from the start onwards using only
integer arithmetic on a large array. No floating point is needed, and there is
no need for high precision arithmetic.

The CPU has no multiply or divide instructions, so the project includes
hand-written 16×16→32 bit multiply (`mmul`), 32÷16 bit divide (`mdiv`, `mdiv32`)
and "multiply by 10000" (`mul10k`) routines, each tuned cycle by cycle for
speed.

A ~100 Hz timer interrupt counts hundredths of a second so the ROM can report
how long the calculation took once it finishes.

## The two versions

There are two pre-built ROMs in the repository, charting the optimisation work:

| ROM | Algorithm | Speed (1000 digits) |
| --- | --- | --- |
| [gbpi-v1-digit-at-a-time.gb](./gbpi-v1-digit-at-a-time.gb) | base 10 - one decimal digit per array element | ~1400 s |
| [gbpi-v2-4-digits-at-a-time.gb](./gbpi-v2-4-digits-at-a-time.gb) | base 10000 - four decimal digits per array element | **350.43 s** |

Switching from base 10 to base 10000 packs four decimal digits into every array
element, giving roughly a 4× speed-up by processing four digits per pass of the
inner multiply/divide loop. The current source builds the base-10000 version.

The number of digits and the working array length are set at the top of
[`src/main/pi.asm`](src/main/pi.asm):

```asm
DEF DIGITS = 1000
DEF LENGTH = 3330
```

## Running it

The quickest way to try it is to load one of the pre-built `.gb` files into a
Game Boy emulator, for example [Emulicious](https://emulicious.net/),
[SameBoy](https://sameboy.github.io/) or [BGB](https://bgb.bircd.org/):

```sh
emulicious gbpi-v2-4-digits-at-a-time.gb
```

The ROMs should also run on real hardware via a flash cart.

## Building from source

The project is built with [RGBDS](https://rgbds.gbdev.io/), the Game Boy
assembler toolchain (`rgbasm`, `rgblink`, `rgbfix`, `rgbgfx`). Install RGBDS,
then run:

```sh
make # build dist/gbpi.gb
make run # build and launch in Emulicious
make clean # remove generated files
```

The build assembles every `.asm` file under `src/main/`, converts the font PNG
under `src/resources/backgrounds/` into Game Boy tile data with `rgbgfx`, links
the objects and runs `rgbfix` to patch the ROM header.

## Source layout

| File | Purpose |
| --- | --- |
| `src/main/main.asm` | Entry point, interrupt vectors, intro text and the ~100 Hz timer |
| `src/main/pi.asm` | The π spigot algorithm plus the multiply/divide maths routines |
| `src/main/display.asm` | LCD control and background clearing |
| `src/main/text.asm` | Text output, scrolling and the font tile loader |
| `src/main/memory.asm` | A small memory-copy helper |
| `src/main/charmap.inc` | Maps ASCII characters to font tile indices |
| `src/main/hardware.inc` | Game Boy hardware register definitions |
| `src/resources/backgrounds/bbc-mode1.png` | The font - a BBC Micro MODE 1 typeface, for old times' sake |

## How the algorithm works

The algorithm comes from the paper:

> Rabinowitz, Stanley; Wagon, Stan (1995). "A Spigot Algorithm for the Digits
> of Pi". *American Mathematical Monthly*. **102** (3): 195–203.
> [doi:10.2307/2975006](https://doi.org/10.2307/2975006).
> [JSTOR 2975006](https://www.jstor.org/stable/2975006).

### π as a number in a strange base

The starting point is this series which can easily be derived from the well known
[Leibniz formula for π](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80):

```
π = 2 + 1/3·(2 + 2/5·(2 + 3/7·(2 + 4/9·(… ))))
```

Read this as a positional number whose "digits" are all `2`, but where each
position has a *different* base. In ordinary base 10 the place values are
1, 1/10, 1/100, … and every position uses the radix 10. Here the place values
are the nested products of the fractions `i/(2i+1)`, so position `i` effectively
uses the **mixed radix** `(2i+1)/i`. In that mixed-radix system π is simply

```
π = (2 ; 2, 2, 2, 2, …)
```

The whole algorithm is therefore a **base conversion**: it takes π written in
this `(2i+1)/i` mixed radix and converts it into ordinary base 10 (or, in the
fast version, base 10000), one block of output digits at a time.

### Converting one block of digits

Converting a number to base *B* is the familiar "multiply by *B* and take the
overflow" trick. To get the next base-10 digit of a fraction you multiply by 10;
the integer part that pops out above the point is the next digit, and you keep
the fractional remainder and repeat. This program does exactly that, but to the
*entire mixed-radix number at once*:

1. **Multiply the whole array by the base** (`B = 10`, or `10000` for four
digits at a time). Each element of the `digit` array holds the value for one
mixed-radix position; all of them are multiplied by `B`.
2. **Normalise from the low end up**, position by position. At position `i` the
value can no longer be ≥ its radix, so it is divided by the denominator
`2·i + 1`: the remainder stays in place (it becomes the new digit `2` for the
next round, scaled), and the quotient is multiplied by the numerator `i` and
**carried** down into position `i-1`. This is just "carrying" during base
conversion, except each position carries with its own weird radix.
3. **The carry out of the front** of the array is the integer part produced by
multiplying by `B` - i.e. the next base-10 (or base-10000) block of π. It is
printed to the screen.

In the source this is the `.outer` loop in [`src/main/pi.asm`](src/main/pi.asm):
the `digit` array is initialised to `2`, `mmul`/`mul10k` do the "multiply by the
base" step, and `mdiv`/`mdiv32` do the divide-and-carry normalisation that walks
the array. The leading carry is assembled in `prev`/`carry` and emitted with
`printdigit` / `print4digits`.

A subtlety from the paper: the last digit of a block can occasionally need to be
revised by a carry from the *next* block (the classic "`…999999`" or "`…000000`"
rollover). The code holds the previous block back in `prev` and only commits it
once the following block confirms there was no carry, which is the `.prevloop` /
`.prevok` logic.

Finally, later digits of π need progressively less precision, so the working
array length is trimmed a little on every pass (about 13 bits ≈ 4 decimal digits
per pass), shrinking the work as the calculation proceeds - a small extra
speed-up beyond the base-10000 change.

## License

[MIT](LICENSE) © Nick Craig-Wood