├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── ci-micropython.yml │ ├── ci-pico-sdk.yml │ └── ci-test.yml ├── .gitignore ├── .husky └── pre-commit ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── build-scripts └── dist-package-json.js ├── debug └── gdbdiff.ts ├── demo ├── bootrom.ts ├── emulator-run.ts ├── intelhex.ts ├── load-flash.ts └── micropython-run.ts ├── jest.config.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── clock │ ├── clock.ts │ ├── mock-clock.ts │ └── simulation-clock.ts ├── cortex-m0-core.ts ├── gdb │ ├── gdb-connection.ts │ ├── gdb-server.ts │ ├── gdb-target.ts │ ├── gdb-tcp-server.ts │ └── gdb-utils.ts ├── gpio-pin.ts ├── index.ts ├── instructions.spec.ts ├── interpolator.ts ├── irq.ts ├── peripherals │ ├── adc.ts │ ├── busctrl.ts │ ├── clocks.ts │ ├── dma.spec.ts │ ├── dma.ts │ ├── i2c.ts │ ├── io.ts │ ├── pads.ts │ ├── peripheral.ts │ ├── pio.spec.ts │ ├── pio.ts │ ├── ppb.ts │ ├── pwm.ts │ ├── reset.ts │ ├── rtc.ts │ ├── spi.ts │ ├── ssi.ts │ ├── syscfg.ts │ ├── sysinfo.ts │ ├── tbman.ts │ ├── timer.spec.ts │ ├── timer.ts │ ├── uart.spec.ts │ ├── uart.ts │ ├── usb.ts │ └── watchdog.ts ├── rp2040.spec.ts ├── rp2040.ts ├── simulator.ts ├── sio.spec.ts ├── sio.ts ├── usb │ ├── cdc.spec.ts │ ├── cdc.ts │ ├── interfaces.ts │ └── setup.ts └── utils │ ├── assembler.spec.ts │ ├── assembler.ts │ ├── bit.ts │ ├── fifo.spec.ts │ ├── fifo.ts │ ├── logging.ts │ ├── pio-assembler.spec.ts │ ├── pio-assembler.ts │ ├── time.spec.ts │ ├── time.ts │ └── timer32.ts ├── test-utils ├── create-test-driver.ts ├── gdbclient.ts ├── test-driver-gdb.ts ├── test-driver-rp2040.ts └── test-driver.ts ├── test ├── micropython-spi-test.ts ├── micropython │ ├── main-spi.py │ └── main.py ├── mklittlefs.py └── requirements.txt ├── tsconfig.cjs.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "rules": { 11 | "@typescript-eslint/no-unused-vars": 1, 12 | "@typescript-eslint/explicit-module-boundary-types": 0, 13 | "@typescript-eslint/no-inferrable-types": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/ci-micropython.yml: -------------------------------------------------------------------------------- 1 | name: Test MicroPython Releases 2 | on: 3 | push: 4 | pull_request: 5 | branches: [main] 6 | jobs: 7 | test-micropython: 8 | name: Micropython ${{ matrix.micropython_version }} 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | micropython_version: 13 | - 20210618-v1.16 14 | - 20210902-v1.17 15 | - 20220117-v1.18 16 | - 20220618-v1.19.1 17 | - 20230426-v1.20.0 18 | - 20231005-v1.21.0 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-python@v5 22 | with: 23 | python-version: '3.10' 24 | - name: Install packages 25 | run: npm ci 26 | - name: Install Python requirements 27 | run: cd test && pip install -r requirements.txt 28 | - name: Download Micropython 29 | run: curl -o micropython.uf2 "https://micropython.org/resources/firmware/RPI_PICO-${VERSION}.uf2" 30 | env: 31 | VERSION: ${{ matrix.micropython_version }} 32 | - name: Create filesystem 33 | run: python test/mklittlefs.py littlefs.img test/micropython/main.py 34 | - name: Test Micropython 35 | run: timeout 10 npm run start:micropython -- --image micropython.uf2 --expect-text "Hello, MicroPython!" 36 | - name: Create SPI test filesystem 37 | run: python test/mklittlefs.py littlefs-spi.img test/micropython/main-spi.py 38 | - name: Test Micropython SPI 39 | run: timeout 10 npm run test:micropython-spi -- "hello world" "h" "0123456789abcdef0123456789abcdef0123456789abcdef" 40 | -------------------------------------------------------------------------------- /.github/workflows/ci-pico-sdk.yml: -------------------------------------------------------------------------------- 1 | name: Test Pi Pico SDK 2 | on: 3 | push: 4 | pull_request: 5 | branches: [main] 6 | jobs: 7 | test-pico-sdk: 8 | name: Test Pi Pico SDK 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | sdk_version: 13 | - 1.2.0 14 | - 1.3.1 15 | - 1.4.0 16 | - 1.5.1 17 | - 2.0.0 18 | - 2.1.1 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Cache Pico SDK 22 | id: cache-pico-sdk 23 | uses: actions/cache@v4 24 | with: 25 | path: ~/pico 26 | key: ${{ runner.os }}-pico-sdk-${{ matrix.sdk_version }} 27 | - name: Set up the toolchain 28 | if: steps.cache-pico-sdk.outputs.cache-hit != 'true' 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential 32 | - name: Set up the Pi Pico SDK and examples 33 | if: steps.cache-pico-sdk.outputs.cache-hit != 'true' 34 | run: | 35 | mkdir ~/pico 36 | cd ~/pico 37 | git clone https://github.com/raspberrypi/pico-sdk.git --depth 1 --branch ${SDK_VERSION} 38 | cd pico-sdk && git submodule update --init && cd .. 39 | git clone https://github.com/raspberrypi/pico-examples.git --depth 1 --branch "sdk-${SDK_VERSION}" 40 | cd pico-examples && mkdir build && cd build && cmake .. && make -C hello_world/usb -j4 41 | env: 42 | SDK_VERSION: ${{ matrix.sdk_version }} 43 | PICO_SDK_PATH: ~/pico/pico-sdk 44 | - name: Install NPM packages 45 | run: npm ci 46 | - name: Test "Hello World" example 47 | run: timeout 10 npm run start:micropython -- --image ~/pico/pico-examples/build/hello_world/usb/hello_usb.uf2 --expect-text "Hello, world!" 48 | -------------------------------------------------------------------------------- /.github/workflows/ci-test.yml: -------------------------------------------------------------------------------- 1 | name: Test and Lint 2 | on: 3 | push: 4 | pull_request: 5 | branches: [main] 6 | jobs: 7 | test: 8 | name: Test and Lint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Install packages 13 | run: npm ci 14 | - name: Prettier 15 | run: npm run format:check 16 | - name: Lint 17 | run: npm run lint 18 | - name: Test code 19 | run: npm test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.hex 4 | *.elf 5 | *.uf2 6 | *.img 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Emulator Demo Project", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeExecutable": "node", 12 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], 13 | 14 | "args": ["${workspaceFolder}/demo/emulator-run.ts"], 15 | 16 | "cwd": "${workspaceRoot}", 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "skipFiles": ["/**", "node_modules/**"] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Uri Shaked 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rp2040js 2 | 3 | Raspberry Pi Pico Emulator for the [Wokwi Simulation Platform](https://wokwi.com). It blinks, runs Arduino code, and even the MicroPython REPL! 4 | 5 | ## Online examples 6 | 7 | If you are just looking to play around with the Raspberry Pi Pico Simulator, check out the Wokwi Simulator: 8 | 9 | - [Raspberry Pi Pico Traffic Light](https://wokwi.com/arduino/projects/297322571959894536) 10 | - [LCD1602 Hello World](https://wokwi.com/arduino/projects/297323005822894602) 11 | - [MicroPython Blink](https://wokwi.com/arduino/projects/300504213470839309) 12 | - [MicroPython 7-Segment Counter](https://wokwi.com/arduino/projects/300210834979684872) 13 | 14 | For more information, take a look at the [wokwi-pi-pico docs](https://docs.wokwi.com/parts/wokwi-pi-pico) and the [Pi Pico MicroPython Guide](https://docs.wokwi.com/guides/micropython). 15 | 16 | If you want to develop your own application using the Raspberry Pi Pico simulator, the following examples may be helpful: 17 | 18 | - [Blink LEDs with RP2040js, from scratch](https://stackblitz.com/edit/rp2040js-blink?file=index.ts) - Press "Run" and patiently wait for the code to compile ;-) 19 | 20 | ## Run the demo project 21 | 22 | ### Native code 23 | 24 | You'd need to get `hello_uart.hex` by building it from the [pico-examples repo](https://github.com/raspberrypi/pico-examples/tree/master/uart/hello_uart), then copy it to the rp2040js root directory and run: 25 | 26 | ``` 27 | npm install 28 | npm start 29 | ``` 30 | 31 | You can also specify the path to the image on the command line and/or load an UF2 image: 32 | 33 | ```sh 34 | npm run start -- --image ./my-pico-project.uf2 35 | ``` 36 | 37 | A GDB server will be available on port 3333, and the data written to UART0 will be printed 38 | to the console. 39 | 40 | ### MicroPython code 41 | 42 | To run the MicroPython demo, first download [RPI_PICO-20230426-v1.20.0.uf2](https://micropython.org/resources/firmware/RPI_PICO-20230426-v1.20.0.uf2), place it in the rp2040js root directory, then run: 43 | 44 | ``` 45 | npm install 46 | npm run start:micropython 47 | ``` 48 | 49 | and enjoy the MicroPython REPL! Quit the REPL with Ctrl+X. A different MicroPython UF2 image can be loaded by supplying the `--image` option: 50 | 51 | ``` 52 | npm run start:micropython -- --image=my_image.uf2 53 | ``` 54 | 55 | A GDB server on port 3333 can be enabled by specifying the `--gdb` flag: 56 | 57 | ``` 58 | npm run start:micropython -- --gdb 59 | ``` 60 | 61 | For using the MicroPython demo code in tests, the `--expect-text` can come handy: it will look for the given text in the serial output and exit with code 0 if found, or 1 if not found. You can find an example in [the MicroPython CI test](./github/workflows/ci-micropython.yml). 62 | 63 | #### Filesystem support 64 | 65 | With MicroPython, you can use the filesystem on the Pico. This becomes useful as more than one script file is used in your code. Just put a [LittleFS](https://github.com/littlefs-project/littlefs) formatted filesystem image called `littlefs.img` into the rp2040js root directory, and your `main.py` will be automatically started from there. 66 | 67 | A simple way to create a suitable LittleFS image containing your script files is outlined in [create_littlefs_image.py](https://github.com/tomods/GrinderController/blob/358ad3e0f795d8cc0bdf4f21bb35f806871d433f/tools/create_littlefs_image.py). 68 | So, using [littlefs-python](https://pypi.org/project/littlefs-python/), you can do the following: 69 | 70 | ```python 71 | from littlefs import LittleFS 72 | files = ['your.py', 'files.py', 'here.py', 'main.py'] 73 | output_image = 'output/littlefs.img' # symlinked/copied to rp2040js root directory 74 | lfs = LittleFS(block_size=4096, block_count=352, prog_size=256) 75 | for filename in files: 76 | with open(filename, 'rb') as src_file, lfs.open(filename, 'w') as lfs_file: 77 | lfs_file.write(src_file.read()) 78 | with open(output_image, 'wb') as fh: 79 | fh.write(lfs.context.buffer) 80 | ``` 81 | 82 | Other ways of creating LittleFS images can be found [here](https://github.com/wokwi/littlefs-wasm) or [here](https://github.com/littlefs-project/littlefs#related-projects). 83 | 84 | Currently, the filesystem is not writeable, as the SSI peripheral required for flash writing is not implemented yet. If you're interested in hacking, see the discussion in https://github.com/wokwi/rp2040js/issues/88 for a workaround. 85 | 86 | ### CircuitPython code 87 | 88 | To run the CircuitPython demo, you can follow the directions above for MicroPython, except download [adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.2.uf2](https://adafruit-circuit-python.s3.amazonaws.com/bin/raspberry_pi_pico/en_US/adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.2.uf2) instead of the MicroPython UF2 file. Place it in the rp2040js root directory, then run: 89 | 90 | ``` 91 | npm install 92 | npm run start:circuitpython 93 | ``` 94 | 95 | and start the CircuitPython REPL! The rest of the experience is the same as the MicroPython demo (Ctrl+X to exit, using the `--image` and 96 | `--gdb` options, etc). 97 | 98 | #### Filesystem support 99 | 100 | For CircuitPython, you can create a FAT12 filesystem in Linux using the `truncate` and `mkfs.vfat` utilities: 101 | 102 | ```shell 103 | truncate fat12.img -s 1M # make the image file 104 | mkfs.vfat -F12 -S512 fat12.img # create the FAT12 filesystem 105 | ``` 106 | 107 | You can then mount the filesystem image and add files to it: 108 | 109 | ```shell 110 | mkdir fat12 # create the mounting folder if needed 111 | sudo mount -o loop fat12.img fat12/ # mount the filesystem to the folder 112 | sudo cp code.py fat12/ # copy code.py to the filesystem 113 | sudo umount fat12/ # unmount the filesystem 114 | ``` 115 | 116 | While CircuitPython does not typically use a writeable filesystem, note that this functionality is unavailable (see MicroPython filesystem 117 | support section for more details). 118 | 119 | ## Learn more 120 | 121 | - [Live-coding stream playlist](https://www.youtube.com/playlist?list=PLLomdjsHtJTxT-vdJHwa3z62dFXZnzYBm) 122 | - [Hackaday project page](https://hackaday.io/project/177082-raspberry-pi-pico-emulator) 123 | 124 | ## License 125 | 126 | Released under the MIT licence. Copyright (c) 2021-2023, Uri Shaked. 127 | -------------------------------------------------------------------------------- /build-scripts/dist-package-json.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import * as url from 'url'; 4 | 5 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 6 | 7 | fs.writeFileSync( 8 | path.join(__dirname, '../dist/cjs/package.json'), 9 | JSON.stringify({ 10 | type: 'commonjs', 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /debug/gdbdiff.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * gdbdiff - helps spotting bugs in instruction implementation. 3 | * 4 | * gdbdiff runs the same piece of code in silicone and in the emulator, instruction-by-instruction. 5 | * It looks for differences in register values after executing each instruction. 6 | * 7 | * Copyright (C) 2021, Uri Shaked. 8 | **/ 9 | 10 | import { GDBClient, dumpUint32, registerNames } from '../test-utils/gdbclient.js'; 11 | 12 | function printComparedRegisters( 13 | registers: Uint32Array, 14 | emulator: Uint32Array, 15 | silicone: Uint32Array, 16 | ) { 17 | for (let i = 0; i < registerNames.length; i++) { 18 | let modified = ' '; 19 | if (emulator[i] !== silicone[i]) { 20 | modified = '*'; 21 | } 22 | console.log( 23 | registerNames[i] + modified, 24 | '\t\t0x' + 25 | dumpUint32(registers[i]) + 26 | '\t0x' + 27 | dumpUint32(emulator[i]) + 28 | '\t0x' + 29 | dumpUint32(silicone[i]), 30 | ); 31 | if (registerNames[i] === 'xPSR' && modified === '*') { 32 | console.log( 33 | 'Flags\t\t', 34 | printFlags(registers[i]), 35 | '\t', 36 | printFlags(emulator[i]), 37 | '\t', 38 | printFlags(silicone[i]), 39 | ); 40 | } 41 | } 42 | } 43 | 44 | function printFlags(xpsr: number) { 45 | const negative = xpsr & 0x80000000 ? 'N' : '-'; 46 | const zero = xpsr & 0x40000000 ? 'Z' : '-'; 47 | const carry = xpsr & 0x20000000 ? 'C' : '-'; 48 | const overflow = xpsr & 0x10000000 ? 'O' : '-'; 49 | return `[${negative}${zero}${carry}${overflow}]`; 50 | } 51 | 52 | async function compareFixRegisters( 53 | emulator: Uint32Array, 54 | silicone: Uint32Array, 55 | toFixClient: GDBClient, 56 | ) { 57 | let result = true; 58 | for (let i = 0; i < emulator.length; i++) { 59 | if (emulator[i] !== silicone[i]) { 60 | await toFixClient.writeRegister(i, silicone[i]); 61 | result = false; 62 | } 63 | } 64 | return result; 65 | } 66 | 67 | async function main() { 68 | const emulatorClient = new GDBClient(); 69 | const siliconeClient = new GDBClient(); 70 | await emulatorClient.connect('localhost', 3334); 71 | await siliconeClient.connect('localhost', 3333); 72 | // Disable interrupts 73 | await emulatorClient.writeRegister(19, 1, 8); 74 | await siliconeClient.writeRegister(19, 1, 8); 75 | // Start diffing 76 | let prevRegSet = await siliconeClient.readRegisters(); 77 | for (let counter = 1; ; counter++) { 78 | const emulatorRegSet = await emulatorClient.readRegisters(); 79 | const siliconeRegSet = await siliconeClient.readRegisters(); 80 | 81 | if (!(await compareFixRegisters(emulatorRegSet, siliconeRegSet, emulatorClient))) { 82 | console.log('\n\nMismatch after ', counter, ' compared instructions'); 83 | console.log('\nRegister\tStartValue\tEmulator\tSilicone'); 84 | printComparedRegisters(prevRegSet, emulatorRegSet, siliconeRegSet); 85 | } 86 | prevRegSet = emulatorRegSet; 87 | await emulatorClient.singleStep(); 88 | await siliconeClient.singleStep(); 89 | if (counter % 200 === 0) { 90 | console.log(`Successfully compared ${counter} instructions`); 91 | } 92 | } 93 | } 94 | 95 | main().catch(console.error); 96 | -------------------------------------------------------------------------------- /demo/emulator-run.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js'; 3 | import { Simulator } from '../src/simulator.js'; 4 | import { bootromB1 } from './bootrom.js'; 5 | import { loadHex } from './intelhex.js'; 6 | import { loadUF2 } from './load-flash.js'; 7 | import minimist from 'minimist'; 8 | 9 | const args = minimist(process.argv.slice(2), { 10 | string: [ 11 | 'image', // An image to load, hex and UF2 are supported 12 | ], 13 | }); 14 | 15 | const simulator = new Simulator(); 16 | const mcu = simulator.rp2040; 17 | mcu.loadBootrom(bootromB1); 18 | 19 | const imageName = args.image ?? 'hello_uart.hex'; 20 | 21 | // Check the extension of the file 22 | const extension = imageName.split('.').pop(); 23 | if (extension === 'hex') { 24 | // Create an array with the compiled code of blink 25 | // Execute the instructions from this array, one by one. 26 | const hex = fs.readFileSync(imageName, 'utf-8'); 27 | 28 | console.log(`Loading hex image ${imageName}`); 29 | loadHex(hex, mcu.flash, 0x10000000); 30 | } else if (extension === 'uf2') { 31 | console.log(`Loading uf2 image ${imageName}`); 32 | loadUF2(imageName, mcu); 33 | } else { 34 | console.log(`Unsupported file type: ${extension}`); 35 | process.exit(1); 36 | } 37 | 38 | const gdbServer = new GDBTCPServer(simulator, 3333); 39 | console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`); 40 | 41 | mcu.uart[0].onByte = (value) => { 42 | process.stdout.write(new Uint8Array([value])); 43 | }; 44 | 45 | simulator.rp2040.core.PC = 0x10000000; 46 | simulator.execute(); 47 | -------------------------------------------------------------------------------- /demo/intelhex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimal Intel HEX loader 3 | * Part of AVR8js 4 | * 5 | * Copyright (C) 2019, Uri Shaked 6 | */ 7 | 8 | export function loadHex(source: string, target: Uint8Array, baseAddress: number = 0) { 9 | let highAddressBytes = 0; 10 | for (const line of source.split('\n')) { 11 | if (line[0] === ':' && line.substr(7, 2) === '04') { 12 | highAddressBytes = parseInt(line.substr(9, 4), 16); 13 | } 14 | if (line[0] === ':' && line.substr(7, 2) === '00') { 15 | const bytes = parseInt(line.substr(1, 2), 16); 16 | const addr = ((highAddressBytes << 16) | parseInt(line.substr(3, 4), 16)) - baseAddress; 17 | for (let i = 0; i < bytes; i++) { 18 | target[addr + i] = parseInt(line.substr(9 + i * 2, 2), 16); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/load-flash.ts: -------------------------------------------------------------------------------- 1 | import { closeSync, openSync, readSync } from 'fs'; 2 | import { decodeBlock } from 'uf2'; 3 | import { RP2040 } from '../src/index.js'; 4 | import { FLASH_START_ADDRESS } from '../src/rp2040.js'; 5 | 6 | const MICROPYTHON_FS_FLASH_START = 0xa0000; 7 | const MICROPYTHON_FS_BLOCKSIZE = 4096; 8 | const MICROPYTHON_FS_BLOCKCOUNT = 352; 9 | 10 | const CIRCUITPYTHON_FS_FLASH_START = 0x100000; 11 | const CIRCUITPYTHON_FS_BLOCKSIZE = 4096; 12 | const CIRCUITPYTHON_FS_BLOCKCOUNT = 512; 13 | 14 | function loadFlashImage(filename: string, rp2040: RP2040, flashStart: number, blockSize: number) { 15 | const file = openSync(filename, 'r'); 16 | const buffer = new Uint8Array(blockSize); 17 | let flashAddress = flashStart; 18 | while (readSync(file, buffer) === buffer.length) { 19 | rp2040.flash.set(buffer, flashAddress); 20 | flashAddress += buffer.length; 21 | } 22 | closeSync(file); 23 | } 24 | 25 | export function loadMicropythonFlashImage(filename: string, rp2040: RP2040) { 26 | loadFlashImage(filename, rp2040, MICROPYTHON_FS_FLASH_START, MICROPYTHON_FS_BLOCKSIZE); 27 | } 28 | 29 | export function loadCircuitpythonFlashImage(filename: string, rp2040: RP2040) { 30 | loadFlashImage(filename, rp2040, CIRCUITPYTHON_FS_FLASH_START, CIRCUITPYTHON_FS_BLOCKSIZE); 31 | } 32 | 33 | export function loadUF2(filename: string, rp2040: RP2040) { 34 | const file = openSync(filename, 'r'); 35 | const buffer = new Uint8Array(512); 36 | while (readSync(file, buffer) === buffer.length) { 37 | const block = decodeBlock(buffer); 38 | const { flashAddress, payload } = block; 39 | rp2040.flash.set(payload, flashAddress - FLASH_START_ADDRESS); 40 | } 41 | closeSync(file); 42 | } 43 | -------------------------------------------------------------------------------- /demo/micropython-run.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import minimist from 'minimist'; 3 | import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js'; 4 | import { Simulator } from '../src/simulator.js'; 5 | import { USBCDC } from '../src/usb/cdc.js'; 6 | import { ConsoleLogger, LogLevel } from '../src/utils/logging.js'; 7 | import { bootromB1 } from './bootrom.js'; 8 | import { loadCircuitpythonFlashImage, loadMicropythonFlashImage, loadUF2 } from './load-flash.js'; 9 | 10 | const args = minimist(process.argv.slice(2), { 11 | string: [ 12 | 'image', // UF2 image to load; defaults to "RPI_PICO-20230426-v1.20.0.uf2" 13 | 'expect-text', // Text to expect on the serial console, process will exit with code 0 if found 14 | ], 15 | boolean: [ 16 | 'gdb', // start GDB server on 3333 17 | 'circuitpython', // use CircuitPython instead of MicroPython 18 | ], 19 | }); 20 | const expectText = args['expect-text']; 21 | 22 | const simulator = new Simulator(); 23 | const mcu = simulator.rp2040; 24 | mcu.loadBootrom(bootromB1); 25 | mcu.logger = new ConsoleLogger(LogLevel.Error); 26 | 27 | let imageName: string; 28 | if (!args.circuitpython) { 29 | imageName = args.image ?? 'RPI_PICO-20230426-v1.20.0.uf2'; 30 | } else { 31 | imageName = args.image ?? 'adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.2.uf2'; 32 | } 33 | console.log(`Loading uf2 image ${imageName}`); 34 | loadUF2(imageName, mcu); 35 | 36 | if (fs.existsSync('littlefs.img') && !args.circuitpython) { 37 | console.log(`Loading uf2 image littlefs.img`); 38 | loadMicropythonFlashImage('littlefs.img', mcu); 39 | } else if (fs.existsSync('fat12.img') && args.circuitpython) { 40 | loadCircuitpythonFlashImage('fat12.img', mcu); 41 | // Instead of reading from file, it would also be possible to generate the LittleFS image on-the-fly here, e.g. using 42 | // https://github.com/wokwi/littlefs-wasm or https://github.com/littlefs-project/littlefs-js 43 | } 44 | 45 | if (args.gdb) { 46 | const gdbServer = new GDBTCPServer(simulator, 3333); 47 | console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`); 48 | } 49 | 50 | const cdc = new USBCDC(mcu.usbCtrl); 51 | cdc.onDeviceConnected = () => { 52 | if (!args.circuitpython) { 53 | // We send a newline so the user sees the MicroPython prompt 54 | cdc.sendSerialByte('\r'.charCodeAt(0)); 55 | cdc.sendSerialByte('\n'.charCodeAt(0)); 56 | } else { 57 | cdc.sendSerialByte(3); 58 | } 59 | }; 60 | 61 | let currentLine = ''; 62 | cdc.onSerialData = (value) => { 63 | process.stdout.write(value); 64 | 65 | for (const byte of value) { 66 | const char = String.fromCharCode(byte); 67 | if (char === '\n') { 68 | if (expectText && currentLine.includes(expectText)) { 69 | console.log(`Expected text found: "${expectText}"`); 70 | console.log('TEST PASSED.'); 71 | process.exit(0); 72 | } 73 | currentLine = ''; 74 | } else { 75 | currentLine += char; 76 | } 77 | } 78 | }; 79 | 80 | if (process.stdin.isTTY) { 81 | process.stdin.setRawMode(true); 82 | } 83 | process.stdin.on('data', (chunk) => { 84 | // 24 is Ctrl+X 85 | if (chunk[0] === 24) { 86 | process.exit(0); 87 | } 88 | for (const byte of chunk) { 89 | cdc.sendSerialByte(byte); 90 | } 91 | }); 92 | 93 | simulator.rp2040.core.PC = 0x10000000; 94 | simulator.execute(); 95 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rp2040js", 3 | "version": "1.1.1", 4 | "description": "Raspberry Pi Pico (RP2040) Emulator", 5 | "repository": "https://github.com/wokwi/rp2040js", 6 | "keywords": [ 7 | "rp2040", 8 | "raspberry pi pico", 9 | "emulator" 10 | ], 11 | "files": [ 12 | "dist" 13 | ], 14 | "author": "Uri Shaked ", 15 | "license": "MIT", 16 | "type": "module", 17 | "main": "./dist/cjs/index.js", 18 | "module": "./dist/esm/index.js", 19 | "typings": "./dist/cjs/index.d.ts", 20 | "engines": { 21 | "node": ">=18.0.0" 22 | }, 23 | "exports": { 24 | ".": { 25 | "import": { 26 | "types": "./dist/esm/index.d.ts", 27 | "default": "./dist/esm/index.js" 28 | }, 29 | "require": { 30 | "types": "./dist/cjs/index.d.ts", 31 | "default": "./dist/cjs/index.js" 32 | } 33 | }, 34 | "./gdb-tcp-server": { 35 | "import": { 36 | "types": "./dist/esm/gdb/gdb-tcp-server.d.ts", 37 | "default": "./dist/esm/gdb/gdb-tcp-server.js" 38 | }, 39 | "require": { 40 | "types": "./dist/cjs/gdb/gdb-tcp-server.d.ts", 41 | "default": "./dist/cjs/gdb/gdb-tcp-server.js" 42 | } 43 | } 44 | }, 45 | "scripts": { 46 | "build": "rimraf dist && tsc && tsc -p tsconfig.cjs.json && node build-scripts/dist-package-json", 47 | "prepublish": "npm run build", 48 | "prepare": "husky install", 49 | "format:check": "prettier --check **/*.{ts,js} !**/dist/** !**/node_modules/**", 50 | "lint": "eslint . --ext .ts", 51 | "start": "tsx demo/emulator-run.ts", 52 | "start:micropython": "tsx demo/micropython-run.ts", 53 | "start:circuitpython": "tsx demo/micropython-run.ts --circuitpython", 54 | "start:gdbdiff": "tsx debug/gdbdiff.ts", 55 | "test": "vitest run", 56 | "test:watch": "vitest", 57 | "test:micropython-spi": "tsx test/micropython-spi-test.ts" 58 | }, 59 | "devDependencies": { 60 | "@types/minimist": "^1.2.2", 61 | "@types/node": "^18", 62 | "@typescript-eslint/eslint-plugin": "^6.7.3", 63 | "@typescript-eslint/parser": "^6.7.3", 64 | "eslint": "^8.50.0", 65 | "husky": "^8.0.3", 66 | "lint-staged": "^15.4.3", 67 | "minimist": "^1.2.7", 68 | "prettier": "^3.0.3", 69 | "rimraf": "^5.0.5", 70 | "tsx": "^4.19.3", 71 | "typescript": "^5.7.3", 72 | "uf2": "^1.0.0", 73 | "vitest": "^3.0.6" 74 | }, 75 | "lint-staged": { 76 | "**/*.ts": [ 77 | "eslint --fix", 78 | "prettier --write" 79 | ], 80 | "**/*.js": [ 81 | "prettier --write" 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | arrowParens: 'always', 3 | printWidth: 100, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | endOfLine: 'auto', 7 | }; 8 | -------------------------------------------------------------------------------- /src/clock/clock.ts: -------------------------------------------------------------------------------- 1 | export type AlarmCallback = () => void; 2 | 3 | export interface IAlarm { 4 | schedule(deltaNanos: number): void; 5 | cancel(): void; 6 | } 7 | 8 | export interface IClock { 9 | readonly nanos: number; 10 | 11 | createAlarm(callback: AlarmCallback): IAlarm; 12 | } 13 | -------------------------------------------------------------------------------- /src/clock/mock-clock.ts: -------------------------------------------------------------------------------- 1 | import { SimulationClock } from './simulation-clock.js'; 2 | 3 | export class MockClock extends SimulationClock { 4 | advance(deltaMicros: number) { 5 | this.tick(this.nanos + deltaMicros * 1000); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/clock/simulation-clock.ts: -------------------------------------------------------------------------------- 1 | import { AlarmCallback, IAlarm, IClock } from './clock.js'; 2 | 3 | type ClockEventCallback = () => void; 4 | 5 | export class ClockAlarm implements IAlarm { 6 | next: ClockAlarm | null = null; 7 | nanos: number = 0; 8 | scheduled = false; 9 | 10 | constructor( 11 | private readonly clock: SimulationClock, 12 | readonly callback: AlarmCallback, 13 | ) {} 14 | 15 | schedule(deltaNanos: number): void { 16 | if (this.scheduled) { 17 | this.cancel(); 18 | } 19 | this.clock.linkAlarm(deltaNanos, this); 20 | } 21 | 22 | cancel(): void { 23 | this.clock.unlinkAlarm(this); 24 | this.scheduled = false; 25 | } 26 | } 27 | 28 | export class SimulationClock implements IClock { 29 | private nextAlarm: ClockAlarm | null = null; 30 | 31 | private nanosCounter = 0; 32 | 33 | constructor(readonly frequency = 125e6) {} 34 | 35 | get nanos() { 36 | return this.nanosCounter; 37 | } 38 | 39 | get micros() { 40 | return this.nanos / 1000; 41 | } 42 | 43 | createAlarm(callback: ClockEventCallback) { 44 | return new ClockAlarm(this, callback); 45 | } 46 | 47 | linkAlarm(nanos: number, alarm: ClockAlarm) { 48 | alarm.nanos = this.nanos + nanos; 49 | let alarmListItem = this.nextAlarm; 50 | let lastItem = null; 51 | while (alarmListItem && alarmListItem.nanos < alarm.nanos) { 52 | lastItem = alarmListItem; 53 | alarmListItem = alarmListItem.next; 54 | } 55 | if (lastItem) { 56 | lastItem.next = alarm; 57 | alarm.next = alarmListItem; 58 | } else { 59 | this.nextAlarm = alarm; 60 | alarm.next = alarmListItem; 61 | } 62 | alarm.scheduled = true; 63 | return alarm; 64 | } 65 | 66 | unlinkAlarm(alarm: ClockAlarm) { 67 | let alarmListItem = this.nextAlarm; 68 | if (!alarmListItem) { 69 | return false; 70 | } 71 | let lastItem = null; 72 | while (alarmListItem) { 73 | if (alarmListItem === alarm) { 74 | if (lastItem) { 75 | lastItem.next = alarmListItem.next; 76 | } else { 77 | this.nextAlarm = alarmListItem.next; 78 | } 79 | return true; 80 | } 81 | lastItem = alarmListItem; 82 | alarmListItem = alarmListItem.next; 83 | } 84 | return false; 85 | } 86 | 87 | tick(deltaNanos: number) { 88 | const targetNanos = this.nanosCounter + deltaNanos; 89 | let alarm = this.nextAlarm; 90 | while (alarm && alarm.nanos <= targetNanos) { 91 | this.nextAlarm = alarm.next; 92 | this.nanosCounter = alarm.nanos; 93 | alarm.callback(); 94 | alarm = this.nextAlarm; 95 | } 96 | this.nanosCounter = targetNanos; 97 | } 98 | 99 | get nanosToNextAlarm() { 100 | if (this.nextAlarm) { 101 | return this.nextAlarm.nanos - this.nanos; 102 | } 103 | return 0; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/gdb/gdb-connection.ts: -------------------------------------------------------------------------------- 1 | import { GDBServer, STOP_REPLY_SIGINT, STOP_REPLY_TRAP } from './gdb-server.js'; 2 | import { gdbChecksum, gdbMessage } from './gdb-utils.js'; 3 | 4 | export type GDBResponseHandler = (value: string) => void; 5 | 6 | export class GDBConnection { 7 | readonly target = this.server.target; 8 | private buf = ''; 9 | 10 | constructor( 11 | private server: GDBServer, 12 | private onResponse: GDBResponseHandler, 13 | ) { 14 | server.addConnection(this); 15 | onResponse('+'); 16 | } 17 | 18 | feedData(data: string) { 19 | const { onResponse } = this; 20 | if (data.charCodeAt(0) === 3) { 21 | this.server.info('BREAK'); 22 | this.target.stop(); 23 | onResponse(gdbMessage(STOP_REPLY_SIGINT)); 24 | data = data.slice(1); 25 | } 26 | 27 | this.buf += data; 28 | for (;;) { 29 | const dolla = this.buf.indexOf('$'); 30 | const hash = this.buf.indexOf('#', dolla + 1); 31 | if (dolla < 0 || hash < 0 || hash + 2 > this.buf.length) { 32 | return; 33 | } 34 | const cmd = this.buf.substring(dolla + 1, hash); 35 | const cksum = this.buf.substr(hash + 1, 2); 36 | this.buf = this.buf.substr(hash + 2); 37 | if (gdbChecksum(cmd) !== cksum) { 38 | this.server.warn(`GDB checksum error in message: ${cmd}`); 39 | onResponse('-'); 40 | } else { 41 | onResponse('+'); 42 | this.server.debug(`>${cmd}`); 43 | const response = this.server.processGDBMessage(cmd); 44 | if (response) { 45 | this.server.debug(`<${response}`); 46 | onResponse(response); 47 | } 48 | } 49 | } 50 | } 51 | 52 | onBreakpoint() { 53 | try { 54 | this.onResponse(gdbMessage(STOP_REPLY_TRAP)); 55 | } catch (e) { 56 | this.server.removeConnection(this); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/gdb/gdb-target.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | 3 | export interface IGDBTarget { 4 | readonly executing: boolean; 5 | rp2040: RP2040; 6 | 7 | execute(): void; 8 | stop(): void; 9 | } 10 | -------------------------------------------------------------------------------- /src/gdb/gdb-tcp-server.ts: -------------------------------------------------------------------------------- 1 | import { createServer, Socket } from 'net'; 2 | import { GDBConnection } from './gdb-connection.js'; 3 | import { GDBServer } from './gdb-server.js'; 4 | import { IGDBTarget } from './gdb-target.js'; 5 | 6 | export class GDBTCPServer extends GDBServer { 7 | private socketServer = createServer(); 8 | 9 | constructor( 10 | target: IGDBTarget, 11 | readonly port: number = 3333, 12 | ) { 13 | super(target); 14 | this.socketServer.listen(port); 15 | this.socketServer.on('connection', (socket) => this.handleConnection(socket)); 16 | } 17 | 18 | handleConnection(socket: Socket) { 19 | this.info('GDB connected'); 20 | socket.setNoDelay(true); 21 | 22 | const connection = new GDBConnection(this, (data) => { 23 | socket.write(data); 24 | }); 25 | 26 | socket.on('data', (data) => { 27 | connection.feedData(data.toString('utf-8')); 28 | }); 29 | 30 | socket.on('error', (err) => { 31 | this.removeConnection(connection); 32 | this.error(`GDB socket error ${err}`); 33 | }); 34 | 35 | socket.on('close', () => { 36 | this.removeConnection(connection); 37 | this.info('GDB disconnected'); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/gdb/gdb-utils.ts: -------------------------------------------------------------------------------- 1 | export function encodeHexByte(value: number) { 2 | return (value >> 4).toString(16) + (value & 0xf).toString(16); 3 | } 4 | 5 | export function encodeHexBuf(buf: Uint8Array) { 6 | return Array.from(buf).map(encodeHexByte).join(''); 7 | } 8 | 9 | export function encodeHexUint32BE(value: number) { 10 | return encodeHexBuf( 11 | new Uint8Array([(value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff]), 12 | ); 13 | } 14 | 15 | export function encodeHexUint32(value: number) { 16 | const buf = new Uint32Array([value]); 17 | return encodeHexBuf(new Uint8Array(buf.buffer)); 18 | } 19 | 20 | export function decodeHexBuf(encoded: string) { 21 | const result = new Uint8Array(encoded.length / 2); 22 | for (let i = 0; i < result.length; i++) { 23 | result[i] = parseInt(encoded.substr(i * 2, 2), 16); 24 | } 25 | return result; 26 | } 27 | 28 | export function decodeHexUint32Array(encoded: string) { 29 | return new Uint32Array(decodeHexBuf(encoded).buffer); 30 | } 31 | 32 | export function decodeHexUint32(encoded: string) { 33 | return decodeHexUint32Array(encoded)[0]; 34 | } 35 | 36 | export function gdbChecksum(text: string) { 37 | const value = 38 | text 39 | .split('') 40 | .map((c) => c.charCodeAt(0)) 41 | .reduce((a, b) => a + b, 0) & 0xff; 42 | return encodeHexByte(value); 43 | } 44 | 45 | export function gdbMessage(value: string) { 46 | return `$${value}#${gdbChecksum(value)}`; 47 | } 48 | -------------------------------------------------------------------------------- /src/gpio-pin.ts: -------------------------------------------------------------------------------- 1 | import { WaitType } from './peripherals/pio.js'; 2 | import { RP2040 } from './rp2040.js'; 3 | 4 | export enum GPIOPinState { 5 | Low, 6 | High, 7 | Input, 8 | InputPullUp, 9 | InputPullDown, 10 | InputBusKeeper, 11 | } 12 | 13 | export const FUNCTION_PWM = 4; 14 | export const FUNCTION_SIO = 5; 15 | export const FUNCTION_PIO0 = 6; 16 | export const FUNCTION_PIO1 = 7; 17 | 18 | export type GPIOPinListener = (state: GPIOPinState, oldState: GPIOPinState) => void; 19 | 20 | function applyOverride(value: boolean, overrideType: number) { 21 | switch (overrideType) { 22 | case 0: 23 | return value; 24 | case 1: 25 | return !value; 26 | case 2: 27 | return false; 28 | case 3: 29 | return true; 30 | } 31 | console.error('applyOverride received invalid override type', overrideType); 32 | return value; 33 | } 34 | 35 | const IRQ_EDGE_HIGH = 1 << 3; 36 | const IRQ_EDGE_LOW = 1 << 2; 37 | const IRQ_LEVEL_HIGH = 1 << 1; 38 | const IRQ_LEVEL_LOW = 1 << 0; 39 | 40 | export class GPIOPin { 41 | private rawInputValue = false; 42 | private lastValue = this.value; 43 | 44 | ctrl: number = 0x1f; 45 | padValue: number = 0b0110110; 46 | irqEnableMask = 0; 47 | irqForceMask = 0; 48 | irqStatus = 0; 49 | 50 | private readonly listeners = new Set(); 51 | 52 | constructor( 53 | readonly rp2040: RP2040, 54 | readonly index: number, 55 | readonly name = index.toString(), 56 | ) {} 57 | 58 | get rawInterrupt() { 59 | return !!((this.irqStatus & this.irqEnableMask) | this.irqForceMask); 60 | } 61 | 62 | get isSlewFast() { 63 | return !!(this.padValue & 1); 64 | } 65 | 66 | get schmittEnabled() { 67 | return !!(this.padValue & 2); 68 | } 69 | 70 | get pulldownEnabled() { 71 | return !!(this.padValue & 4); 72 | } 73 | 74 | get pullupEnabled() { 75 | return !!(this.padValue & 8); 76 | } 77 | 78 | get driveStrength() { 79 | return (this.padValue >> 4) & 0x3; 80 | } 81 | 82 | get inputEnable() { 83 | return !!(this.padValue & 0x40); 84 | } 85 | 86 | get outputDisable() { 87 | return !!(this.padValue & 0x80); 88 | } 89 | 90 | get functionSelect() { 91 | return this.ctrl & 0x1f; 92 | } 93 | 94 | get outputOverride() { 95 | return (this.ctrl >> 8) & 0x3; 96 | } 97 | 98 | get outputEnableOverride() { 99 | return (this.ctrl >> 12) & 0x3; 100 | } 101 | 102 | get inputOverride() { 103 | return (this.ctrl >> 16) & 0x3; 104 | } 105 | 106 | get irqOverride() { 107 | return (this.ctrl >> 28) & 0x3; 108 | } 109 | 110 | get rawOutputEnable() { 111 | const { index, rp2040, functionSelect } = this; 112 | const bitmask = 1 << index; 113 | switch (functionSelect) { 114 | case FUNCTION_PWM: 115 | return !!(rp2040.pwm.gpioDirection & bitmask); 116 | 117 | case FUNCTION_SIO: 118 | return !!(rp2040.sio.gpioOutputEnable & bitmask); 119 | 120 | case FUNCTION_PIO0: 121 | return !!(rp2040.pio[0].pinDirections & bitmask); 122 | 123 | case FUNCTION_PIO1: 124 | return !!(rp2040.pio[1].pinDirections & bitmask); 125 | 126 | default: 127 | return false; 128 | } 129 | } 130 | 131 | get rawOutputValue() { 132 | const { index, rp2040, functionSelect } = this; 133 | const bitmask = 1 << index; 134 | switch (functionSelect) { 135 | case FUNCTION_PWM: 136 | return !!(rp2040.pwm.gpioValue & bitmask); 137 | 138 | case FUNCTION_SIO: 139 | return !!(rp2040.sio.gpioValue & bitmask); 140 | 141 | case FUNCTION_PIO0: 142 | return !!(rp2040.pio[0].pinValues & bitmask); 143 | 144 | case FUNCTION_PIO1: 145 | return !!(rp2040.pio[1].pinValues & bitmask); 146 | 147 | default: 148 | return false; 149 | } 150 | } 151 | 152 | get inputValue() { 153 | return applyOverride(this.rawInputValue && this.inputEnable, this.inputOverride); 154 | } 155 | 156 | get irqValue() { 157 | return applyOverride(this.rawInterrupt, this.irqOverride); 158 | } 159 | 160 | get outputEnable() { 161 | return applyOverride(this.rawOutputEnable, this.outputEnableOverride); 162 | } 163 | 164 | get outputValue() { 165 | return applyOverride(this.rawOutputValue, this.outputOverride); 166 | } 167 | 168 | /** 169 | * Returns the STATUS register value for the pin, as outlined in section 2.19.6 of the datasheet 170 | */ 171 | get status() { 172 | const irqToProc = this.irqValue ? 1 << 26 : 0; 173 | const irqFromPad = this.rawInterrupt ? 1 << 24 : 0; 174 | const inToPeri = this.inputValue ? 1 << 19 : 0; 175 | const inFromPad = this.rawInputValue ? 1 << 17 : 0; 176 | const oeToPad = this.outputEnable ? 1 << 13 : 0; 177 | const oeFromPeri = this.rawOutputEnable ? 1 << 12 : 0; 178 | const outToPad = this.outputValue ? 1 << 9 : 0; 179 | const outFromPeri = this.rawOutputValue ? 1 << 8 : 0; 180 | return ( 181 | irqToProc | irqFromPad | inToPeri | inFromPad | oeToPad | oeFromPeri | outToPad | outFromPeri 182 | ); 183 | } 184 | 185 | get value() { 186 | if (this.outputEnable) { 187 | return this.outputValue ? GPIOPinState.High : GPIOPinState.Low; 188 | } else { 189 | // TODO: check what happens when we enable both pullup/pulldown 190 | // ANSWER: It is valid, see: 2.19.4.1. Bus Keeper Mode, datasheet p240 191 | if (this.pulldownEnabled && this.pullupEnabled) { 192 | // Pull high when high, pull low when low: 193 | return GPIOPinState.InputBusKeeper; 194 | } else if (this.pulldownEnabled) { 195 | return GPIOPinState.InputPullDown; 196 | } else if (this.pullupEnabled) { 197 | return GPIOPinState.InputPullUp; 198 | } 199 | return GPIOPinState.Input; 200 | } 201 | } 202 | 203 | setInputValue(value: boolean) { 204 | this.rawInputValue = value; 205 | const prevIrqValue = this.irqValue; 206 | if (value && this.inputEnable) { 207 | this.irqStatus |= IRQ_EDGE_HIGH | IRQ_LEVEL_HIGH; 208 | this.irqStatus &= ~IRQ_LEVEL_LOW; 209 | } else { 210 | this.irqStatus |= IRQ_EDGE_LOW | IRQ_LEVEL_LOW; 211 | this.irqStatus &= ~IRQ_LEVEL_HIGH; 212 | } 213 | if (this.irqValue !== prevIrqValue) { 214 | this.rp2040.updateIOInterrupt(); 215 | } 216 | if (this.functionSelect === FUNCTION_PWM) { 217 | this.rp2040.pwm.gpioOnInput(this.index); 218 | } 219 | for (const pio of this.rp2040.pio) { 220 | for (const machine of pio.machines) { 221 | if ( 222 | machine.enabled && 223 | machine.waiting && 224 | machine.waitType === WaitType.Pin && 225 | machine.waitIndex === this.index 226 | ) { 227 | machine.checkWait(); 228 | } 229 | } 230 | } 231 | } 232 | 233 | checkForUpdates() { 234 | const { lastValue, value } = this; 235 | if (value !== lastValue) { 236 | this.lastValue = value; 237 | for (const listener of this.listeners) { 238 | listener(value, lastValue); 239 | } 240 | } 241 | } 242 | 243 | refreshInput() { 244 | this.setInputValue(this.rawInputValue); 245 | } 246 | 247 | updateIRQValue(value: number) { 248 | if (value & IRQ_EDGE_LOW && this.irqStatus & IRQ_EDGE_LOW) { 249 | this.irqStatus &= ~IRQ_EDGE_LOW; 250 | this.rp2040.updateIOInterrupt(); 251 | } 252 | if (value & IRQ_EDGE_HIGH && this.irqStatus & IRQ_EDGE_HIGH) { 253 | this.irqStatus &= ~IRQ_EDGE_HIGH; 254 | this.rp2040.updateIOInterrupt(); 255 | } 256 | } 257 | 258 | addListener(callback: GPIOPinListener) { 259 | this.listeners.add(callback); 260 | return () => this.listeners.delete(callback); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { GDBConnection } from './gdb/gdb-connection.js'; 2 | export { GDBServer } from './gdb/gdb-server.js'; 3 | export { GPIOPin, GPIOPinState } from './gpio-pin.js'; 4 | export { I2CMode, I2CSpeed, RPI2C } from './peripherals/i2c.js'; 5 | export { BasePeripheral, type Peripheral } from './peripherals/peripheral.js'; 6 | export { RPUSBController } from './peripherals/usb.js'; 7 | export { RP2040 } from './rp2040.js'; 8 | export { Simulator } from './simulator.js'; 9 | export { USBCDC } from './usb/cdc.js'; 10 | export { 11 | DataDirection, 12 | DescriptorType, 13 | SetupRecipient, 14 | SetupRequest, 15 | SetupType, 16 | type ISetupPacketParams, 17 | } from './usb/interfaces.js'; 18 | export { 19 | createSetupPacket, 20 | getDescriptorPacket, 21 | setDeviceAddressPacket, 22 | setDeviceConfigurationPacket, 23 | } from './usb/setup.js'; 24 | export { ConsoleLogger, LogLevel, type Logger } from './utils/logging.js'; 25 | -------------------------------------------------------------------------------- /src/interpolator.ts: -------------------------------------------------------------------------------- 1 | import { s32, u32 } from './utils/bit.js'; 2 | 3 | export class InterpolatorConfig { 4 | shift = 0; 5 | maskLSB = 0; 6 | maskMSB = 0; 7 | signed = false; 8 | crossInput = false; 9 | crossResult = false; 10 | addRaw = false; 11 | forceMSB = 0; 12 | blend = false; 13 | clamp = false; 14 | overf0 = false; 15 | overf1 = false; 16 | overf = false; 17 | 18 | constructor(value: number) { 19 | this.shift = (value >>> 0) & 0b11111; 20 | this.maskLSB = (value >>> 5) & 0b11111; 21 | this.maskMSB = (value >>> 10) & 0b11111; 22 | this.signed = Boolean((value >>> 15) & 1); 23 | this.crossInput = Boolean((value >>> 16) & 1); 24 | this.crossResult = Boolean((value >>> 17) & 1); 25 | this.addRaw = Boolean((value >>> 18) & 1); 26 | this.forceMSB = (value >>> 19) & 0b11; 27 | this.blend = Boolean((value >>> 21) & 1); 28 | this.clamp = Boolean((value >>> 22) & 1); 29 | this.overf0 = Boolean((value >>> 23) & 1); 30 | this.overf1 = Boolean((value >>> 24) & 1); 31 | this.overf = Boolean((value >>> 25) & 1); 32 | } 33 | 34 | toUint32() { 35 | return ( 36 | ((this.shift & 0b11111) << 0) | 37 | ((this.maskLSB & 0b11111) << 5) | 38 | ((this.maskMSB & 0b11111) << 10) | 39 | ((Number(this.signed) & 1) << 15) | 40 | ((Number(this.crossInput) & 1) << 16) | 41 | ((Number(this.crossResult) & 1) << 17) | 42 | ((Number(this.addRaw) & 1) << 18) | 43 | ((this.forceMSB & 0b11) << 19) | 44 | ((Number(this.blend) & 1) << 21) | 45 | ((Number(this.clamp) & 1) << 22) | 46 | ((Number(this.overf0) & 1) << 23) | 47 | ((Number(this.overf1) & 1) << 24) | 48 | ((Number(this.overf) & 1) << 25) 49 | ); 50 | } 51 | } 52 | 53 | export class Interpolator { 54 | accum0 = 0; 55 | accum1 = 0; 56 | base0 = 0; 57 | base1 = 0; 58 | base2 = 0; 59 | ctrl0 = 0; 60 | ctrl1 = 0; 61 | result0 = 0; 62 | result1 = 0; 63 | result2 = 0; 64 | smresult0 = 0; 65 | smresult1 = 0; 66 | 67 | constructor(private readonly index: number) { 68 | this.update(); 69 | } 70 | 71 | update() { 72 | const N = this.index; 73 | const ctrl0 = new InterpolatorConfig(this.ctrl0); 74 | const ctrl1 = new InterpolatorConfig(this.ctrl1); 75 | 76 | const do_clamp = ctrl0.clamp && N == 1; 77 | const do_blend = ctrl0.blend && N == 0; 78 | 79 | ctrl0.clamp = do_clamp; 80 | ctrl0.blend = do_blend; 81 | ctrl1.clamp = false; 82 | ctrl1.blend = false; 83 | ctrl1.overf0 = false; 84 | ctrl1.overf1 = false; 85 | ctrl1.overf = false; 86 | 87 | const input0 = s32(ctrl0.crossInput ? this.accum1 : this.accum0); 88 | const input1 = s32(ctrl1.crossInput ? this.accum0 : this.accum1); 89 | 90 | const msbmask0 = ctrl0.maskMSB == 31 ? 0xffffffff : (1 << (ctrl0.maskMSB + 1)) - 1; 91 | const msbmask1 = ctrl1.maskMSB == 31 ? 0xffffffff : (1 << (ctrl1.maskMSB + 1)) - 1; 92 | const mask0 = msbmask0 & ~((1 << ctrl0.maskLSB) - 1); 93 | const mask1 = msbmask1 & ~((1 << ctrl1.maskLSB) - 1); 94 | 95 | const uresult0 = (input0 >>> ctrl0.shift) & mask0; 96 | const uresult1 = (input1 >>> ctrl1.shift) & mask1; 97 | 98 | const overf0 = Boolean((input0 >>> ctrl0.shift) & ~msbmask0); 99 | const overf1 = Boolean((input1 >>> ctrl1.shift) & ~msbmask1); 100 | const overf = overf0 || overf1; 101 | 102 | const sextmask0 = uresult0 & (1 << ctrl0.maskMSB) ? -1 << ctrl0.maskMSB : 0; 103 | const sextmask1 = uresult1 & (1 << ctrl1.maskMSB) ? -1 << ctrl1.maskMSB : 0; 104 | 105 | const sresult0 = uresult0 | sextmask0; 106 | const sresult1 = uresult1 | sextmask1; 107 | 108 | const result0 = ctrl0.signed ? sresult0 : uresult0; 109 | const result1 = ctrl1.signed ? sresult1 : uresult1; 110 | 111 | const addresult0 = this.base0 + (ctrl0.addRaw ? input0 : result0); 112 | const addresult1 = this.base1 + (ctrl1.addRaw ? input1 : result1); 113 | const addresult2 = this.base2 + result0 + (do_blend ? 0 : result1); 114 | 115 | const uclamp0 = 116 | u32(result0) < u32(this.base0) 117 | ? this.base0 118 | : u32(result0) > u32(this.base1) 119 | ? this.base1 120 | : result0; 121 | const sclamp0 = 122 | s32(result0) < s32(this.base0) 123 | ? this.base0 124 | : s32(result0) > s32(this.base1) 125 | ? this.base1 126 | : result0; 127 | const clamp0 = ctrl0.signed ? sclamp0 : uclamp0; 128 | 129 | const alpha1 = result1 & 0xff; 130 | const ublend1 = 131 | u32(this.base0) + (Math.floor((alpha1 * (u32(this.base1) - u32(this.base0))) / 256) | 0); 132 | const sblend1 = 133 | s32(this.base0) + (Math.floor((alpha1 * (s32(this.base1) - s32(this.base0))) / 256) | 0); 134 | const blend1 = ctrl1.signed ? sblend1 : ublend1; 135 | 136 | this.smresult0 = u32(result0); 137 | this.smresult1 = u32(result1); 138 | this.result0 = u32( 139 | do_blend ? alpha1 : (do_clamp ? clamp0 : addresult0) | (ctrl0.forceMSB << 28), 140 | ); 141 | this.result1 = u32((do_blend ? blend1 : addresult1) | (ctrl0.forceMSB << 28)); 142 | this.result2 = u32(addresult2); 143 | 144 | ctrl0.overf0 = overf0; 145 | ctrl0.overf1 = overf1; 146 | ctrl0.overf = overf; 147 | this.ctrl0 = ctrl0.toUint32(); 148 | this.ctrl1 = ctrl1.toUint32(); 149 | } 150 | 151 | writeback() { 152 | const ctrl0 = new InterpolatorConfig(this.ctrl0); 153 | const ctrl1 = new InterpolatorConfig(this.ctrl1); 154 | 155 | this.accum0 = u32(ctrl0.crossResult ? this.result1 : this.result0); 156 | this.accum1 = u32(ctrl1.crossResult ? this.result0 : this.result1); 157 | 158 | this.update(); 159 | } 160 | 161 | setBase01(value: number) { 162 | const N = this.index; 163 | const ctrl0 = new InterpolatorConfig(this.ctrl0); 164 | const ctrl1 = new InterpolatorConfig(this.ctrl1); 165 | 166 | const do_blend = ctrl0.blend && N == 0; 167 | 168 | const input0 = value & 0xffff; 169 | const input1 = (value >>> 16) & 0xffff; 170 | 171 | const sextmask0 = input0 & (1 << 15) ? -1 << 15 : 0; 172 | const sextmask1 = input1 & (1 << 15) ? -1 << 15 : 0; 173 | 174 | const base0 = (do_blend ? ctrl1.signed : ctrl0.signed) ? input0 | sextmask0 : input0; 175 | const base1 = ctrl1.signed ? input1 | sextmask1 : input1; 176 | 177 | this.base0 = u32(base0); 178 | this.base1 = u32(base1); 179 | 180 | this.update(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/irq.ts: -------------------------------------------------------------------------------- 1 | export enum IRQ { 2 | TIMER_0 = 0, 3 | TIMER_1 = 1, 4 | TIMER_2 = 2, 5 | TIMER_3 = 3, 6 | PWM_WRAP = 4, 7 | USBCTRL = 5, 8 | XIP = 6, 9 | PIO0_IRQ0 = 7, 10 | PIO0_IRQ1 = 8, 11 | PIO1_IRQ0 = 9, 12 | PIO1_IRQ1 = 10, 13 | DMA_IRQ0 = 11, 14 | DMA_IRQ1 = 12, 15 | IO_BANK0 = 13, 16 | IO_QSPI = 14, 17 | SIO_PROC0 = 15, 18 | SIO_PROC1 = 16, 19 | CLOCKS = 17, 20 | SPI0 = 18, 21 | SPI1 = 19, 22 | UART0 = 20, 23 | UART1 = 21, 24 | ADC_FIFO = 22, 25 | I2C0 = 23, 26 | I2C1 = 24, 27 | RTC = 25, 28 | } 29 | 30 | export const MAX_HARDWARE_IRQ = IRQ.RTC; 31 | -------------------------------------------------------------------------------- /src/peripherals/adc.ts: -------------------------------------------------------------------------------- 1 | import { IRQ } from '../irq.js'; 2 | import { RP2040 } from '../rp2040.js'; 3 | import { FIFO } from '../utils/fifo.js'; 4 | import { DREQChannel } from './dma.js'; 5 | import { BasePeripheral, Peripheral } from './peripheral.js'; 6 | 7 | const CS = 0x00; // ADC Control and Status 8 | const RESULT = 0x04; // Result of most recent ADC conversion 9 | const FCS = 0x08; // FIFO control and status 10 | const FIFO_REG = 0x0c; // Conversion result FIFO 11 | const DIV = 0x10; // Clock divider.0x14 INTR Raw Interrupts 12 | const INTR = 0x14; // Raw Interrupts 13 | const INTE = 0x18; // Interrupt Enable 14 | const INTF = 0x1c; // Interrupt Force 15 | const INTS = 0x20; // Interrupt status after masking & forcing 16 | 17 | // CS bits 18 | const CS_RROBIN_MASK = 0x1f; 19 | const CS_RROBIN_SHIFT = 16; 20 | const CS_AINSEL_MASK = 0x7; 21 | const CS_AINSEL_SHIFT = 12; 22 | const CS_ERR_STICKY = 1 << 10; 23 | const CS_ERR = 1 << 9; 24 | const CS_READY = 1 << 8; 25 | const CS_START_MANY = 1 << 3; 26 | const CS_START_ONE = 1 << 2; 27 | const CS_TS_EN = 1 << 1; 28 | const CS_EN = 1 << 0; 29 | const CS_WRITE_MASK = 30 | (CS_RROBIN_MASK << CS_RROBIN_SHIFT) | 31 | (CS_AINSEL_MASK << CS_AINSEL_SHIFT) | 32 | CS_START_MANY | 33 | CS_START_ONE | 34 | CS_TS_EN | 35 | CS_EN; 36 | 37 | // FCS bits 38 | const FCS_THRES_MASK = 0xf; 39 | const FCS_THRESH_SHIFT = 24; 40 | const FCS_LEVEL_MASK = 0xf; 41 | const FCS_LEVEL_SHIFT = 16; 42 | const FCS_OVER = 1 << 11; 43 | const FCS_UNDER = 1 << 10; 44 | const FCS_FULL = 1 << 9; 45 | const FCS_EMPTY = 1 << 8; 46 | const FCS_DREQ_EN = 1 << 3; 47 | const FCS_ERR = 1 << 2; 48 | const FCS_SHIFT = 1 << 1; 49 | const FCS_EN = 1 << 0; 50 | const FCS_WRITE_MASK = 51 | (FCS_THRES_MASK << FCS_THRESH_SHIFT) | FCS_DREQ_EN | FCS_ERR | FCS_SHIFT | FCS_EN; 52 | 53 | // FIFO_REG bits 54 | const FIFO_ERR = 1 << 15; 55 | 56 | // DIV bits 57 | const DIV_INT_MASK = 0xffff; 58 | const DIV_INT_SHIFT = 8; 59 | const DIV_FRAC_MASK = 0xff; 60 | const DIV_FRAC_SHIFT = 0; 61 | 62 | // Interrupt bits 63 | const FIFO_INT = 1 << 0; 64 | 65 | export class RPADC extends BasePeripheral implements Peripheral { 66 | /* Number of ADC channels */ 67 | readonly numChannels = 5; 68 | 69 | /** ADC resolution (in bits) */ 70 | readonly resolution = 12; 71 | 72 | /** Time to read a single sample, in microseconds */ 73 | readonly sampleTime = 2; 74 | 75 | /** 76 | * ADC Channel values. Channels 0...3 are connected to GPIO 26...29, and channel 4 is connected to the built-in 77 | * temperature sensor: T=27-(ADC_voltage-0.706)/0.001721. 78 | * 79 | * Changing the values will change the ADC reading, unless you override onADCRead() with a custom implementation. 80 | */ 81 | readonly channelValues = [0, 0, 0, 0, 0]; 82 | 83 | /** 84 | * Invoked whenever the emulated code performs an ADC read. 85 | * 86 | * The default implementation reads the result from the `channelValues` array, and then calls 87 | * completeADCRead() after `sampleTime` microseconds. 88 | * 89 | * If you override the default implementation, make sure to call `completeADCRead()` after 90 | * `sampleTime` microseconds (or else the ADC read will never complete). 91 | */ 92 | onADCRead: (channel: number) => void = (channel) => { 93 | // Default implementation 94 | this.currentChannel = channel; 95 | this.sampleAlarm.schedule(this.sampleTime * 1000); 96 | }; 97 | 98 | readonly fifo = new FIFO(4); 99 | readonly dreq = DREQChannel.DREQ_ADC; 100 | 101 | // Registers 102 | cs = 0; 103 | fcs = 0; 104 | clockDiv = 0; 105 | intEnable = 0; 106 | intForce = 0; 107 | result = 0; 108 | 109 | // Status 110 | busy = false; 111 | err = false; 112 | 113 | currentChannel = 0; 114 | /** Used to simulate ADC sample time */ 115 | sampleAlarm; 116 | 117 | /** For scheduling multi-shot ADC capture */ 118 | multiShotAlarm; 119 | 120 | get temperatueEnable() { 121 | return this.cs & CS_TS_EN; 122 | } 123 | 124 | get enabled() { 125 | return this.cs & CS_EN; 126 | } 127 | 128 | get divider() { 129 | return ( 130 | 1 + 131 | ((this.clockDiv >> DIV_INT_SHIFT) & DIV_INT_MASK) + 132 | ((this.clockDiv >> DIV_FRAC_SHIFT) & DIV_FRAC_MASK) / 256 133 | ); 134 | } 135 | 136 | get intRaw() { 137 | const thres = (this.fcs >> FCS_THRESH_SHIFT) & FCS_THRES_MASK; 138 | return this.fifo.itemCount >= thres ? FIFO_INT : 0; 139 | } 140 | 141 | get intStatus() { 142 | return (this.intRaw & this.intEnable) | this.intForce; 143 | } 144 | 145 | private get activeChannel() { 146 | return (this.cs >> CS_AINSEL_SHIFT) & CS_AINSEL_MASK; 147 | } 148 | 149 | private set activeChannel(channel: number) { 150 | this.cs &= ~(CS_AINSEL_MASK << CS_AINSEL_SHIFT); 151 | this.cs |= (channel & CS_AINSEL_SHIFT) << CS_AINSEL_SHIFT; 152 | } 153 | 154 | constructor(rp2040: RP2040, name: string) { 155 | super(rp2040, name); 156 | this.sampleAlarm = this.rp2040.clock.createAlarm(() => 157 | this.completeADCRead(this.channelValues[this.currentChannel], false), 158 | ); 159 | this.multiShotAlarm = this.rp2040.clock.createAlarm(() => { 160 | if (this.cs & CS_START_MANY) { 161 | this.startADCRead(); 162 | } 163 | }); 164 | } 165 | 166 | checkInterrupts() { 167 | this.rp2040.setInterrupt(IRQ.ADC_FIFO, !!this.intStatus); 168 | } 169 | 170 | startADCRead() { 171 | this.busy = true; 172 | this.onADCRead(this.activeChannel); 173 | } 174 | 175 | private updateDMA() { 176 | if (this.fcs & FCS_DREQ_EN) { 177 | const thres = (this.fcs >> FCS_THRESH_SHIFT) & FCS_THRES_MASK; 178 | if (this.fifo.itemCount >= thres) { 179 | this.rp2040.dma.setDREQ(this.dreq); 180 | } else { 181 | this.rp2040.dma.clearDREQ(this.dreq); 182 | } 183 | } 184 | } 185 | 186 | completeADCRead(value: number, error: boolean) { 187 | this.busy = false; 188 | this.result = value; 189 | if (error) { 190 | this.cs |= CS_ERR_STICKY | CS_ERR; 191 | } else { 192 | this.cs &= ~CS_ERR; 193 | } 194 | 195 | // FIFO 196 | if (this.fcs & FCS_EN) { 197 | if (this.fifo.full) { 198 | this.fcs |= FCS_OVER; 199 | } else { 200 | value &= 0xfff; // 12 bits 201 | if (this.fcs & FCS_SHIFT) { 202 | value >>= 4; 203 | } 204 | if (error && this.fcs & FCS_ERR) { 205 | value |= FIFO_ERR; 206 | } 207 | this.fifo.push(value); 208 | this.updateDMA(); 209 | this.checkInterrupts(); 210 | } 211 | } 212 | 213 | // Round-robin 214 | const round = (this.cs >> CS_RROBIN_SHIFT) & CS_RROBIN_MASK; 215 | if (round) { 216 | let channel = this.activeChannel + 1; 217 | while (!(round & (1 << channel))) { 218 | channel = (channel + 1) % this.numChannels; 219 | } 220 | this.activeChannel = channel; 221 | } 222 | 223 | // Multi-shot conversions 224 | if (this.cs & CS_START_MANY) { 225 | const clockMHZ = 48; 226 | const sampleTicks = clockMHZ * this.sampleTime; 227 | if (this.divider > sampleTicks) { 228 | // clock runs at 48MHz, subtract 2uS 229 | const micros = (this.divider - sampleTicks) / clockMHZ; 230 | this.multiShotAlarm.schedule(micros * 1000); 231 | } else { 232 | this.startADCRead(); 233 | } 234 | } 235 | } 236 | 237 | readUint32(offset: number) { 238 | switch (offset) { 239 | case CS: 240 | return this.cs | (this.err ? CS_ERR : 0) | (this.busy ? 0 : CS_READY); 241 | case RESULT: 242 | return this.result; 243 | case FCS: 244 | return ( 245 | this.fcs | 246 | ((this.fifo.itemCount & FCS_LEVEL_MASK) << FCS_LEVEL_SHIFT) | 247 | (this.fifo.full ? FCS_FULL : 0) | 248 | (this.fifo.empty ? FCS_EMPTY : 0) 249 | ); 250 | case FIFO_REG: 251 | if (this.fifo.empty) { 252 | this.fcs |= FCS_UNDER; 253 | return 0; 254 | } else { 255 | const value = this.fifo.pull(); 256 | this.updateDMA(); 257 | return value; 258 | } 259 | case DIV: 260 | return this.clockDiv; 261 | case INTR: 262 | return this.intRaw; 263 | case INTE: 264 | return this.intEnable; 265 | case INTF: 266 | return this.intForce; 267 | case INTS: 268 | return this.intStatus; 269 | } 270 | return super.readUint32(offset); 271 | } 272 | 273 | writeUint32(offset: number, value: number) { 274 | switch (offset) { 275 | case CS: 276 | this.fcs &= ~(value & CS_ERR_STICKY); // Write-clear bits 277 | this.cs = (this.cs & ~CS_WRITE_MASK) | (value & CS_WRITE_MASK); 278 | if (value & CS_EN && !this.busy && (value & CS_START_ONE || value & CS_START_MANY)) { 279 | this.startADCRead(); 280 | } 281 | break; 282 | case FCS: 283 | this.fcs &= ~(value & (FCS_OVER | FCS_UNDER)); // Write-clear bits 284 | this.fcs = (this.fcs & ~FCS_WRITE_MASK) | (value & FCS_WRITE_MASK); 285 | this.checkInterrupts(); 286 | break; 287 | case DIV: 288 | this.clockDiv = value; 289 | break; 290 | case INTE: 291 | this.intEnable = value & FIFO_INT; 292 | this.checkInterrupts(); 293 | break; 294 | case INTF: 295 | this.intForce = value & FIFO_INT; 296 | this.checkInterrupts(); 297 | break; 298 | default: 299 | super.writeUint32(offset, value); 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/peripherals/busctrl.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { BasePeripheral, Peripheral } from './peripheral.js'; 3 | 4 | /** Bus priority acknowledge */ 5 | const BUS_PRIORITY_ACK = 0x004; 6 | 7 | /** Bus fabric performance counter 0 */ 8 | const PERFCTR0 = 0x008; 9 | /** Bus fabric performance event select for PERFCTR0 */ 10 | const PERFSEL0 = 0x00c; 11 | 12 | /** Bus fabric performance counter 1 */ 13 | const PERFCTR1 = 0x010; 14 | /** Bus fabric performance event select for PERFCTR1 */ 15 | const PERFSEL1 = 0x014; 16 | 17 | /** Bus fabric performance counter 2 */ 18 | const PERFCTR2 = 0x018; 19 | /** Bus fabric performance event select for PERFCTR2 */ 20 | const PERFSEL2 = 0x01c; 21 | 22 | /** Bus fabric performance counter 3 */ 23 | const PERFCTR3 = 0x020; 24 | /** Bus fabric performance event select for PERFCTR3 */ 25 | const PERFSEL3 = 0x024; 26 | 27 | export class RPBUSCTRL extends BasePeripheral implements Peripheral { 28 | voltageSelect = 0; 29 | readonly perfCtr = [0, 0, 0, 0]; 30 | readonly perfSel = [0x1f, 0x1f, 0x1f, 0x1f]; 31 | 32 | constructor(rp2040: RP2040, name: string) { 33 | super(rp2040, name); 34 | } 35 | 36 | readUint32(offset: number) { 37 | switch (offset) { 38 | case BUS_PRIORITY_ACK: 39 | return 1; 40 | case PERFCTR0: 41 | return this.perfCtr[0]; 42 | case PERFSEL0: 43 | return this.perfSel[0]; 44 | case PERFCTR1: 45 | return this.perfCtr[1]; 46 | case PERFSEL1: 47 | return this.perfSel[1]; 48 | case PERFCTR2: 49 | return this.perfCtr[2]; 50 | case PERFSEL2: 51 | return this.perfSel[2]; 52 | case PERFCTR3: 53 | return this.perfCtr[3]; 54 | case PERFSEL3: 55 | return this.perfSel[3]; 56 | } 57 | return super.readUint32(offset); 58 | } 59 | 60 | writeUint32(offset: number, value: number) { 61 | switch (offset) { 62 | case PERFCTR0: 63 | this.perfCtr[0] = 0; 64 | break; 65 | case PERFSEL0: 66 | this.perfSel[0] = value & 0x1f; 67 | break; 68 | case PERFCTR1: 69 | this.perfCtr[1] = 0; 70 | break; 71 | case PERFSEL1: 72 | this.perfSel[1] = value & 0x1f; 73 | break; 74 | case PERFCTR2: 75 | this.perfCtr[2] = 0; 76 | break; 77 | case PERFSEL2: 78 | this.perfSel[2] = value & 0x1f; 79 | break; 80 | case PERFCTR3: 81 | this.perfCtr[3] = 0; 82 | break; 83 | case PERFSEL3: 84 | this.perfSel[3] = value & 0x1f; 85 | break; 86 | default: 87 | super.writeUint32(offset, value); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/peripherals/clocks.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { BasePeripheral, Peripheral } from './peripheral.js'; 3 | 4 | const CLK_GPOUT0_CTRL = 0x00; 5 | const CLK_GPOUT0_DIV = 0x04; 6 | const CLK_GPOUT0_SELECTED = 0x8; 7 | const CLK_GPOUT1_CTRL = 0x0c; 8 | const CLK_GPOUT1_DIV = 0x10; 9 | const CLK_GPOUT1_SELECTED = 0x14; 10 | const CLK_GPOUT2_CTRL = 0x18; 11 | const CLK_GPOUT2_DIV = 0x01c; 12 | const CLK_GPOUT2_SELECTED = 0x20; 13 | const CLK_GPOUT3_CTRL = 0x24; 14 | const CLK_GPOUT3_DIV = 0x28; 15 | const CLK_GPOUT3_SELECTED = 0x2c; 16 | const CLK_REF_CTRL = 0x30; 17 | const CLK_REF_DIV = 0x34; 18 | const CLK_REF_SELECTED = 0x38; 19 | const CLK_SYS_CTRL = 0x3c; 20 | const CLK_SYS_DIV = 0x40; 21 | const CLK_SYS_SELECTED = 0x44; 22 | const CLK_PERI_CTRL = 0x48; 23 | const CLK_PERI_DIV = 0x4c; 24 | const CLK_PERI_SELECTED = 0x50; 25 | const CLK_USB_CTRL = 0x54; 26 | const CLK_USB_DIV = 0x58; 27 | const CLK_USB_SELECTED = 0x5c; 28 | const CLK_ADC_CTRL = 0x60; 29 | const CLK_ADC_DIV = 0x64; 30 | const CLK_ADC_SELECTED = 0x68; 31 | const CLK_RTC_CTRL = 0x6c; 32 | const CLK_RTC_DIV = 0x70; 33 | const CLK_RTC_SELECTED = 0x74; 34 | const CLK_SYS_RESUS_CTRL = 0x78; 35 | const CLK_SYS_RESUS_STATUS = 0x7c; 36 | 37 | export class RPClocks extends BasePeripheral implements Peripheral { 38 | gpout0Ctrl = 0; 39 | gpout0Div = 0x100; 40 | gpout1Ctrl = 0; 41 | gpout1Div = 0x100; 42 | gpout2Ctrl = 0; 43 | gpout2Div = 0x100; 44 | gpout3Ctrl = 0; 45 | gpout3Div = 0x100; 46 | refCtrl = 0; 47 | refDiv = 0x100; 48 | periCtrl = 0; 49 | periDiv = 0x100; 50 | usbCtrl = 0; 51 | usbDiv = 0x100; 52 | sysCtrl = 0; 53 | sysDiv = 0x100; 54 | adcCtrl = 0; 55 | adcDiv = 0x100; 56 | rtcCtrl = 0; 57 | rtcDiv = 0x100; 58 | constructor(rp2040: RP2040, name: string) { 59 | super(rp2040, name); 60 | } 61 | 62 | readUint32(offset: number) { 63 | switch (offset) { 64 | case CLK_GPOUT0_CTRL: 65 | return this.gpout0Ctrl & 0b100110001110111100000; 66 | case CLK_GPOUT0_DIV: 67 | return this.gpout0Div; 68 | case CLK_GPOUT0_SELECTED: 69 | return 1; 70 | case CLK_GPOUT1_CTRL: 71 | return this.gpout1Ctrl & 0b100110001110111100000; 72 | case CLK_GPOUT1_DIV: 73 | return this.gpout1Div; 74 | case CLK_GPOUT1_SELECTED: 75 | return 1; 76 | case CLK_GPOUT2_CTRL: 77 | return this.gpout2Ctrl & 0b100110001110111100000; 78 | case CLK_GPOUT2_DIV: 79 | return this.gpout2Div; 80 | case CLK_GPOUT2_SELECTED: 81 | return 1; 82 | case CLK_GPOUT3_CTRL: 83 | return this.gpout3Ctrl & 0b100110001110111100000; 84 | case CLK_GPOUT3_DIV: 85 | return this.gpout3Div; 86 | case CLK_GPOUT3_SELECTED: 87 | return 1; 88 | case CLK_REF_CTRL: 89 | return this.refCtrl & 0b000001100011; 90 | case CLK_REF_DIV: 91 | return this.refDiv & 0x30; // b8..9 = int divisor. no frac divisor present 92 | case CLK_REF_SELECTED: 93 | return 1 << (this.refCtrl & 0x03); 94 | case CLK_SYS_CTRL: 95 | return this.sysCtrl & 0b000011100001; 96 | case CLK_SYS_DIV: 97 | return this.sysDiv; 98 | case CLK_SYS_SELECTED: 99 | return 1 << (this.sysCtrl & 0x01); 100 | case CLK_PERI_CTRL: 101 | return this.periCtrl & 0b110011100000; 102 | case CLK_PERI_DIV: 103 | return this.periDiv; 104 | case CLK_PERI_SELECTED: 105 | return 1; 106 | case CLK_USB_CTRL: 107 | return this.usbCtrl & 0b100110000110011100000; 108 | case CLK_USB_DIV: 109 | return this.usbDiv; 110 | case CLK_USB_SELECTED: 111 | return 1; 112 | case CLK_ADC_CTRL: 113 | return this.adcCtrl & 0b100110000110011100000; 114 | case CLK_ADC_DIV: 115 | return this.adcDiv & 0x30; 116 | case CLK_ADC_SELECTED: 117 | return 1; 118 | case CLK_RTC_CTRL: 119 | return this.rtcCtrl & 0b100110000110011100000; 120 | case CLK_RTC_DIV: 121 | return this.rtcDiv & 0x30; 122 | case CLK_RTC_SELECTED: 123 | return 1; 124 | case CLK_SYS_RESUS_CTRL: 125 | return 0xff; 126 | case CLK_SYS_RESUS_STATUS: 127 | return 0; /* clock resus not implemented */ 128 | } 129 | return super.readUint32(offset); 130 | } 131 | 132 | writeUint32(offset: number, value: number): void { 133 | switch (offset) { 134 | case CLK_GPOUT0_CTRL: 135 | this.gpout0Ctrl = value; 136 | break; 137 | case CLK_GPOUT0_DIV: 138 | this.gpout0Div = value; 139 | break; 140 | case CLK_GPOUT1_CTRL: 141 | this.gpout1Ctrl = value; 142 | break; 143 | case CLK_GPOUT1_DIV: 144 | this.gpout1Div = value; 145 | break; 146 | case CLK_GPOUT2_CTRL: 147 | this.gpout2Ctrl = value; 148 | break; 149 | case CLK_GPOUT2_DIV: 150 | this.gpout2Div = value; 151 | break; 152 | case CLK_GPOUT3_CTRL: 153 | this.gpout3Ctrl = value; 154 | break; 155 | case CLK_GPOUT3_DIV: 156 | this.gpout3Div = value; 157 | break; 158 | case CLK_REF_CTRL: 159 | this.refCtrl = value; 160 | break; 161 | case CLK_REF_DIV: 162 | this.refDiv = value; 163 | break; 164 | case CLK_SYS_CTRL: 165 | this.sysCtrl = value; 166 | break; 167 | case CLK_SYS_DIV: 168 | this.sysDiv = value; 169 | break; 170 | case CLK_PERI_CTRL: 171 | this.periCtrl = value; 172 | break; 173 | case CLK_PERI_DIV: 174 | this.periDiv = value; 175 | break; 176 | case CLK_USB_CTRL: 177 | this.usbCtrl = value; 178 | break; 179 | case CLK_USB_DIV: 180 | this.usbDiv = value; 181 | break; 182 | case CLK_ADC_CTRL: 183 | this.adcCtrl = value; 184 | break; 185 | case CLK_ADC_DIV: 186 | this.adcDiv = value; 187 | break; 188 | case CLK_RTC_CTRL: 189 | this.rtcCtrl = value; 190 | break; 191 | case CLK_RTC_DIV: 192 | this.rtcDiv = value; 193 | break; 194 | case CLK_SYS_RESUS_CTRL: 195 | return; /* clock resus not implemented */ 196 | default: 197 | super.writeUint32(offset, value); 198 | break; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/peripherals/dma.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { RP2040 } from '../rp2040.js'; 3 | import { MockClock } from '../clock/mock-clock.js'; 4 | import { bit } from '../utils/bit.js'; 5 | 6 | const CH2_WRITE_ADDR = 0x50000084; 7 | const CH2_TRANS_COUNT = 0x50000088; 8 | const CH2_AL1_CTRL = 0x50000090; 9 | const CH2_AL3_READ_ADDR_TRIG = 0x500000bc; 10 | const CH6_READ_ADDR = 0x50000180; 11 | const CH6_WRITE_ADDR = 0x50000184; 12 | const CH6_TRANS_COUNT = 0x50000188; 13 | const CH6_CTRL_TRIG = 0x5000018c; 14 | const INTR = 0x50000400; 15 | 16 | const EN = bit(0); 17 | const DATA_SIZE_SHIFT = 2; 18 | const INCR_WRITE = bit(5); 19 | const INCR_READ = bit(4); 20 | const CHAIN_TO_SHIFT = 11; 21 | const TREQ_SEL_SHIFT = 15; 22 | const BUSY = bit(24); 23 | 24 | const TREQ_PERMANENT = 0x3f; 25 | 26 | describe('DMA', () => { 27 | it('should support DMA channel chaining', () => { 28 | const clock = new MockClock(); 29 | const cpu = new RP2040(clock); 30 | 31 | // This test uses DMA to copy 4 chunks of 8-byte data, located in different memory areas, into a single memory area. 32 | // We use two DMA channels, 2 and 6 (numbers are arbitrary). 33 | // All the RAM addresses below are arbitrary: 34 | const CHUNKS_ADDR = [0x20001000, 0x20001100, 0x20002200, 0x20002300]; 35 | const DEST_ADDR = 0x20008000; 36 | const DMA_CONTROL_BLOCK_ADDR = 0x2000a000; 37 | 38 | // Write the data to be copied, split into four chunks: 39 | cpu.writeUint32(CHUNKS_ADDR[0], 0x10); 40 | cpu.writeUint32(CHUNKS_ADDR[0] + 4, 0x20); 41 | cpu.writeUint32(CHUNKS_ADDR[1], 0x30); 42 | cpu.writeUint32(CHUNKS_ADDR[1] + 4, 0x40); 43 | cpu.writeUint32(CHUNKS_ADDR[2], 0x50); 44 | cpu.writeUint32(CHUNKS_ADDR[2] + 4, 0x60); 45 | cpu.writeUint32(CHUNKS_ADDR[3], 0x70); 46 | cpu.writeUint32(CHUNKS_ADDR[3] + 4, 0x80); 47 | 48 | // Write the source addresses into a DMA control block: 49 | cpu.writeUint32(DMA_CONTROL_BLOCK_ADDR, CHUNKS_ADDR[0]); 50 | cpu.writeUint32(DMA_CONTROL_BLOCK_ADDR + 4, CHUNKS_ADDR[1]); 51 | cpu.writeUint32(DMA_CONTROL_BLOCK_ADDR + 8, CHUNKS_ADDR[2]); 52 | cpu.writeUint32(DMA_CONTROL_BLOCK_ADDR + 12, CHUNKS_ADDR[3]); 53 | cpu.writeUint32(DMA_CONTROL_BLOCK_ADDR + 16, 0); // This marks the end of the chain 54 | 55 | // Channel 2 is used to copy the 8-byte chunks. Configure it: 56 | cpu.writeUint32(CH2_WRITE_ADDR, DEST_ADDR); 57 | cpu.writeUint32(CH2_TRANS_COUNT, 2); // 2 transfers of 4 bytes each = 8 bytes 58 | cpu.writeUint32( 59 | CH2_AL1_CTRL, 60 | EN | 61 | (6 << CHAIN_TO_SHIFT) | 62 | INCR_WRITE | 63 | INCR_READ | 64 | (TREQ_PERMANENT << TREQ_SEL_SHIFT) | 65 | (2 << DATA_SIZE_SHIFT), 66 | ); 67 | 68 | // Channel 6 is used to control channel 2: 69 | cpu.writeUint32(CH6_WRITE_ADDR, CH2_AL3_READ_ADDR_TRIG); 70 | cpu.writeUint32(CH6_READ_ADDR, DMA_CONTROL_BLOCK_ADDR); 71 | cpu.writeUint32(CH6_TRANS_COUNT, 1); // we'll copy one word at a time 72 | cpu.writeUint32( 73 | CH6_CTRL_TRIG, 74 | EN | INCR_READ | (TREQ_PERMANENT << TREQ_SEL_SHIFT) | (2 << DATA_SIZE_SHIFT), 75 | ); 76 | 77 | expect(cpu.readUint32(CH6_CTRL_TRIG) & BUSY).toEqual(BUSY); 78 | 79 | // Now the DMA transfer should be running. Skip some clock cycles, allowing it to finish: 80 | clock.advance(32); 81 | 82 | // Check that the transfer has indeed completed 83 | expect(cpu.readUint32(CH2_AL3_READ_ADDR_TRIG)).toEqual(0); 84 | expect(cpu.readUint32(CH2_AL1_CTRL) & BUSY).toEqual(0); 85 | expect(cpu.readUint32(CH6_CTRL_TRIG) & BUSY).toEqual(0); 86 | expect(cpu.readUint32(INTR)).toEqual(bit(2) | bit(6)); 87 | 88 | // Assert that the data was copied correctly: 89 | expect(cpu.readUint16(DEST_ADDR + 0)).toEqual(0x10); 90 | expect(cpu.readUint16(DEST_ADDR + 4)).toEqual(0x20); 91 | expect(cpu.readUint16(DEST_ADDR + 8)).toEqual(0x30); 92 | expect(cpu.readUint16(DEST_ADDR + 12)).toEqual(0x40); 93 | expect(cpu.readUint16(DEST_ADDR + 16)).toEqual(0x50); 94 | expect(cpu.readUint16(DEST_ADDR + 20)).toEqual(0x60); 95 | expect(cpu.readUint16(DEST_ADDR + 24)).toEqual(0x70); 96 | expect(cpu.readUint16(DEST_ADDR + 28)).toEqual(0x80); 97 | expect(cpu.readUint16(DEST_ADDR + 32)).toEqual(0x0); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /src/peripherals/io.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { BasePeripheral, Peripheral } from './peripheral.js'; 3 | 4 | const GPIO_CTRL_LAST = 0x0ec; 5 | const INTR0 = 0xf0; 6 | const PROC0_INTE0 = 0x100; 7 | const PROC0_INTF0 = 0x110; 8 | const PROC0_INTS0 = 0x120; 9 | const PROC0_INTS3 = 0x12c; 10 | 11 | export class RPIO extends BasePeripheral implements Peripheral { 12 | constructor(rp2040: RP2040, name: string) { 13 | super(rp2040, name); 14 | } 15 | 16 | getPinFromOffset(offset: number) { 17 | const gpioIndex = offset >>> 3; 18 | return { 19 | gpio: this.rp2040.gpio[gpioIndex], 20 | isCtrl: !!(offset & 0x4), 21 | }; 22 | } 23 | 24 | readUint32(offset: number) { 25 | if (offset <= GPIO_CTRL_LAST) { 26 | const { gpio, isCtrl } = this.getPinFromOffset(offset); 27 | return isCtrl ? gpio.ctrl : gpio.status; 28 | } 29 | if (offset >= INTR0 && offset <= PROC0_INTS3) { 30 | const startIndex = (offset & 0xf) * 2; 31 | const register = offset & ~0xf; 32 | const { gpio } = this.rp2040; 33 | let result = 0; 34 | for (let index = 7; index >= 0; index--) { 35 | const pin = gpio[index + startIndex]; 36 | if (!pin) { 37 | continue; 38 | } 39 | result <<= 4; 40 | switch (register) { 41 | case INTR0: 42 | result |= pin.irqStatus; 43 | break; 44 | case PROC0_INTE0: 45 | result |= pin.irqEnableMask; 46 | break; 47 | case PROC0_INTF0: 48 | result |= pin.irqForceMask; 49 | break; 50 | case PROC0_INTS0: 51 | result |= (pin.irqStatus & pin.irqEnableMask) | pin.irqForceMask; 52 | break; 53 | } 54 | } 55 | return result; 56 | } 57 | return super.readUint32(offset); 58 | } 59 | 60 | writeUint32(offset: number, value: number) { 61 | if (offset <= GPIO_CTRL_LAST) { 62 | const { gpio, isCtrl } = this.getPinFromOffset(offset); 63 | if (isCtrl) { 64 | gpio.ctrl = value; 65 | gpio.checkForUpdates(); 66 | } 67 | return; 68 | } 69 | if (offset >= INTR0 && offset <= PROC0_INTS3) { 70 | const startIndex = (offset & 0xf) * 2; 71 | const register = offset & ~0xf; 72 | const { gpio } = this.rp2040; 73 | for (let index = 0; index < 8; index++) { 74 | const pin = gpio[index + startIndex]; 75 | if (!pin) { 76 | continue; 77 | } 78 | const pinValue = (value >> (index * 4)) & 0xf; 79 | const pinRawWriteValue = (this.rawWriteValue >> (index * 4)) & 0xf; 80 | switch (register) { 81 | case INTR0: 82 | pin.updateIRQValue(pinRawWriteValue); 83 | break; 84 | case PROC0_INTE0: 85 | if (pin.irqEnableMask !== pinValue) { 86 | pin.irqEnableMask = pinValue; 87 | this.rp2040.updateIOInterrupt(); 88 | } 89 | break; 90 | case PROC0_INTF0: 91 | if (pin.irqForceMask !== pinValue) { 92 | pin.irqForceMask = pinValue; 93 | this.rp2040.updateIOInterrupt(); 94 | } 95 | break; 96 | } 97 | } 98 | return; 99 | } 100 | 101 | super.writeUint32(offset, value); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/peripherals/pads.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { BasePeripheral, Peripheral } from './peripheral.js'; 3 | 4 | const VOLTAGE_SELECT = 0; 5 | const GPIO_FIRST = 0x4; 6 | const GPIO_LAST = 0x78; 7 | 8 | const QSPI_FIRST = 0x4; 9 | const QSPI_LAST = 0x18; 10 | 11 | export type IIOBank = 'qspi' | 'bank0'; 12 | 13 | export class RPPADS extends BasePeripheral implements Peripheral { 14 | voltageSelect = 0; 15 | 16 | private readonly firstPadRegister = this.bank === 'qspi' ? QSPI_FIRST : GPIO_FIRST; 17 | private readonly lastPadRegister = this.bank === 'qspi' ? QSPI_LAST : GPIO_LAST; 18 | 19 | constructor( 20 | rp2040: RP2040, 21 | name: string, 22 | readonly bank: IIOBank, 23 | ) { 24 | super(rp2040, name); 25 | } 26 | 27 | getPinFromOffset(offset: number) { 28 | const gpioIndex = (offset - this.firstPadRegister) >>> 2; 29 | if (this.bank === 'qspi') { 30 | return this.rp2040.qspi[gpioIndex]; 31 | } else { 32 | return this.rp2040.gpio[gpioIndex]; 33 | } 34 | } 35 | 36 | readUint32(offset: number) { 37 | if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) { 38 | const gpio = this.getPinFromOffset(offset); 39 | return gpio.padValue; 40 | } 41 | switch (offset) { 42 | case VOLTAGE_SELECT: 43 | return this.voltageSelect; 44 | } 45 | return super.readUint32(offset); 46 | } 47 | 48 | writeUint32(offset: number, value: number) { 49 | if (offset >= this.firstPadRegister && offset <= this.lastPadRegister) { 50 | const gpio = this.getPinFromOffset(offset); 51 | const oldInputEnable = gpio.inputEnable; 52 | gpio.padValue = value; 53 | gpio.checkForUpdates(); 54 | if (oldInputEnable !== gpio.inputEnable) { 55 | gpio.refreshInput(); 56 | } 57 | return; 58 | } 59 | switch (offset) { 60 | case VOLTAGE_SELECT: 61 | this.voltageSelect = value & 1; 62 | break; 63 | default: 64 | super.writeUint32(offset, value); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/peripherals/peripheral.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | 3 | const ATOMIC_NORMAL = 0; 4 | const ATOMIC_XOR = 1; 5 | const ATOMIC_SET = 2; 6 | const ATOMIC_CLEAR = 3; 7 | 8 | export function atomicUpdate(currentValue: number, atomicType: number, newValue: number) { 9 | switch (atomicType) { 10 | case ATOMIC_XOR: 11 | return currentValue ^ newValue; 12 | case ATOMIC_SET: 13 | return currentValue | newValue; 14 | case ATOMIC_CLEAR: 15 | return currentValue & ~newValue; 16 | default: 17 | console.warn('Atomic update called with invalid writeType', atomicType); 18 | return newValue; 19 | } 20 | } 21 | 22 | export interface Peripheral { 23 | readUint32(offset: number): number; 24 | writeUint32(offset: number, value: number): void; 25 | writeUint32Atomic(offset: number, value: number, atomicType: number): void; 26 | } 27 | 28 | export class BasePeripheral implements Peripheral { 29 | protected rawWriteValue = 0; 30 | 31 | constructor( 32 | protected rp2040: RP2040, 33 | readonly name: string, 34 | ) {} 35 | 36 | readUint32(offset: number) { 37 | this.warn(`Unimplemented peripheral read from 0x${offset.toString(16)}`); 38 | if (offset > 0x1000) { 39 | this.warn('Unimplemented read from peripheral in the atomic operation region'); 40 | } 41 | return 0xffffffff; 42 | } 43 | 44 | writeUint32(offset: number, value: number) { 45 | this.warn( 46 | `Unimplemented peripheral write to 0x${offset.toString(16)}: 0x${value.toString(16)}`, 47 | ); 48 | } 49 | 50 | writeUint32Atomic(offset: number, value: number, atomicType: number) { 51 | this.rawWriteValue = value; 52 | const newValue = 53 | atomicType != ATOMIC_NORMAL 54 | ? atomicUpdate(this.readUint32(offset), atomicType, value) 55 | : value; 56 | this.writeUint32(offset, newValue); 57 | } 58 | 59 | debug(msg: string) { 60 | this.rp2040.logger.debug(this.name, msg); 61 | } 62 | 63 | info(msg: string) { 64 | this.rp2040.logger.info(this.name, msg); 65 | } 66 | 67 | warn(msg: string) { 68 | this.rp2040.logger.warn(this.name, msg); 69 | } 70 | 71 | error(msg: string) { 72 | this.rp2040.logger.error(this.name, msg); 73 | } 74 | } 75 | 76 | export class UnimplementedPeripheral extends BasePeripheral {} 77 | -------------------------------------------------------------------------------- /src/peripherals/ppb.ts: -------------------------------------------------------------------------------- 1 | import { MAX_HARDWARE_IRQ } from '../irq.js'; 2 | import { RP2040 } from '../rp2040.js'; 3 | import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js'; 4 | import { BasePeripheral, Peripheral } from './peripheral.js'; 5 | 6 | export const CPUID = 0xd00; 7 | export const ICSR = 0xd04; 8 | export const VTOR = 0xd08; 9 | export const SHPR2 = 0xd1c; 10 | export const SHPR3 = 0xd20; 11 | 12 | const SYST_CSR = 0x010; // SysTick Control and Status Register 13 | const SYST_RVR = 0x014; // SysTick Reload Value Register 14 | const SYST_CVR = 0x018; // SysTick Current Value Register 15 | const SYST_CALIB = 0x01c; // SysTick Calibration Value Register 16 | const NVIC_ISER = 0x100; // Interrupt Set-Enable Register 17 | const NVIC_ICER = 0x180; // Interrupt Clear-Enable Register 18 | const NVIC_ISPR = 0x200; // Interrupt Set-Pending Register 19 | const NVIC_ICPR = 0x280; // Interrupt Clear-Pending Register 20 | 21 | // Interrupt priority registers: 22 | const NVIC_IPR0 = 0x400; 23 | const NVIC_IPR1 = 0x404; 24 | const NVIC_IPR2 = 0x408; 25 | const NVIC_IPR3 = 0x40c; 26 | const NVIC_IPR4 = 0x410; 27 | const NVIC_IPR5 = 0x414; 28 | const NVIC_IPR6 = 0x418; 29 | const NVIC_IPR7 = 0x41c; 30 | 31 | /** ICSR Bits */ 32 | const NMIPENDSET = 1 << 31; 33 | const PENDSVSET = 1 << 28; 34 | const PENDSVCLR = 1 << 27; 35 | const PENDSTSET = 1 << 26; 36 | const PENDSTCLR = 1 << 25; 37 | const ISRPREEMPT = 1 << 23; 38 | const ISRPENDING = 1 << 22; 39 | const VECTPENDING_MASK = 0x1ff; 40 | const VECTPENDING_SHIFT = 12; 41 | const VECTACTIVE_MASK = 0x1ff; 42 | const VECTACTIVE_SHIFT = 0; 43 | 44 | /** PPB stands for Private Periphral Bus. 45 | * These are peripherals that are part of the ARM Cortex Core, and there's one copy for each processor core. 46 | * 47 | * Included peripheral: NVIC, SysTick timer 48 | */ 49 | export class RPPPB extends BasePeripheral implements Peripheral { 50 | // Systick 51 | systickCountFlag = false; 52 | systickClkSource = false; 53 | systickIntEnable = false; 54 | systickReload = 0; 55 | readonly systickTimer = new Timer32(this.rp2040.clock, this.rp2040.clkSys); 56 | readonly systickAlarm = new Timer32PeriodicAlarm(this.systickTimer, () => { 57 | this.systickCountFlag = true; 58 | if (this.systickIntEnable) { 59 | this.rp2040.core.pendingSystick = true; 60 | this.rp2040.core.interruptsUpdated = true; 61 | } 62 | this.systickTimer.set(this.systickReload); 63 | }); 64 | 65 | constructor(rp2040: RP2040, name: string) { 66 | super(rp2040, name); 67 | this.systickTimer.top = 0xffffff; 68 | this.systickTimer.mode = TimerMode.Decrement; 69 | this.systickAlarm.target = 0; 70 | this.systickAlarm.enable = true; 71 | this.reset(); 72 | } 73 | 74 | reset() { 75 | this.writeUint32(SYST_CSR, 0); 76 | this.writeUint32(SYST_RVR, 0xffffff); 77 | this.systickTimer.set(0xffffff); 78 | } 79 | 80 | readUint32(offset: number) { 81 | const { rp2040 } = this; 82 | const { core } = rp2040; 83 | 84 | switch (offset) { 85 | case CPUID: 86 | return 0x410cc601; /* Verified against actual hardware */ 87 | 88 | case ICSR: { 89 | const pendingInterrupts = 90 | core.pendingInterrupts || core.pendingPendSV || core.pendingSystick || core.pendingSVCall; 91 | const vectPending = core.vectPending; 92 | return ( 93 | (core.pendingNMI ? NMIPENDSET : 0) | 94 | (core.pendingPendSV ? PENDSVSET : 0) | 95 | (core.pendingSystick ? PENDSTSET : 0) | 96 | (pendingInterrupts ? ISRPENDING : 0) | 97 | (vectPending << VECTPENDING_SHIFT) | 98 | ((core.IPSR & VECTACTIVE_MASK) << VECTACTIVE_SHIFT) 99 | ); 100 | } 101 | 102 | case VTOR: 103 | return core.VTOR; 104 | 105 | /* NVIC */ 106 | case NVIC_ISPR: 107 | return core.pendingInterrupts >>> 0; 108 | case NVIC_ICPR: 109 | return core.pendingInterrupts >>> 0; 110 | case NVIC_ISER: 111 | return core.enabledInterrupts >>> 0; 112 | case NVIC_ICER: 113 | return core.enabledInterrupts >>> 0; 114 | 115 | case NVIC_IPR0: 116 | case NVIC_IPR1: 117 | case NVIC_IPR2: 118 | case NVIC_IPR3: 119 | case NVIC_IPR4: 120 | case NVIC_IPR5: 121 | case NVIC_IPR6: 122 | case NVIC_IPR7: { 123 | const regIndex = (offset - NVIC_IPR0) >> 2; 124 | let result = 0; 125 | for (let byteIndex = 0; byteIndex < 4; byteIndex++) { 126 | const interruptNumber = regIndex * 4 + byteIndex; 127 | for (let priority = 0; priority < core.interruptPriorities.length; priority++) { 128 | if (core.interruptPriorities[priority] & (1 << interruptNumber)) { 129 | result |= priority << (8 * byteIndex + 6); 130 | } 131 | } 132 | } 133 | return result; 134 | } 135 | 136 | case SHPR2: 137 | return core.SHPR2; 138 | case SHPR3: 139 | return core.SHPR3; 140 | 141 | /* SysTick */ 142 | case SYST_CSR: { 143 | const countFlagValue = this.systickCountFlag ? 1 << 16 : 0; 144 | const clkSourceValue = this.systickClkSource ? 1 << 2 : 0; 145 | const tickIntValue = this.systickIntEnable ? 1 << 1 : 0; 146 | const enableFlagValue = this.systickTimer.enable ? 1 << 0 : 0; 147 | this.systickCountFlag = false; 148 | return countFlagValue | clkSourceValue | tickIntValue | enableFlagValue; 149 | } 150 | case SYST_CVR: 151 | return this.systickTimer.counter; 152 | case SYST_RVR: 153 | return this.systickReload; 154 | case SYST_CALIB: 155 | return 0x0000270f; 156 | } 157 | return super.readUint32(offset); 158 | } 159 | 160 | writeUint32(offset: number, value: number) { 161 | const { rp2040 } = this; 162 | const { core } = rp2040; 163 | 164 | const hardwareInterruptMask = (1 << MAX_HARDWARE_IRQ) - 1; 165 | 166 | switch (offset) { 167 | case ICSR: 168 | if (value & NMIPENDSET) { 169 | core.pendingNMI = true; 170 | core.interruptsUpdated = true; 171 | } 172 | if (value & PENDSVSET) { 173 | core.pendingPendSV = true; 174 | core.interruptsUpdated = true; 175 | } 176 | if (value & PENDSVCLR) { 177 | core.pendingPendSV = false; 178 | } 179 | if (value & PENDSTSET) { 180 | core.pendingSystick = true; 181 | core.interruptsUpdated = true; 182 | } 183 | if (value & PENDSTCLR) { 184 | core.pendingSystick = false; 185 | } 186 | return; 187 | 188 | case VTOR: 189 | core.VTOR = value; 190 | return; 191 | 192 | /* NVIC */ 193 | case NVIC_ISPR: 194 | core.pendingInterrupts |= value; 195 | core.interruptsUpdated = true; 196 | return; 197 | case NVIC_ICPR: 198 | core.pendingInterrupts &= ~value | hardwareInterruptMask; 199 | return; 200 | case NVIC_ISER: 201 | core.enabledInterrupts |= value; 202 | core.interruptsUpdated = true; 203 | return; 204 | case NVIC_ICER: 205 | core.enabledInterrupts &= ~value; 206 | return; 207 | 208 | case NVIC_IPR0: 209 | case NVIC_IPR1: 210 | case NVIC_IPR2: 211 | case NVIC_IPR3: 212 | case NVIC_IPR4: 213 | case NVIC_IPR5: 214 | case NVIC_IPR6: 215 | case NVIC_IPR7: { 216 | const regIndex = (offset - NVIC_IPR0) >> 2; 217 | for (let byteIndex = 0; byteIndex < 4; byteIndex++) { 218 | const interruptNumber = regIndex * 4 + byteIndex; 219 | const newPriority = (value >> (8 * byteIndex + 6)) & 0x3; 220 | for (let priority = 0; priority < core.interruptPriorities.length; priority++) { 221 | core.interruptPriorities[priority] &= ~(1 << interruptNumber); 222 | } 223 | core.interruptPriorities[newPriority] |= 1 << interruptNumber; 224 | } 225 | core.interruptsUpdated = true; 226 | return; 227 | } 228 | 229 | case SHPR2: 230 | core.SHPR2 = value; 231 | return; 232 | case SHPR3: 233 | core.SHPR3 = value; 234 | return; 235 | 236 | // SysTick 237 | case SYST_CSR: 238 | this.systickClkSource = value & (1 << 2) ? true : false; 239 | this.systickIntEnable = value & (1 << 1) ? true : false; 240 | this.systickTimer.enable = value & (1 << 0) ? true : false; 241 | return; 242 | case SYST_CVR: 243 | this.systickTimer.set(0); 244 | return; 245 | case SYST_RVR: 246 | this.systickReload = value; 247 | return; 248 | 249 | default: 250 | super.writeUint32(offset, value); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/peripherals/pwm.ts: -------------------------------------------------------------------------------- 1 | import { IClock } from '../clock/clock.js'; 2 | import { IRQ } from '../irq.js'; 3 | import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js'; 4 | import { DREQChannel } from './dma.js'; 5 | import { BasePeripheral, Peripheral } from './peripheral.js'; 6 | 7 | /** Control and status register */ 8 | const CHn_CSR = 0x00; 9 | /** 10 | * INT and FRAC form a fixed-point fractional number. 11 | * Counting rate is system clock frequency divided by this number. 12 | * Fractional division uses simple 1st-order sigma-delta. 13 | */ 14 | const CHn_DIV = 0x04; 15 | /** Direct access to the PWM counter */ 16 | const CHn_CTR = 0x08; 17 | /** Counter compare values */ 18 | const CHn_CC = 0x0c; 19 | /** Counter wrap value */ 20 | const CHn_TOP = 0x10; 21 | 22 | /** 23 | * This register aliases the CSR_EN bits for all channels. 24 | * Writing to this register allows multiple channels to be enabled 25 | * or disabled simultaneously, so they can run in perfect sync. 26 | * For each channel, there is only one physical EN register bit, 27 | * which can be accessed through here or CHx_CSR. 28 | */ 29 | const EN = 0xa0; 30 | /** Raw Interrupts */ 31 | const INTR = 0xa4; 32 | /** Interrupt Enable */ 33 | const INTE = 0xa8; 34 | /** Interrupt Force */ 35 | const INTF = 0xac; 36 | /** Interrupt status after masking & forcing */ 37 | const INTS = 0xb0; 38 | 39 | const INT_MASK = 0xff; 40 | 41 | /* CHn_CSR bits */ 42 | const CSR_PH_ADV = 1 << 7; 43 | const CSR_PH_RET = 1 << 6; 44 | const CSR_DIVMODE_SHIFT = 4; 45 | const CSR_DIVMODE_MASK = 0x3; 46 | const CSR_B_INV = 1 << 3; 47 | const CSR_A_INV = 1 << 2; 48 | const CSR_PH_CORRECT = 1 << 1; 49 | const CSR_EN = 1 << 0; 50 | 51 | enum PWMDivMode { 52 | FreeRunning, 53 | BGated, 54 | BRisingEdge, 55 | BFallingEdge, 56 | } 57 | 58 | class PWMChannel { 59 | readonly timer = new Timer32(this.clock, this.pwm.clockFreq); 60 | readonly alarmA = new Timer32PeriodicAlarm(this.timer, () => { 61 | this.setA(false); 62 | }); 63 | readonly alarmB = new Timer32PeriodicAlarm(this.timer, () => { 64 | this.setB(false); 65 | }); 66 | readonly alarmBottom = new Timer32PeriodicAlarm(this.timer, () => this.wrap()); 67 | 68 | csr: number = 0; 69 | div: number = 0; 70 | cc: number = 0; 71 | top: number = 0; 72 | lastBValue = false; 73 | countingUp = true; 74 | ccUpdated = false; 75 | topUpdated = false; 76 | tickCounter = 0; 77 | divMode = PWMDivMode.FreeRunning; 78 | 79 | // GPIO pin indices: Table 525. Mapping of PWM channels to GPIO pins on RP2040 80 | readonly pinA1 = this.index * 2; 81 | readonly pinB1 = this.index * 2 + 1; 82 | readonly pinA2 = this.index < 7 ? 16 + this.index * 2 : -1; 83 | readonly pinB2 = this.index < 7 ? 16 + this.index * 2 + 1 : -1; 84 | 85 | constructor( 86 | private pwm: RPPWM, 87 | readonly clock: IClock, 88 | readonly index: number, 89 | ) { 90 | this.alarmA.enable = true; 91 | this.alarmB.enable = true; 92 | this.alarmBottom.enable = true; 93 | } 94 | 95 | readRegister(offset: number) { 96 | switch (offset) { 97 | case CHn_CSR: 98 | return this.csr; 99 | case CHn_DIV: 100 | return this.div; 101 | case CHn_CTR: 102 | return this.timer.counter; 103 | case CHn_CC: 104 | return this.cc; 105 | case CHn_TOP: 106 | return this.top; 107 | } 108 | /* Shouldn't get here */ 109 | return 0; 110 | } 111 | 112 | writeRegister(offset: number, value: number) { 113 | switch (offset) { 114 | case CHn_CSR: 115 | if (value & CSR_EN && !(this.csr & CSR_EN)) { 116 | this.updateDoubleBuffered(); 117 | } 118 | this.csr = value & ~(CSR_PH_ADV | CSR_PH_RET); 119 | if (this.csr & CSR_PH_ADV) { 120 | this.timer.advance(1); 121 | } 122 | if (this.csr & CSR_PH_RET) { 123 | this.timer.advance(-1); 124 | } 125 | this.divMode = (this.csr >> CSR_DIVMODE_SHIFT) & CSR_DIVMODE_MASK; 126 | this.setBDirection(this.divMode === PWMDivMode.FreeRunning); 127 | this.updateEnable(); 128 | this.lastBValue = this.gpioBValue; 129 | this.timer.mode = value & CSR_PH_CORRECT ? TimerMode.ZigZag : TimerMode.Increment; 130 | break; 131 | case CHn_DIV: { 132 | this.div = value & 0x000f_ffff; 133 | const intValue = (value >> 4) & 0xff; 134 | const fracValue = value & 0xf; 135 | this.timer.prescaler = (intValue ? intValue : 256) + fracValue / 16; 136 | break; 137 | } 138 | case CHn_CTR: 139 | this.timer.set(value & 0xffff); 140 | break; 141 | case CHn_CC: 142 | this.cc = value; 143 | this.ccUpdated = true; 144 | break; 145 | case CHn_TOP: 146 | this.top = value & 0xffff; 147 | this.topUpdated = true; 148 | break; 149 | } 150 | } 151 | 152 | reset() { 153 | this.writeRegister(CHn_CSR, 0); 154 | this.writeRegister(CHn_DIV, 0x01 << 4); 155 | this.writeRegister(CHn_CTR, 0); 156 | this.writeRegister(CHn_CC, 0); 157 | this.writeRegister(CHn_TOP, 0xffff); 158 | this.countingUp = true; 159 | this.timer.enable = false; 160 | this.timer.reset(); 161 | } 162 | 163 | private updateDoubleBuffered() { 164 | if (this.ccUpdated) { 165 | this.alarmB.target = this.cc >>> 16; 166 | this.alarmA.target = this.cc & 0xffff; 167 | this.ccUpdated = false; 168 | } 169 | if (this.topUpdated) { 170 | this.timer.top = this.top; 171 | this.topUpdated = false; 172 | } 173 | } 174 | 175 | private wrap() { 176 | this.pwm.channelInterrupt(this.index); 177 | this.updateDoubleBuffered(); 178 | if (!(this.csr & CSR_PH_CORRECT)) { 179 | this.setA(this.alarmA.target > 0); 180 | this.setB(this.alarmB.target > 0); 181 | } 182 | } 183 | 184 | setA(value: boolean) { 185 | if (this.csr & CSR_A_INV) { 186 | value = !value; 187 | } 188 | this.pwm.gpioSet(this.pinA1, value); 189 | if (this.pinA2 >= 0) { 190 | this.pwm.gpioSet(this.pinA2, value); 191 | } 192 | } 193 | 194 | setB(value: boolean) { 195 | if (this.csr & CSR_B_INV) { 196 | value = !value; 197 | } 198 | this.pwm.gpioSet(this.pinB1, value); 199 | if (this.pinB2 >= 0) { 200 | this.pwm.gpioSet(this.pinB2, value); 201 | } 202 | } 203 | 204 | get gpioBValue() { 205 | return ( 206 | this.pwm.gpioRead(this.pinB1) || (this.pinB2 > 0 ? this.pwm.gpioRead(this.pinB2) : false) 207 | ); 208 | } 209 | 210 | setBDirection(value: boolean) { 211 | this.pwm.gpioSetDir(this.pinB1, value); 212 | if (this.pinB2 >= 0) { 213 | this.pwm.gpioSetDir(this.pinB2, value); 214 | } 215 | } 216 | 217 | gpioBChanged() { 218 | const value = this.gpioBValue; 219 | if (value === this.lastBValue) { 220 | return; 221 | } 222 | this.lastBValue = value; 223 | switch (this.divMode) { 224 | case PWMDivMode.BGated: 225 | this.updateEnable(); 226 | break; 227 | 228 | case PWMDivMode.BRisingEdge: 229 | if (value) { 230 | this.tickCounter++; 231 | } 232 | break; 233 | 234 | case PWMDivMode.BFallingEdge: 235 | if (!value) { 236 | this.tickCounter++; 237 | } 238 | break; 239 | } 240 | 241 | if (this.tickCounter >= this.timer.prescaler) { 242 | this.timer.advance(1); 243 | this.tickCounter -= this.timer.prescaler; 244 | } 245 | } 246 | 247 | updateEnable() { 248 | const { csr, divMode } = this; 249 | const enable = !!(csr & CSR_EN); 250 | this.timer.enable = 251 | enable && 252 | (divMode === PWMDivMode.FreeRunning || (divMode === PWMDivMode.BGated && this.gpioBValue)); 253 | } 254 | 255 | set en(value: number) { 256 | if (value && !(this.csr & CSR_EN)) { 257 | this.updateDoubleBuffered(); 258 | } 259 | if (value) { 260 | this.csr |= CSR_EN; 261 | } else { 262 | this.csr &= ~CSR_EN; 263 | } 264 | this.updateEnable(); 265 | } 266 | } 267 | 268 | export class RPPWM extends BasePeripheral implements Peripheral { 269 | readonly channels = [ 270 | new PWMChannel(this, this.rp2040.clock, 0), 271 | new PWMChannel(this, this.rp2040.clock, 1), 272 | new PWMChannel(this, this.rp2040.clock, 2), 273 | new PWMChannel(this, this.rp2040.clock, 3), 274 | new PWMChannel(this, this.rp2040.clock, 4), 275 | new PWMChannel(this, this.rp2040.clock, 5), 276 | new PWMChannel(this, this.rp2040.clock, 6), 277 | new PWMChannel(this, this.rp2040.clock, 7), 278 | ]; 279 | private intRaw = 0; 280 | private intEnable = 0; 281 | private intForce = 0; 282 | 283 | gpioValue = 0; 284 | gpioDirection = 0; 285 | 286 | get intStatus() { 287 | return (this.intRaw & this.intEnable) | this.intForce; 288 | } 289 | 290 | readUint32(offset: number) { 291 | if (offset < EN) { 292 | const channel = Math.floor(offset / 0x14); 293 | return this.channels[channel].readRegister(offset % 0x14); 294 | } 295 | switch (offset) { 296 | case EN: 297 | return ( 298 | (this.channels[7].en << 7) | 299 | (this.channels[6].en << 6) | 300 | (this.channels[5].en << 5) | 301 | (this.channels[4].en << 4) | 302 | (this.channels[3].en << 3) | 303 | (this.channels[2].en << 2) | 304 | (this.channels[1].en << 1) | 305 | (this.channels[0].en << 0) 306 | ); 307 | case INTR: 308 | return this.intRaw; 309 | case INTE: 310 | return this.intEnable; 311 | case INTF: 312 | return this.intForce; 313 | case INTS: 314 | return this.intStatus; 315 | } 316 | return super.readUint32(offset); 317 | } 318 | 319 | writeUint32(offset: number, value: number) { 320 | if (offset < EN) { 321 | const channel = Math.floor(offset / 0x14); 322 | return this.channels[channel].writeRegister(offset % 0x14, value); 323 | } 324 | 325 | switch (offset) { 326 | case EN: 327 | this.channels[7].en = value & (1 << 7); 328 | this.channels[6].en = value & (1 << 6); 329 | this.channels[5].en = value & (1 << 5); 330 | this.channels[4].en = value & (1 << 4); 331 | this.channels[3].en = value & (1 << 3); 332 | this.channels[2].en = value & (1 << 2); 333 | this.channels[1].en = value & (1 << 1); 334 | this.channels[0].en = value & (1 << 0); 335 | break; 336 | case INTR: 337 | this.intRaw &= ~(value & INT_MASK); 338 | this.checkInterrupts(); 339 | break; 340 | case INTE: 341 | this.intEnable = value & INT_MASK; 342 | this.checkInterrupts(); 343 | break; 344 | case INTF: 345 | this.intForce = value & INT_MASK; 346 | this.checkInterrupts(); 347 | break; 348 | default: 349 | super.writeUint32(offset, value); 350 | } 351 | } 352 | 353 | get clockFreq() { 354 | return this.rp2040.clkSys; 355 | } 356 | 357 | channelInterrupt(index: number) { 358 | this.intRaw |= 1 << index; 359 | this.checkInterrupts(); 360 | 361 | // We also set the DMA Request (DREQ) for the channel 362 | this.rp2040.dma.setDREQ(DREQChannel.DREQ_PWM_WRAP0 + index); 363 | } 364 | 365 | checkInterrupts() { 366 | this.rp2040.setInterrupt(IRQ.PWM_WRAP, !!this.intStatus); 367 | } 368 | 369 | gpioSet(index: number, value: boolean) { 370 | const bit = 1 << index; 371 | const newGpioValue = value ? this.gpioValue | bit : this.gpioValue & ~bit; 372 | if (this.gpioValue != newGpioValue) { 373 | this.gpioValue = newGpioValue; 374 | this.rp2040.gpio[index].checkForUpdates(); 375 | } 376 | } 377 | 378 | gpioSetDir(index: number, output: boolean) { 379 | const bit = 1 << index; 380 | const newGpioDirection = output ? this.gpioDirection | bit : this.gpioDirection & ~bit; 381 | if (this.gpioDirection != newGpioDirection) { 382 | this.gpioDirection = newGpioDirection; 383 | this.rp2040.gpio[index].checkForUpdates(); 384 | } 385 | } 386 | 387 | gpioRead(index: number) { 388 | return this.rp2040.gpio[index].inputValue; 389 | } 390 | 391 | gpioOnInput(index: number) { 392 | if (this.gpioDirection && 1 << index) { 393 | return; 394 | } 395 | for (const channel of this.channels) { 396 | if (channel.pinB1 === index || channel.pinB2 === index) { 397 | channel.gpioBChanged(); 398 | } 399 | } 400 | } 401 | 402 | reset() { 403 | this.gpioDirection = 0xffffffff; 404 | for (const channel of this.channels) { 405 | channel.reset(); 406 | } 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/peripherals/reset.ts: -------------------------------------------------------------------------------- 1 | import { BasePeripheral, Peripheral } from './peripheral.js'; 2 | 3 | const RESET = 0x0; //Reset control. 4 | const WDSEL = 0x4; //Watchdog select. 5 | const RESET_DONE = 0x8; //Reset Done 6 | 7 | export class RPReset extends BasePeripheral implements Peripheral { 8 | private reset: number = 0; 9 | private wdsel: number = 0; 10 | private reset_done: number = 0x1ffffff; 11 | 12 | readUint32(offset: number) { 13 | switch (offset) { 14 | case RESET: 15 | return this.reset; 16 | case WDSEL: 17 | return this.wdsel; 18 | case RESET_DONE: 19 | return this.reset_done; 20 | } 21 | return super.readUint32(offset); 22 | } 23 | 24 | writeUint32(offset: number, value: number) { 25 | switch (offset) { 26 | case RESET: 27 | this.reset = value & 0x1ffffff; 28 | break; 29 | case WDSEL: 30 | this.wdsel = value & 0x1ffffff; 31 | break; 32 | default: 33 | super.writeUint32(offset, value); 34 | break; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/peripherals/rtc.ts: -------------------------------------------------------------------------------- 1 | import { BasePeripheral, Peripheral } from './peripheral.js'; 2 | 3 | const RTC_SETUP0 = 0x04; 4 | const RTC_SETUP1 = 0x08; 5 | const RTC_CTRL = 0x0c; 6 | const IRQ_SETUP_0 = 0x10; 7 | const RTC_RTC1 = 0x18; 8 | const RTC_RTC0 = 0x1c; 9 | 10 | const RTC_ENABLE_BITS = 0x01; 11 | const RTC_ACTIVE_BITS = 0x2; 12 | const RTC_LOAD_BITS = 0x10; 13 | 14 | const SETUP_0_YEAR_SHIFT = 12; 15 | const SETUP_0_YEAR_MASK = 0xfff; 16 | const SETUP_0_MONTH_SHIFT = 8; 17 | const SETUP_0_MONTH_MASK = 0xf; 18 | const SETUP_0_DAY_SHIFT = 0; 19 | const SETUP_0_DAY_MASK = 0x1f; 20 | 21 | const SETUP_1_DOTW_SHIFT = 24; 22 | const SETUP_1_DOTW_MASK = 0x7; 23 | const SETUP_1_HOUR_SHIFT = 16; 24 | const SETUP_1_HOUR_MASK = 0x1f; 25 | const SETUP_1_MIN_SHIFT = 8; 26 | const SETUP_1_MIN_MASK = 0x3f; 27 | const SETUP_1_SEC_SHIFT = 0; 28 | const SETUP_1_SEC_MASK = 0x3f; 29 | 30 | const RTC_0_YEAR_SHIFT = 12; 31 | const RTC_0_YEAR_MASK = 0xfff; 32 | const RTC_0_MONTH_SHIFT = 8; 33 | const RTC_0_MONTH_MASK = 0xf; 34 | const RTC_0_DAY_SHIFT = 0; 35 | const RTC_0_DAY_MASK = 0x1f; 36 | 37 | const RTC_1_DOTW_SHIFT = 24; 38 | const RTC_1_DOTW_MASK = 0x7; 39 | const RTC_1_HOUR_SHIFT = 16; 40 | const RTC_1_HOUR_MASK = 0x1f; 41 | const RTC_1_MIN_SHIFT = 8; 42 | const RTC_1_MIN_MASK = 0x3f; 43 | const RTC_1_SEC_SHIFT = 0; 44 | const RTC_1_SEC_MASK = 0x3f; 45 | 46 | export class RP2040RTC extends BasePeripheral implements Peripheral { 47 | setup0 = 0; 48 | setup1 = 0; 49 | ctrl = 0; 50 | baseline = new Date(2021, 0, 1); 51 | baselineNanos = 0; 52 | 53 | readUint32(offset: number) { 54 | const date = new Date( 55 | this.baseline.getTime() + (this.rp2040.clock.nanos - this.baselineNanos) / 1_000_000, 56 | ); 57 | switch (offset) { 58 | case RTC_SETUP0: 59 | return this.setup0; 60 | case RTC_SETUP1: 61 | return this.setup1; 62 | case RTC_CTRL: 63 | return this.ctrl; 64 | case IRQ_SETUP_0: 65 | return 0; 66 | case RTC_RTC1: 67 | return ( 68 | ((date.getFullYear() & RTC_0_YEAR_MASK) << RTC_0_YEAR_SHIFT) | 69 | (((date.getMonth() + 1) & RTC_0_MONTH_MASK) << RTC_0_MONTH_SHIFT) | 70 | ((date.getDate() & RTC_0_DAY_MASK) << RTC_0_DAY_SHIFT) 71 | ); 72 | case RTC_RTC0: 73 | return ( 74 | ((date.getDay() & RTC_1_DOTW_MASK) << RTC_1_DOTW_SHIFT) | 75 | ((date.getHours() & RTC_1_HOUR_MASK) << RTC_1_HOUR_SHIFT) | 76 | ((date.getMinutes() & RTC_1_MIN_MASK) << RTC_1_MIN_SHIFT) | 77 | ((date.getSeconds() & RTC_1_SEC_MASK) << RTC_1_SEC_SHIFT) 78 | ); 79 | default: 80 | break; 81 | } 82 | return super.readUint32(offset); 83 | } 84 | 85 | writeUint32(offset: number, value: number) { 86 | switch (offset) { 87 | case RTC_SETUP0: 88 | this.setup0 = value; 89 | break; 90 | case RTC_SETUP1: 91 | this.setup1 = value; 92 | break; 93 | case RTC_CTRL: 94 | // Though RTC_LOAD_BITS is type SC and should be cleared on next cycle, pico-sdk write 95 | // RTC_LOAD_BITS & RTC_ENABLE_BITS seperatly. 96 | // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c#L76-L80 97 | if (value & RTC_LOAD_BITS) { 98 | this.ctrl |= RTC_LOAD_BITS; 99 | } 100 | if (value & RTC_ENABLE_BITS) { 101 | this.ctrl |= RTC_ENABLE_BITS; 102 | this.ctrl |= RTC_ACTIVE_BITS; 103 | if (this.ctrl & RTC_LOAD_BITS) { 104 | const year = (this.setup0 >> SETUP_0_YEAR_SHIFT) & SETUP_0_YEAR_MASK; 105 | const month = (this.setup0 >> SETUP_0_MONTH_SHIFT) & SETUP_0_MONTH_MASK; 106 | const day = (this.setup0 >> SETUP_0_DAY_SHIFT) & SETUP_0_DAY_MASK; 107 | const hour = (this.setup1 >> SETUP_1_HOUR_SHIFT) & SETUP_1_HOUR_MASK; 108 | const min = (this.setup1 >> SETUP_1_MIN_SHIFT) & SETUP_1_MIN_MASK; 109 | const sec = (this.setup1 >> SETUP_1_SEC_SHIFT) & SETUP_1_SEC_MASK; 110 | this.baseline = new Date(year, month - 1, day, hour, min, sec); 111 | this.baselineNanos = this.rp2040.clock.nanos; 112 | this.ctrl &= ~RTC_LOAD_BITS; 113 | } 114 | } else { 115 | this.ctrl &= ~RTC_ENABLE_BITS; 116 | this.ctrl &= ~RTC_ACTIVE_BITS; 117 | } 118 | break; 119 | default: 120 | super.writeUint32(offset, value); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/peripherals/spi.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { FIFO } from '../utils/fifo.js'; 3 | import { DREQChannel } from './dma.js'; 4 | import { BasePeripheral, Peripheral } from './peripheral.js'; 5 | 6 | const SSPCR0 = 0x000; // Control register 0, SSPCR0 on page 3-4 7 | const SSPCR1 = 0x004; // Control register 1, SSPCR1 on page 3-5 8 | const SSPDR = 0x008; // Data register, SSPDR on page 3-6 9 | const SSPSR = 0x00c; // Status register, SSPSR on page 3-7 10 | const SSPCPSR = 0x010; // Clock prescale register, SSPCPSR on page 3-8 11 | const SSPIMSC = 0x014; // Interrupt mask set or clear register, SSPIMSC on page 3-9 12 | const SSPRIS = 0x018; // Raw interrupt status register, SSPRIS on page 3-10 13 | const SSPMIS = 0x01c; // Masked interrupt status register, SSPMIS on page 3-11 14 | const SSPICR = 0x020; // Interrupt clear register, SSPICR on page 3-11 15 | const SSPDMACR = 0x024; // DMA control register, SSPDMACR on page 3-12 16 | const SSPPERIPHID0 = 0xfe0; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13 17 | const SSPPERIPHID1 = 0xfe4; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13 18 | const SSPPERIPHID2 = 0xfe8; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13 19 | const SSPPERIPHID3 = 0xfec; // Peripheral identification registers, SSPPeriphID0-3 on page 3-13 20 | const SSPPCELLID0 = 0xff0; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16 21 | const SSPPCELLID1 = 0xff4; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16 22 | const SSPPCELLID2 = 0xff8; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16 23 | const SSPPCELLID3 = 0xffc; // PrimeCell identification registers, SSPPCellID0-3 on page 3-16 24 | 25 | // SSPCR0 bits: 26 | const SCR_MASK = 0xff; 27 | const SCR_SHIFT = 8; 28 | const SPH = 1 << 7; 29 | const SPO = 1 << 6; 30 | const FRF_MASK = 0x3; 31 | const FRF_SHIFT = 4; 32 | const DSS_MASK = 0xf; 33 | const DSS_SHIFT = 0; 34 | 35 | // SSPCR1 bits: 36 | const SOD = 1 << 3; 37 | const MS = 1 << 2; 38 | const SSE = 1 << 1; 39 | const LBM = 1 << 0; 40 | 41 | // SSPSR bits: 42 | const BSY = 1 << 4; 43 | const RFF = 1 << 3; 44 | const RNE = 1 << 2; 45 | const TNF = 1 << 1; 46 | const TFE = 1 << 0; 47 | 48 | // SSPCPSR bits: 49 | const CPSDVSR_MASK = 0xfe; 50 | const CPSDVSR_SHIFT = 0; 51 | 52 | // SSPDMACR bits: 53 | const TXDMAE = 1 << 1; 54 | const RXDMAE = 1 << 0; 55 | 56 | // Interrupts: 57 | const SSPTXINTR = 1 << 3; 58 | const SSPRXINTR = 1 << 2; 59 | const SSPRTINTR = 1 << 1; 60 | const SSPRORINTR = 1 << 0; 61 | 62 | export interface ISPIDMAChannels { 63 | rx: DREQChannel; 64 | tx: DREQChannel; 65 | } 66 | 67 | export class RPSPI extends BasePeripheral implements Peripheral { 68 | readonly rxFIFO = new FIFO(8); 69 | readonly txFIFO = new FIFO(8); 70 | 71 | // User provided callbacks 72 | onTransmit: (value: number) => void = () => this.completeTransmit(0); 73 | 74 | private busy = false; 75 | private control0 = 0; 76 | private control1 = 0; 77 | private dmaControl = 0; 78 | private clockDivisor = 0; 79 | private intRaw = 0; 80 | private intEnable = 0; 81 | 82 | get intStatus() { 83 | return this.intRaw & this.intEnable; 84 | } 85 | 86 | get enabled() { 87 | return !!(this.control1 & SSE); 88 | } 89 | 90 | /** Data size in bits: 4 to 16 bits */ 91 | get dataBits() { 92 | return ((this.control0 >> DSS_SHIFT) & DSS_MASK) + 1; 93 | } 94 | 95 | get masterMode() { 96 | return !(this.control0 & MS); 97 | } 98 | 99 | get spiMode() { 100 | const cpol = this.control0 & SPO; 101 | const cpha = this.control0 & SPH; 102 | return cpol ? (cpha ? 2 : 3) : cpha ? 1 : 0; 103 | } 104 | 105 | get clockFrequency() { 106 | if (!this.clockDivisor) { 107 | return 0; 108 | } 109 | 110 | const scr = (this.control0 >> SCR_SHIFT) & SCR_MASK; 111 | return this.rp2040.clkPeri / (this.clockDivisor * (1 + scr)); 112 | } 113 | 114 | private updateDMATx() { 115 | if (this.txFIFO.full) { 116 | this.rp2040.dma.clearDREQ(this.dreq.tx); 117 | } else { 118 | this.rp2040.dma.setDREQ(this.dreq.tx); 119 | } 120 | } 121 | 122 | private updateDMARx() { 123 | if (this.rxFIFO.empty) { 124 | this.rp2040.dma.clearDREQ(this.dreq.rx); 125 | } else { 126 | this.rp2040.dma.setDREQ(this.dreq.rx); 127 | } 128 | } 129 | 130 | constructor( 131 | rp2040: RP2040, 132 | name: string, 133 | readonly irq: number, 134 | readonly dreq: ISPIDMAChannels, 135 | ) { 136 | super(rp2040, name); 137 | this.updateDMATx(); 138 | this.updateDMARx(); 139 | } 140 | 141 | private doTX() { 142 | if (!this.busy && !this.txFIFO.empty) { 143 | const value = this.txFIFO.pull(); 144 | this.busy = true; 145 | this.onTransmit(value); 146 | this.fifosUpdated(); 147 | } 148 | } 149 | 150 | completeTransmit(rxValue: number) { 151 | this.busy = false; 152 | if (!this.rxFIFO.full) { 153 | this.rxFIFO.push(rxValue); 154 | } else { 155 | this.intRaw |= SSPRORINTR; 156 | } 157 | this.fifosUpdated(); 158 | this.doTX(); 159 | } 160 | 161 | checkInterrupts() { 162 | this.rp2040.setInterrupt(this.irq, !!this.intStatus); 163 | } 164 | 165 | private fifosUpdated() { 166 | const prevStatus = this.intStatus; 167 | if (this.txFIFO.itemCount <= this.txFIFO.size / 2) { 168 | this.intRaw |= SSPTXINTR; 169 | } else { 170 | this.intRaw &= ~SSPTXINTR; 171 | } 172 | if (this.rxFIFO.itemCount >= this.rxFIFO.size / 2) { 173 | this.intRaw |= SSPRXINTR; 174 | } else { 175 | this.intRaw &= ~SSPRXINTR; 176 | } 177 | if (this.intStatus !== prevStatus) { 178 | this.checkInterrupts(); 179 | } 180 | 181 | this.updateDMATx(); 182 | this.updateDMARx(); 183 | } 184 | 185 | readUint32(offset: number) { 186 | switch (offset) { 187 | case SSPCR0: 188 | return this.control0; 189 | case SSPCR1: 190 | return this.control1; 191 | case SSPDR: 192 | if (!this.rxFIFO.empty) { 193 | const value = this.rxFIFO.pull(); 194 | this.fifosUpdated(); 195 | return value; 196 | } 197 | return 0; 198 | case SSPSR: 199 | return ( 200 | (this.busy || !this.txFIFO.empty ? BSY : 0) | 201 | (this.rxFIFO.full ? RFF : 0) | 202 | (!this.rxFIFO.empty ? RNE : 0) | 203 | (!this.txFIFO.full ? TNF : 0) | 204 | (this.txFIFO.empty ? TFE : 0) 205 | ); 206 | case SSPCPSR: 207 | return this.clockDivisor; 208 | case SSPIMSC: 209 | return this.intEnable; 210 | case SSPRIS: 211 | return this.intRaw; 212 | case SSPMIS: 213 | return this.intStatus; 214 | case SSPDMACR: 215 | return this.dmaControl; 216 | case SSPPERIPHID0: 217 | return 0x22; 218 | case SSPPERIPHID1: 219 | return 0x10; 220 | case SSPPERIPHID2: 221 | return 0x34; 222 | case SSPPERIPHID3: 223 | return 0x00; 224 | case SSPPCELLID0: 225 | return 0x0d; 226 | case SSPPCELLID1: 227 | return 0xf0; 228 | case SSPPCELLID2: 229 | return 0x05; 230 | case SSPPCELLID3: 231 | return 0xb1; 232 | } 233 | return super.readUint32(offset); 234 | } 235 | 236 | writeUint32(offset: number, value: number) { 237 | switch (offset) { 238 | case SSPCR0: 239 | this.control0 = value; 240 | return; 241 | case SSPCR1: 242 | this.control1 = value; 243 | return; 244 | case SSPDR: 245 | if (!this.txFIFO.full) { 246 | // decoded with respect to SSPCR0.DSS 247 | this.txFIFO.push(value & ((1 << this.dataBits) - 1)); 248 | this.doTX(); 249 | this.fifosUpdated(); 250 | } 251 | return; 252 | case SSPCPSR: 253 | this.clockDivisor = value & CPSDVSR_MASK; 254 | return; 255 | case SSPIMSC: 256 | this.intEnable = value; 257 | this.checkInterrupts(); 258 | return; 259 | case SSPDMACR: 260 | this.dmaControl = value; 261 | return; 262 | case SSPICR: 263 | this.intRaw &= ~(value & (SSPRTINTR | SSPRORINTR)); 264 | this.checkInterrupts(); 265 | return; 266 | default: 267 | super.writeUint32(offset, value); 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/peripherals/ssi.ts: -------------------------------------------------------------------------------- 1 | import { BasePeripheral, Peripheral } from './peripheral.js'; 2 | 3 | /* See RP2040 datasheet sect 4.10.13 */ 4 | const SSI_CTRLR0 = 0x00000000; 5 | const SSI_CTRLR1 = 0x00000004; 6 | const SSI_SSIENR = 0x00000008; 7 | const SSI_MWCR = 0x0000000c; 8 | const SSI_SER = 0x00000010; 9 | const SSI_BAUDR = 0x00000014; 10 | const SSI_TXFTLR = 0x00000018; 11 | const SSI_RXFTLR = 0x0000001c; 12 | const SSI_TXFLR = 0x00000020; 13 | const SSI_RXFLR = 0x00000024; 14 | const SSI_SR = 0x00000028; 15 | const SSI_SR_TFNF_BITS = 0x00000002; 16 | const SSI_SR_TFE_BITS = 0x00000004; 17 | const SSI_SR_RFNE_BITS = 0x00000008; 18 | const SSI_IMR = 0x0000002c; 19 | const SSI_ISR = 0x00000030; 20 | const SSI_RISR = 0x00000034; 21 | const SSI_TXOICR = 0x00000038; 22 | const SSI_RXOICR = 0x0000003c; 23 | const SSI_RXUICR = 0x00000040; 24 | const SSI_MSTICR = 0x00000044; 25 | const SSI_ICR = 0x00000048; 26 | const SSI_DMACR = 0x0000004c; 27 | const SSI_DMATDLR = 0x00000050; 28 | const SSI_DMARDLR = 0x00000054; 29 | /** Identification register */ 30 | const SSI_IDR = 0x00000058; 31 | const SSI_VERSION_ID = 0x0000005c; 32 | const SSI_DR0 = 0x00000060; 33 | const SSI_RX_SAMPLE_DLY = 0x000000f0; 34 | const SSI_SPI_CTRL_R0 = 0x000000f4; 35 | const SSI_TXD_DRIVE_EDGE = 0x000000f8; 36 | 37 | const CMD_READ_STATUS = 0x05; 38 | 39 | export class RPSSI extends BasePeripheral implements Peripheral { 40 | private dr0 = 0; 41 | private txflr = 0; 42 | private rxflr = 0; 43 | private baudr = 0; 44 | private crtlr0 = 0; 45 | private crtlr1 = 0; 46 | private ssienr = 0; 47 | private spictlr0 = 0; 48 | private rxsampldly = 0; 49 | private txddriveedge = 0; 50 | 51 | readUint32(offset: number) { 52 | switch (offset) { 53 | case SSI_TXFLR: 54 | return this.txflr; 55 | case SSI_RXFLR: 56 | return this.rxflr; 57 | case SSI_CTRLR0: 58 | return this.crtlr0; /* & 0x017FFFFF = b23,b25..31 reserved */ 59 | case SSI_CTRLR1: 60 | return this.crtlr1; 61 | case SSI_SSIENR: 62 | return this.ssienr; 63 | case SSI_BAUDR: 64 | return this.baudr; 65 | case SSI_SR: 66 | return SSI_SR_TFE_BITS | SSI_SR_RFNE_BITS | SSI_SR_TFNF_BITS; 67 | case SSI_IDR: 68 | return 0x51535049; 69 | case SSI_VERSION_ID: 70 | return 0x3430312a; 71 | case SSI_RX_SAMPLE_DLY: 72 | return this.rxsampldly; 73 | case SSI_TXD_DRIVE_EDGE: 74 | return this.txddriveedge; 75 | case SSI_SPI_CTRL_R0: 76 | return this.spictlr0; /* b6,7,10,19..23 reserved */ 77 | case SSI_DR0: 78 | return this.dr0; 79 | } 80 | return super.readUint32(offset); 81 | } 82 | 83 | writeUint32(offset: number, value: number) { 84 | switch (offset) { 85 | case SSI_TXFLR: 86 | this.txflr = value; 87 | return; 88 | case SSI_RXFLR: 89 | this.rxflr = value; 90 | return; 91 | case SSI_CTRLR0: 92 | this.crtlr0 = value; /* & 0x017FFFFF = b23,b25..31 reserved */ 93 | return; 94 | case SSI_CTRLR1: 95 | this.crtlr1 = value; 96 | return; 97 | case SSI_SSIENR: 98 | this.ssienr = value; 99 | return; 100 | case SSI_BAUDR: 101 | this.baudr = value; 102 | return; 103 | case SSI_RX_SAMPLE_DLY: 104 | this.rxsampldly = value & 0xff; 105 | return; 106 | case SSI_TXD_DRIVE_EDGE: 107 | this.txddriveedge = value & 0xff; 108 | return; 109 | case SSI_SPI_CTRL_R0: 110 | this.spictlr0 = value; 111 | return; 112 | case SSI_DR0: 113 | if (value === CMD_READ_STATUS) { 114 | this.dr0 = 0; // tell stage2 that we completed a write 115 | } 116 | return; 117 | default: 118 | super.writeUint32(offset, value); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/peripherals/syscfg.ts: -------------------------------------------------------------------------------- 1 | import { BasePeripheral, Peripheral } from './peripheral.js'; 2 | 3 | const PROC0_NMI_MASK = 0; 4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 5 | const PROC1_NMI_MASK = 4; 6 | 7 | export class RP2040SysCfg extends BasePeripheral implements Peripheral { 8 | readUint32(offset: number) { 9 | switch (offset) { 10 | case PROC0_NMI_MASK: 11 | return this.rp2040.core.interruptNMIMask; 12 | } 13 | return super.readUint32(offset); 14 | } 15 | 16 | writeUint32(offset: number, value: number) { 17 | switch (offset) { 18 | case PROC0_NMI_MASK: 19 | this.rp2040.core.interruptNMIMask = value; 20 | break; 21 | 22 | default: 23 | super.writeUint32(offset, value); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/peripherals/sysinfo.ts: -------------------------------------------------------------------------------- 1 | import { BasePeripheral, Peripheral } from './peripheral.js'; 2 | 3 | const CHIP_ID = 0; 4 | const PLATFORM = 0x4; 5 | const GITREF_RP2040 = 0x40; 6 | 7 | export class RP2040SysInfo extends BasePeripheral implements Peripheral { 8 | readUint32(offset: number) { 9 | // All the values here were verified against the silicon 10 | switch (offset) { 11 | case CHIP_ID: 12 | return 0x10002927; 13 | 14 | case PLATFORM: 15 | return 0x00000002; 16 | 17 | case GITREF_RP2040: 18 | return 0xe0c912e8; 19 | } 20 | return super.readUint32(offset); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/peripherals/tbman.ts: -------------------------------------------------------------------------------- 1 | import { BasePeripheral, Peripheral } from './peripheral.js'; 2 | const PLATFORM = 0; 3 | const ASIC = 1; 4 | 5 | export class RPTBMAN extends BasePeripheral implements Peripheral { 6 | readUint32(offset: number) { 7 | switch (offset) { 8 | case PLATFORM: 9 | return ASIC; 10 | default: 11 | return super.readUint32(offset); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/peripherals/timer.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { MockClock } from '../clock/mock-clock.js'; 3 | import { RP2040 } from '../rp2040.js'; 4 | 5 | const ALARM1 = 0x40054014; 6 | const ALARM2 = 0x40054018; 7 | const ALARM3 = 0x4005401c; 8 | const ARMED = 0x40054020; 9 | const INTR = 0x40054034; 10 | const INTR_CLEAR = INTR | 0x3000; 11 | const INTE = 0x40054038; 12 | const INTF = 0x4005403c; 13 | const INTS = 0x40054040; 14 | 15 | describe('RPTimer', () => { 16 | describe('Alarms', () => { 17 | it('should set Alarm 1 to armed when writing to ALARM1 register', () => { 18 | const rp2040 = new RP2040(new MockClock()); 19 | rp2040.writeUint32(ALARM1, 0x1000); 20 | expect(rp2040.readUint32(ARMED)).toEqual(0x2); 21 | }); 22 | 23 | it('should disarm Alarm 2 when writing 0x4 to the ARMED register', () => { 24 | const rp2040 = new RP2040(); 25 | rp2040.writeUint32(ALARM2, 0x1000); 26 | expect(rp2040.readUint32(ARMED)).toEqual(0x4); 27 | rp2040.writeUint32(ARMED, 0xff); 28 | expect(rp2040.readUint32(ARMED)).toEqual(0); 29 | }); 30 | 31 | it('should generate an IRQ 3 interrupt when Alarm 3 fires', () => { 32 | const clock = new MockClock(); 33 | const rp2040 = new RP2040(clock); 34 | // Arm the alarm 35 | rp2040.writeUint32(ALARM3, 1000); 36 | expect(rp2040.readUint32(ARMED)).toEqual(0x8); 37 | expect(rp2040.readUint32(INTR)).toEqual(0); 38 | // Advance time so that the alarm will fire 39 | clock.advance(2000); 40 | expect(rp2040.readUint32(ARMED)).toEqual(0); 41 | expect(rp2040.readUint32(INTR)).toEqual(0x8); 42 | expect(rp2040.readUint32(INTS)).toEqual(0); 43 | expect(rp2040.core.pendingInterrupts).toBe(0); 44 | // Enable the interrupts for all alarms 45 | rp2040.writeUint32(INTE, 0xff); 46 | expect(rp2040.readUint32(INTS)).toEqual(0x8); 47 | expect(rp2040.core.pendingInterrupts).toBe(0x8); 48 | expect(rp2040.core.interruptsUpdated).toEqual(true); 49 | // Clear the alarm's interrupt 50 | rp2040.writeUint32(INTR_CLEAR, 0x8); 51 | expect(rp2040.readUint32(INTS)).toEqual(0); 52 | expect(rp2040.core.pendingInterrupts).toBe(0); 53 | }); 54 | 55 | it('should generate an interrupt if INTF is 1 even when the INTE bit is 0', () => { 56 | const clock = new MockClock(); 57 | const rp2040 = new RP2040(clock); 58 | expect(rp2040.readUint32(INTS)).toEqual(0); 59 | expect(rp2040.readUint32(INTE)).toEqual(0); 60 | rp2040.writeUint32(INTF, 0x4); 61 | // The corresponding interrupt bit should be 1 62 | expect(rp2040.readUint32(INTS)).toEqual(0x4); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/peripherals/timer.ts: -------------------------------------------------------------------------------- 1 | import { IClock, IAlarm } from '../clock/clock.js'; 2 | import { IRQ } from '../irq.js'; 3 | import { RP2040 } from '../rp2040.js'; 4 | import { BasePeripheral, Peripheral } from './peripheral.js'; 5 | 6 | const TIMEHR = 0x08; 7 | const TIMELR = 0x0c; 8 | const TIMERAWH = 0x24; 9 | const TIMERAWL = 0x28; 10 | const ALARM0 = 0x10; 11 | const ALARM1 = 0x14; 12 | const ALARM2 = 0x18; 13 | const ALARM3 = 0x1c; 14 | const ARMED = 0x20; 15 | const PAUSE = 0x30; 16 | const INTR = 0x34; 17 | const INTE = 0x38; 18 | const INTF = 0x3c; 19 | const INTS = 0x40; 20 | 21 | const ALARM_0 = 1 << 0; 22 | const ALARM_1 = 1 << 1; 23 | const ALARM_2 = 1 << 2; 24 | const ALARM_3 = 1 << 3; 25 | 26 | const timerInterrupts = [IRQ.TIMER_0, IRQ.TIMER_1, IRQ.TIMER_2, IRQ.TIMER_3]; 27 | 28 | class RPTimerAlarm { 29 | armed = false; 30 | targetMicros = 0; 31 | 32 | constructor( 33 | readonly bitValue: number, 34 | readonly clockAlarm: IAlarm, 35 | ) {} 36 | } 37 | 38 | export class RPTimer extends BasePeripheral implements Peripheral { 39 | private readonly clock: IClock; 40 | private latchedTimeHigh = 0; 41 | private readonly alarms; 42 | private intRaw = 0; 43 | private intEnable = 0; 44 | private intForce = 0; 45 | private paused = false; 46 | 47 | constructor(rp2040: RP2040, name: string) { 48 | super(rp2040, name); 49 | this.clock = rp2040.clock; 50 | this.alarms = [ 51 | new RPTimerAlarm( 52 | ALARM_0, 53 | this.clock.createAlarm(() => this.fireAlarm(0)), 54 | ), 55 | new RPTimerAlarm( 56 | ALARM_1, 57 | this.clock.createAlarm(() => this.fireAlarm(1)), 58 | ), 59 | new RPTimerAlarm( 60 | ALARM_2, 61 | this.clock.createAlarm(() => this.fireAlarm(2)), 62 | ), 63 | new RPTimerAlarm( 64 | ALARM_3, 65 | this.clock.createAlarm(() => this.fireAlarm(3)), 66 | ), 67 | ]; 68 | } 69 | 70 | get intStatus() { 71 | return (this.intRaw & this.intEnable) | this.intForce; 72 | } 73 | 74 | readUint32(offset: number) { 75 | const time = this.clock.nanos / 1000; 76 | 77 | switch (offset) { 78 | case TIMEHR: 79 | return this.latchedTimeHigh; 80 | 81 | case TIMELR: 82 | this.latchedTimeHigh = Math.floor(time / 2 ** 32); 83 | return time >>> 0; 84 | 85 | case TIMERAWH: 86 | return Math.floor(time / 2 ** 32); 87 | 88 | case TIMERAWL: 89 | return time >>> 0; 90 | 91 | case ALARM0: 92 | return this.alarms[0].targetMicros; 93 | case ALARM1: 94 | return this.alarms[1].targetMicros; 95 | case ALARM2: 96 | return this.alarms[2].targetMicros; 97 | case ALARM3: 98 | return this.alarms[3].targetMicros; 99 | 100 | case PAUSE: 101 | return this.paused ? 1 : 0; 102 | 103 | case INTR: 104 | return this.intRaw; 105 | case INTE: 106 | return this.intEnable; 107 | case INTF: 108 | return this.intForce; 109 | case INTS: 110 | return this.intStatus; 111 | 112 | case ARMED: 113 | return ( 114 | (this.alarms[0].armed ? this.alarms[0].bitValue : 0) | 115 | (this.alarms[1].armed ? this.alarms[1].bitValue : 0) | 116 | (this.alarms[2].armed ? this.alarms[2].bitValue : 0) | 117 | (this.alarms[3].armed ? this.alarms[3].bitValue : 0) 118 | ); 119 | } 120 | return super.readUint32(offset); 121 | } 122 | 123 | writeUint32(offset: number, value: number) { 124 | switch (offset) { 125 | case ALARM0: 126 | case ALARM1: 127 | case ALARM2: 128 | case ALARM3: { 129 | const alarmIndex = (offset - ALARM0) / 4; 130 | const alarm = this.alarms[alarmIndex]; 131 | const deltaMicros = (value - this.clock.nanos / 1000) >>> 0; 132 | alarm.armed = true; 133 | alarm.targetMicros = value; 134 | alarm.clockAlarm.schedule(deltaMicros * 1000); 135 | break; 136 | } 137 | case ARMED: 138 | for (const alarm of this.alarms) { 139 | if (this.rawWriteValue & alarm.bitValue) { 140 | this.disarmAlarm(alarm); 141 | } 142 | } 143 | break; 144 | case PAUSE: 145 | this.paused = !!(value & 1); 146 | if (this.paused) { 147 | this.warn('Unimplemented Timer Pause'); 148 | } 149 | // TODO actually pause the timer 150 | break; 151 | case INTR: 152 | this.intRaw &= ~this.rawWriteValue; 153 | this.checkInterrupts(); 154 | break; 155 | case INTE: 156 | this.intEnable = value & 0xf; 157 | this.checkInterrupts(); 158 | break; 159 | case INTF: 160 | this.intForce = value & 0xf; 161 | this.checkInterrupts(); 162 | break; 163 | default: 164 | super.writeUint32(offset, value); 165 | } 166 | } 167 | 168 | private fireAlarm(index: number) { 169 | const alarm = this.alarms[index]; 170 | this.disarmAlarm(alarm); 171 | this.intRaw |= alarm.bitValue; 172 | this.checkInterrupts(); 173 | } 174 | 175 | private checkInterrupts() { 176 | const { intStatus } = this; 177 | for (let i = 0; i < this.alarms.length; i++) { 178 | this.rp2040.setInterrupt(timerInterrupts[i], !!(intStatus & (1 << i))); 179 | } 180 | } 181 | 182 | private disarmAlarm(alarm: RPTimerAlarm) { 183 | alarm.clockAlarm.cancel(); 184 | alarm.armed = false; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/peripherals/uart.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { RP2040 } from '../rp2040.js'; 3 | import { RPUART } from './uart.js'; 4 | 5 | const UARTIBRD = 0x24; 6 | const UARTFBRD = 0x28; 7 | const OFFSET_UARTLCR_H = 0x2c; 8 | 9 | describe('UART', () => { 10 | it('should correctly return wordLength based on UARTLCR_H value', () => { 11 | const rp2040 = new RP2040(); 12 | const uart = new RPUART(rp2040, 'UART', 0, { rx: 0, tx: 0 }); 13 | uart.writeUint32(OFFSET_UARTLCR_H, 0x70); 14 | expect(uart.wordLength).toEqual(8); 15 | }); 16 | 17 | it('should correctly calculate the baud rate based on UARTIBRD, UARTFBRD values', () => { 18 | const rp2040 = new RP2040(); 19 | const uart = new RPUART(rp2040, 'UART', 0, { rx: 0, tx: 0 }); 20 | uart.writeUint32(UARTIBRD, 67); // Values taken from example in section 4.2.7.1. of the datasheet 21 | uart.writeUint32(UARTFBRD, 52); 22 | expect(uart.baudRate).toEqual(115207); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/peripherals/uart.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { FIFO } from '../utils/fifo.js'; 3 | import { DREQChannel } from './dma.js'; 4 | import { BasePeripheral, Peripheral } from './peripheral.js'; 5 | 6 | const UARTDR = 0x0; 7 | const UARTFR = 0x18; 8 | const UARTIBRD = 0x24; 9 | const UARTFBRD = 0x28; 10 | const UARTLCR_H = 0x2c; 11 | const UARTCR = 0x30; 12 | const UARTIMSC = 0x38; 13 | const UARTIRIS = 0x3c; 14 | const UARTIMIS = 0x40; 15 | const UARTICR = 0x44; 16 | const UARTPERIPHID0 = 0xfe0; 17 | const UARTPERIPHID1 = 0xfe4; 18 | const UARTPERIPHID2 = 0xfe8; 19 | const UARTPERIPHID3 = 0xfec; 20 | const UARTPCELLID0 = 0xff0; 21 | const UARTPCELLID1 = 0xff4; 22 | const UARTPCELLID2 = 0xff8; 23 | const UARTPCELLID3 = 0xffc; 24 | 25 | // UARTFR bits: 26 | const TXFE = 1 << 7; 27 | const RXFF = 1 << 6; 28 | const RXFE = 1 << 4; 29 | 30 | // UARTLCR_H bits: 31 | const FEN = 1 << 4; 32 | 33 | // UARTCR bits: 34 | const RXE = 1 << 9; 35 | const TXE = 1 << 8; 36 | const UARTEN = 1 << 0; 37 | 38 | // Interrupt bits 39 | const UARTTXINTR = 1 << 5; 40 | const UARTRXINTR = 1 << 4; 41 | 42 | export interface IUARTDMAChannels { 43 | rx: DREQChannel; 44 | tx: DREQChannel; 45 | } 46 | 47 | export class RPUART extends BasePeripheral implements Peripheral { 48 | private ctrlRegister = RXE | TXE; 49 | private lineCtrlRegister = 0; 50 | private rxFIFO = new FIFO(32); 51 | private interruptMask = 0; 52 | private interruptStatus = 0; 53 | private intDivisor = 0; 54 | private fracDivisor = 0; 55 | 56 | public onByte?: (value: number) => void; 57 | public onBaudRateChange?: (baudRate: number) => void; 58 | 59 | constructor( 60 | rp2040: RP2040, 61 | name: string, 62 | readonly irq: number, 63 | readonly dreq: IUARTDMAChannels, 64 | ) { 65 | super(rp2040, name); 66 | } 67 | 68 | get enabled() { 69 | return !!(this.ctrlRegister & UARTEN); 70 | } 71 | 72 | get txEnabled() { 73 | return !!(this.ctrlRegister & TXE); 74 | } 75 | 76 | get rxEnabled() { 77 | return !!(this.ctrlRegister & RXE); 78 | } 79 | 80 | get fifosEnabled() { 81 | return !!(this.lineCtrlRegister & FEN); 82 | } 83 | 84 | /** 85 | * Number of bits per UART character 86 | */ 87 | get wordLength() { 88 | switch ((this.lineCtrlRegister >>> 5) & 0x3) { 89 | case 0b00: 90 | return 5; 91 | case 0b01: 92 | return 6; 93 | case 0b10: 94 | return 7; 95 | case 0b11: 96 | return 8; 97 | } 98 | } 99 | 100 | get baudDivider() { 101 | return this.intDivisor + this.fracDivisor / 64; 102 | } 103 | 104 | get baudRate() { 105 | return Math.round(this.rp2040.clkPeri / (this.baudDivider * 16)); 106 | } 107 | 108 | get flags() { 109 | return (this.rxFIFO.full ? RXFF : 0) | (this.rxFIFO.empty ? RXFE : 0) | TXFE; 110 | } 111 | 112 | checkInterrupts() { 113 | // TODO We should actually implement a proper FIFO for TX 114 | this.interruptStatus |= UARTTXINTR; 115 | this.rp2040.setInterrupt(this.irq, !!(this.interruptStatus & this.interruptMask)); 116 | } 117 | 118 | feedByte(value: number) { 119 | this.rxFIFO.push(value); 120 | // TODO check if the FIFO has reached the threshold level 121 | this.interruptStatus |= UARTRXINTR; 122 | this.checkInterrupts(); 123 | } 124 | 125 | readUint32(offset: number) { 126 | switch (offset) { 127 | case UARTDR: { 128 | const value = this.rxFIFO.pull(); 129 | if (!this.rxFIFO.empty) { 130 | this.interruptStatus |= UARTRXINTR; 131 | } else { 132 | this.interruptStatus &= ~UARTRXINTR; 133 | } 134 | this.checkInterrupts(); 135 | return value; 136 | } 137 | case UARTFR: 138 | return this.flags; 139 | case UARTIBRD: 140 | return this.intDivisor; 141 | case UARTFBRD: 142 | return this.fracDivisor; 143 | case UARTLCR_H: 144 | return this.lineCtrlRegister; 145 | case UARTCR: 146 | return this.ctrlRegister; 147 | case UARTIMSC: 148 | return this.interruptMask; 149 | case UARTIRIS: 150 | return this.interruptStatus; 151 | case UARTIMIS: 152 | return this.interruptStatus & this.interruptMask; 153 | case UARTPERIPHID0: 154 | return 0x11; 155 | case UARTPERIPHID1: 156 | return 0x10; 157 | case UARTPERIPHID2: 158 | return 0x34; 159 | case UARTPERIPHID3: 160 | return 0x00; 161 | case UARTPCELLID0: 162 | return 0x0d; 163 | case UARTPCELLID1: 164 | return 0xf0; 165 | case UARTPCELLID2: 166 | return 0x05; 167 | case UARTPCELLID3: 168 | return 0xb1; 169 | } 170 | return super.readUint32(offset); 171 | } 172 | 173 | writeUint32(offset: number, value: number) { 174 | switch (offset) { 175 | case UARTDR: 176 | this.onByte?.(value & 0xff); 177 | break; 178 | 179 | case UARTIBRD: 180 | this.intDivisor = value & 0xffff; 181 | this.onBaudRateChange?.(this.baudRate); 182 | break; 183 | 184 | case UARTFBRD: 185 | this.fracDivisor = value & 0x3f; 186 | this.onBaudRateChange?.(this.baudRate); 187 | break; 188 | 189 | case UARTLCR_H: 190 | this.lineCtrlRegister = value; 191 | break; 192 | 193 | case UARTCR: 194 | this.ctrlRegister = value; 195 | if (this.enabled) { 196 | this.rp2040.dma.setDREQ(this.dreq.tx); 197 | } else { 198 | this.rp2040.dma.clearDREQ(this.dreq.tx); 199 | } 200 | break; 201 | 202 | case UARTIMSC: 203 | this.interruptMask = value & 0x7ff; 204 | this.checkInterrupts(); 205 | break; 206 | 207 | case UARTICR: 208 | this.interruptStatus &= ~this.rawWriteValue; 209 | this.checkInterrupts(); 210 | break; 211 | 212 | default: 213 | super.writeUint32(offset, value); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/peripherals/watchdog.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../rp2040.js'; 2 | import { Timer32, Timer32PeriodicAlarm, TimerMode } from '../utils/timer32.js'; 3 | import { BasePeripheral, Peripheral } from './peripheral.js'; 4 | 5 | const CTRL = 0x00; // Control register 6 | const LOAD = 0x04; // Load the watchdog timer. 7 | const REASON = 0x08; // Logs the reason for the last reset. 8 | const SCRATCH0 = 0x0c; // Scratch register 9 | const SCRATCH1 = 0x10; // Scratch register 10 | const SCRATCH2 = 0x14; // Scratch register 11 | const SCRATCH3 = 0x18; // Scratch register 12 | const SCRATCH4 = 0x1c; // Scratch register 13 | const SCRATCH5 = 0x20; // Scratch register 14 | const SCRATCH6 = 0x24; // Scratch register 15 | const SCRATCH7 = 0x28; // Scratch register 16 | const TICK = 0x2c; // Controls the tick generator 17 | 18 | // CTRL bits: 19 | const TRIGGER = 1 << 31; 20 | const ENABLE = 1 << 30; 21 | const PAUSE_DBG1 = 1 << 26; 22 | const PAUSE_DBG0 = 1 << 25; 23 | const PAUSE_JTAG = 1 << 24; 24 | const TIME_MASK = 0xffffff; 25 | const TIME_SHIFT = 0; 26 | 27 | // LOAD bits 28 | const LOAD_MASK = 0xffffff; 29 | const LOAD_SHIFT = 0; 30 | 31 | // REASON bits: 32 | const FORCE = 1 << 1; 33 | const TIMER = 1 << 0; 34 | 35 | // TICK bits: 36 | const COUNT_MASK = 0x1ff; 37 | const COUNT_SHIFT = 11; 38 | const RUNNING = 1 << 10; 39 | const TICK_ENABLE = 1 << 9; 40 | const CYCLES_MASK = 0x1ff; 41 | const CYCLES_SHIFT = 0; 42 | 43 | const TICK_FREQUENCY = 2_000_000; // Actually 1 MHz, but due to errata RP2040-E1, the timer is decremented twice per tick 44 | 45 | export class RPWatchdog extends BasePeripheral implements Peripheral { 46 | readonly timer; 47 | readonly alarm; 48 | readonly scratchData = new Uint32Array(8); 49 | 50 | private enable = false; 51 | private tickEnable = true; 52 | private reason = 0; 53 | private pauseDbg0 = true; 54 | private pauseDbg1 = true; 55 | private pauseJtag = true; 56 | 57 | /** Called when the watchdog triggers - override with your own soft reset implementation */ 58 | onWatchdogTrigger = () => { 59 | this.rp2040.logger.warn(this.name, 'Watchdog triggered, but no reset handler provided'); 60 | }; 61 | 62 | // User provided 63 | constructor(rp2040: RP2040, name: string) { 64 | super(rp2040, name); 65 | this.timer = new Timer32(rp2040.clock, TICK_FREQUENCY); 66 | this.timer.mode = TimerMode.Decrement; 67 | this.timer.enable = false; 68 | this.alarm = new Timer32PeriodicAlarm(this.timer, () => { 69 | this.reason = TIMER; 70 | this.onWatchdogTrigger?.(); 71 | }); 72 | this.alarm.target = 0; 73 | this.alarm.enable = false; 74 | } 75 | 76 | readUint32(offset: number) { 77 | switch (offset) { 78 | case CTRL: 79 | return ( 80 | (this.timer.enable ? ENABLE : 0) | 81 | (this.pauseDbg0 ? PAUSE_DBG0 : 0) | 82 | (this.pauseDbg1 ? PAUSE_DBG1 : 0) | 83 | (this.pauseJtag ? PAUSE_JTAG : 0) | 84 | ((this.timer.counter & TIME_MASK) << TIME_SHIFT) 85 | ); 86 | 87 | case REASON: 88 | return this.reason; 89 | 90 | case SCRATCH0: 91 | case SCRATCH1: 92 | case SCRATCH2: 93 | case SCRATCH3: 94 | case SCRATCH4: 95 | case SCRATCH5: 96 | case SCRATCH6: 97 | case SCRATCH7: 98 | return this.scratchData[(offset - SCRATCH0) >> 2]; 99 | 100 | case TICK: 101 | // TODO COUNT bits 102 | return this.tickEnable ? RUNNING | TICK_ENABLE : 0; 103 | } 104 | return super.readUint32(offset); 105 | } 106 | 107 | writeUint32(offset: number, value: number) { 108 | switch (offset) { 109 | case CTRL: 110 | if (value & TRIGGER) { 111 | this.reason = FORCE; 112 | this.onWatchdogTrigger?.(); 113 | } 114 | this.enable = !!(value & ENABLE); 115 | this.timer.enable = this.enable && this.tickEnable; 116 | this.alarm.enable = this.enable && this.tickEnable; 117 | this.pauseDbg0 = !!(value & PAUSE_DBG0); 118 | this.pauseDbg1 = !!(value & PAUSE_DBG1); 119 | this.pauseJtag = !!(value & PAUSE_JTAG); 120 | break; 121 | 122 | case LOAD: 123 | this.timer.set((value >>> LOAD_SHIFT) & LOAD_MASK); 124 | break; 125 | 126 | case SCRATCH0: 127 | case SCRATCH1: 128 | case SCRATCH2: 129 | case SCRATCH3: 130 | case SCRATCH4: 131 | case SCRATCH5: 132 | case SCRATCH6: 133 | case SCRATCH7: 134 | this.scratchData[(offset - SCRATCH0) >> 2] = value; 135 | break; 136 | 137 | case TICK: 138 | this.tickEnable = !!(value & TICK_ENABLE); 139 | this.timer.enable = this.enable && this.tickEnable; 140 | this.alarm.enable = this.enable && this.tickEnable; 141 | // TODO - handle CYCLES (tick also affectes timer) 142 | break; 143 | 144 | default: 145 | super.writeUint32(offset, value); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/rp2040.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { BasePeripheral } from './peripherals/peripheral.js'; 3 | import { RP2040 } from './rp2040.js'; 4 | import { opcodeBX, opcodeMOVS, opcodeNOP, opcodePOP, opcodePUSH } from './utils/assembler.js'; 5 | 6 | const r0 = 0; 7 | const r4 = 4; 8 | const lr = 14; 9 | 10 | const VTOR = 0xe000ed08; 11 | const NVIC_ISER = 0xe000e100; 12 | const NVIC_ICER = 0xe000e180; 13 | const NVIC_ISPR = 0xe000e200; 14 | const NVIC_ICPR = 0xe000e280; 15 | 16 | describe('RP2040', () => { 17 | it(`should initialize PC and SP according to bootrom's vector table`, () => { 18 | const rp2040 = new RP2040(); 19 | rp2040.loadBootrom(new Uint32Array([0x20041f00, 0xee])); 20 | expect(rp2040.core.SP).toEqual(0x20041f00); 21 | expect(rp2040.core.PC).toEqual(0xee); 22 | }); 23 | 24 | describe('IO Register Writes', () => { 25 | it('should replicate 8-bit values four times', () => { 26 | const rp2040 = new RP2040(); 27 | const testPeripheral = new BasePeripheral(rp2040, 'TestPeripheral'); 28 | const writeUint32 = vi.spyOn(testPeripheral, 'writeUint32'); 29 | rp2040.peripherals[0x10] = testPeripheral; 30 | rp2040.writeUint8(0x10123, 0x534); 31 | expect(writeUint32).toHaveBeenCalledWith(0x120, 0x34343434); 32 | }); 33 | 34 | it('should replicate 16-bit values twice', () => { 35 | const rp2040 = new RP2040(); 36 | const testPeripheral = new BasePeripheral(rp2040, 'TestPeripheral'); 37 | const writeUint32 = vi.spyOn(testPeripheral, 'writeUint32'); 38 | rp2040.peripherals[0x10] = testPeripheral; 39 | rp2040.writeUint16(0x10123, 0x12345678); 40 | expect(writeUint32).toHaveBeenCalledWith(0x120, 0x56785678); 41 | }); 42 | 43 | it('should support atomic I/O register write addresses', () => { 44 | const rp2040 = new RP2040(); 45 | const testPeripheral = new BasePeripheral(rp2040, 'TestAtomic'); 46 | vi.spyOn(testPeripheral, 'readUint32').mockReturnValue(0xff); 47 | const writeUint32 = vi.spyOn(testPeripheral, 'writeUint32'); 48 | rp2040.peripherals[0x10] = testPeripheral; 49 | rp2040.writeUint32(0x11120, 0x0f); 50 | expect(writeUint32).toHaveBeenCalledWith(0x120, 0xf0); 51 | }); 52 | }); 53 | 54 | describe('exceptionEntry and exceptionReturn', () => { 55 | it('should execute an exception handler and return from it correctly', () => { 56 | const INT1 = 1 << 1; 57 | const INT1_HANDLER = 0x10000100; 58 | const EXC_INT1 = 16 + 1; 59 | const rp2040 = new RP2040(); 60 | rp2040.core.SP = 0x20004000; 61 | rp2040.core.PC = 0x10004001; 62 | rp2040.core.registers[r0] = 0x44; 63 | rp2040.core.pendingInterrupts = INT1; 64 | rp2040.core.enabledInterrupts = INT1; 65 | rp2040.core.interruptsUpdated = true; 66 | rp2040.writeUint32(VTOR, 0x10000000); 67 | rp2040.writeUint32(0x10000000 + EXC_INT1 * 4, INT1_HANDLER); 68 | rp2040.writeUint16(INT1_HANDLER, opcodeMOVS(r0, 0x55)); 69 | rp2040.writeUint16(INT1_HANDLER + 2, opcodeBX(lr)); 70 | // Exception handler should start at this point. 71 | rp2040.step(); // MOVS r0, 0x55 72 | expect(rp2040.core.IPSR).toEqual(EXC_INT1); 73 | expect(rp2040.core.PC).toEqual(INT1_HANDLER + 2); 74 | expect(rp2040.core.registers[r0]).toEqual(0x55); 75 | rp2040.step(); // BX lr 76 | // Exception handler should return at this point. 77 | expect(rp2040.core.PC).toEqual(0x10004000); 78 | expect(rp2040.core.registers[r0]).toEqual(0x44); 79 | expect(rp2040.core.IPSR).toEqual(0); 80 | }); 81 | 82 | it('should return correctly from exception with POP {lr}', () => { 83 | const INT1 = 1 << 1; 84 | const INT1_HANDLER = 0x10000100; 85 | const EXC_INT1 = 16 + 1; 86 | const rp2040 = new RP2040(); 87 | rp2040.core.SP = 0x20004000; 88 | rp2040.core.PC = 0x10004001; 89 | rp2040.core.registers[r4] = 105; 90 | rp2040.core.pendingInterrupts = INT1; 91 | rp2040.core.enabledInterrupts = INT1; 92 | rp2040.core.interruptsUpdated = true; 93 | rp2040.writeUint32(VTOR, 0x10000000); 94 | rp2040.writeUint32(0x10000000 + EXC_INT1 * 4, INT1_HANDLER); 95 | rp2040.writeUint16(INT1_HANDLER, opcodePUSH(true, 0b01110000)); 96 | rp2040.writeUint16(INT1_HANDLER + 2, opcodeMOVS(r4, 42)); 97 | rp2040.writeUint16(INT1_HANDLER + 4, opcodePOP(true, 0b01110000)); 98 | // Exception handler should start at this point. 99 | rp2040.step(); // push {r4, r5, r6, lr} 100 | expect(rp2040.core.IPSR).toEqual(EXC_INT1); 101 | expect(rp2040.core.PC).toEqual(INT1_HANDLER + 2); 102 | rp2040.step(); // mov r4, 42 103 | expect(rp2040.core.registers[r4]).toEqual(42); 104 | rp2040.step(); // pop {r4, r5, r6, pc} 105 | // Exception handler should return at this point. 106 | expect(rp2040.core.PC).toEqual(0x10004000); 107 | expect(rp2040.core.registers[r4]).toEqual(105); 108 | expect(rp2040.core.IPSR).toEqual(0); 109 | }); 110 | 111 | it('should clear the pending interrupt flag in exceptionEntry() for user IRQs (> 25)', () => { 112 | const INT31 = 1 << 31; 113 | const INT31_HANDLER = 0x10003100; 114 | const EXC_INT31 = 16 + 31; 115 | const rp2040 = new RP2040(); 116 | rp2040.core.SP = 0x20004000; 117 | rp2040.core.PC = 0x10004001; 118 | rp2040.writeUint32(NVIC_ISPR, INT31); // Set IRQ31 to pending 119 | rp2040.core.enabledInterrupts = INT31; 120 | rp2040.core.interruptsUpdated = true; 121 | rp2040.writeUint32(VTOR, 0x10000000); 122 | rp2040.writeUint32(0x10000000 + EXC_INT31 * 4, INT31_HANDLER); 123 | rp2040.writeUint16(INT31_HANDLER, opcodeNOP()); 124 | expect(rp2040.core.pendingInterrupts).toEqual(INT31); 125 | // Exception handler should start at this point. 126 | rp2040.step(); // nop 127 | expect(rp2040.core.pendingInterrupts).toEqual(0); // interrupt flag has been cleared 128 | expect(rp2040.readUint32(NVIC_ISPR)).toEqual(0); 129 | }); 130 | }); 131 | 132 | describe('NVIC registers', () => { 133 | it('writing to NVIC_ISPR should set the corresponding pending interrupt bits', () => { 134 | const rp2040 = new RP2040(); 135 | rp2040.core.pendingInterrupts = 0x1; 136 | rp2040.writeUint32(NVIC_ISPR, 0x10); 137 | expect(rp2040.core.pendingInterrupts).toBe(0x11); 138 | }); 139 | 140 | it('writing to NVIC_ICPR should clear corresponding pending interrupt bits', () => { 141 | const rp2040 = new RP2040(); 142 | rp2040.core.pendingInterrupts = 0xff00000f; 143 | rp2040.writeUint32(NVIC_ICPR, 0x1000000f); 144 | // Only the high 6 bits are actually cleared (see commit 5bc96994 for details) 145 | expect(rp2040.readUint32(NVIC_ISPR)).toBe(0xef00000f); 146 | }); 147 | 148 | it('writing to NVIC_ISER should set the corresponding enabled interrupt bits', () => { 149 | const rp2040 = new RP2040(); 150 | rp2040.core.enabledInterrupts = 0x1; 151 | rp2040.writeUint32(NVIC_ISER, 0x10); 152 | expect(rp2040.core.enabledInterrupts).toBe(0x11); 153 | }); 154 | 155 | it('writing to NVIC_ICER should clear corresponding enabled interrupt bits', () => { 156 | const rp2040 = new RP2040(); 157 | rp2040.core.enabledInterrupts = 0xff; 158 | rp2040.writeUint32(NVIC_ICER, 0x10); 159 | expect(rp2040.core.enabledInterrupts).toBe(0xef); 160 | }); 161 | 162 | it('reading from NVIC_ISER/NVIC_ICER should return the current enabled interrupt bits', () => { 163 | const rp2040 = new RP2040(); 164 | rp2040.core.enabledInterrupts = 0x1; 165 | expect(rp2040.readUint32(NVIC_ISER)).toEqual(0x1); 166 | expect(rp2040.readUint32(NVIC_ICER)).toEqual(0x1); 167 | }); 168 | 169 | it('reading from NVIC_ISPR/NVIC_ICPR should return the current enabled interrupt bits', () => { 170 | const rp2040 = new RP2040(); 171 | rp2040.core.pendingInterrupts = 0x2; 172 | expect(rp2040.readUint32(NVIC_ISPR)).toEqual(0x2); 173 | expect(rp2040.readUint32(NVIC_ICPR)).toEqual(0x2); 174 | }); 175 | 176 | it('should update the interrupt levels correctly when writing to NVIC_IPR3', () => { 177 | const rp2040 = new RP2040(); 178 | // Set the priority of interrupt number 14 to 2 179 | rp2040.writeUint32(0xe000e40c, 0x00800000); 180 | const { interruptPriorities } = rp2040.core; 181 | expect(interruptPriorities[0] | 0).toEqual(~(1 << 14)); 182 | expect(interruptPriorities[1]).toEqual(0); 183 | expect(interruptPriorities[2]).toEqual(1 << 14); 184 | expect(interruptPriorities[3]).toEqual(0); 185 | expect(rp2040.readUint32(0xe000e40c)).toEqual(0x00800000); 186 | }); 187 | 188 | it('should return the correct interrupt priorities when reading from NVIC_IPR5', () => { 189 | const rp2040 = new RP2040(); 190 | rp2040.core.interruptPriorities[0] = 0; 191 | rp2040.core.interruptPriorities[1] = 0x001fffff; // interrupts 0 ... 20 192 | rp2040.core.interruptPriorities[2] = 0x00200000; // interrupt 21 193 | rp2040.core.interruptPriorities[3] = 0xffc00000; // interrupt 22 ... 31 194 | // Set the priority of interrupt number 14 to 2 195 | expect(rp2040.readUint32(0xe000e414)).toEqual(0xc0c08040 | 0); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /src/simulator.ts: -------------------------------------------------------------------------------- 1 | import { SimulationClock } from './clock/simulation-clock.js'; 2 | import { IGDBTarget } from './gdb/gdb-target.js'; 3 | import { RP2040 } from './rp2040.js'; 4 | 5 | export class Simulator implements IGDBTarget { 6 | executeTimer: ReturnType | null = null; 7 | rp2040: RP2040; 8 | stopped = true; 9 | 10 | constructor(readonly clock = new SimulationClock()) { 11 | this.rp2040 = new RP2040(clock); 12 | this.rp2040.onBreak = () => this.stop(); 13 | } 14 | 15 | execute() { 16 | const { rp2040, clock } = this; 17 | 18 | this.executeTimer = null; 19 | this.stopped = false; 20 | const cycleNanos = 1e9 / 125_000_000; // 125 MHz 21 | for (let i = 0; i < 1000000 && !this.stopped; i++) { 22 | if (rp2040.core.waiting) { 23 | const { nanosToNextAlarm } = clock; 24 | clock.tick(nanosToNextAlarm); 25 | i += nanosToNextAlarm / cycleNanos; 26 | } else { 27 | const cycles = rp2040.core.executeInstruction(); 28 | clock.tick(cycles * cycleNanos); 29 | } 30 | } 31 | if (!this.stopped) { 32 | this.executeTimer = setTimeout(() => this.execute(), 0); 33 | } 34 | } 35 | 36 | stop() { 37 | this.stopped = true; 38 | if (this.executeTimer != null) { 39 | clearTimeout(this.executeTimer); 40 | this.executeTimer = null; 41 | } 42 | } 43 | 44 | get executing() { 45 | return !this.stopped; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/sio.spec.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, describe, expect, it } from 'vitest'; 2 | import { createTestDriver } from '../test-utils/create-test-driver.js'; 3 | import { ICortexTestDriver } from '../test-utils/test-driver.js'; 4 | import { SIO_START_ADDRESS } from './rp2040.js'; 5 | 6 | //Hardware Divider registers absolute address 7 | const SIO_DIV_UDIVIDEND = SIO_START_ADDRESS + 0x060; // Divider unsigned dividend 8 | const SIO_DIV_UDIVISOR = SIO_START_ADDRESS + 0x064; // Divider unsigned divisor 9 | const SIO_DIV_SDIVIDEND = SIO_START_ADDRESS + 0x068; // Divider signed dividend 10 | const SIO_DIV_SDIVISOR = SIO_START_ADDRESS + 0x06c; // Divider signed divisor 11 | const SIO_DIV_QUOTIENT = SIO_START_ADDRESS + 0x070; // Divider result quotient 12 | const SIO_DIV_REMAINDER = SIO_START_ADDRESS + 0x074; //Divider result remainder 13 | const SIO_DIV_CSR = SIO_START_ADDRESS + 0x078; 14 | 15 | //SPINLOCK 16 | const SIO_SPINLOCK10 = SIO_START_ADDRESS + 0x128; 17 | const SIO_SPINLOCKST = SIO_START_ADDRESS + 0x5c; 18 | 19 | describe('RPSIO', () => { 20 | let cpu: ICortexTestDriver; 21 | 22 | beforeEach(async () => { 23 | cpu = await createTestDriver(); 24 | }); 25 | 26 | afterEach(async () => { 27 | await cpu.tearDown(); 28 | }); 29 | 30 | describe('Hardware Divider', () => { 31 | it('should perform a signed hardware divider 123456 / -321 = -384 REM 192', async () => { 32 | await cpu.writeUint32(SIO_DIV_SDIVIDEND, 123456); 33 | expect(await cpu.readInt32(SIO_DIV_SDIVIDEND)).toEqual(123456); 34 | await cpu.writeUint32(SIO_DIV_SDIVISOR, -321); 35 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(3); 36 | expect(await cpu.readInt32(SIO_DIV_SDIVISOR)).toEqual(-321); 37 | expect(await cpu.readInt32(SIO_DIV_REMAINDER)).toEqual(192); 38 | expect(await cpu.readInt32(SIO_DIV_QUOTIENT)).toEqual(-384); 39 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(1); 40 | }); 41 | 42 | it('should perform a signed hardware divider -3000 / 2 = -1500 REM 0', async () => { 43 | await cpu.writeUint32(SIO_DIV_SDIVIDEND, -3000); 44 | expect(await cpu.readInt32(SIO_DIV_SDIVIDEND)).toEqual(-3000); 45 | await cpu.writeUint32(SIO_DIV_SDIVISOR, 2); 46 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(3); 47 | expect(await cpu.readInt32(SIO_DIV_SDIVISOR)).toEqual(2); 48 | expect(await cpu.readInt32(SIO_DIV_REMAINDER)).toEqual(0); 49 | expect(await cpu.readInt32(SIO_DIV_QUOTIENT)).toEqual(-1500); 50 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(1); 51 | }); 52 | 53 | it('should perform an unsigned hardware divider 123456 / 321 = 384 REM 192', async () => { 54 | await cpu.writeUint32(SIO_DIV_UDIVIDEND, 123456); 55 | await cpu.writeUint32(SIO_DIV_UDIVISOR, 321); 56 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(3); 57 | expect(await cpu.readUint32(SIO_DIV_REMAINDER)).toEqual(192); 58 | expect(await cpu.readUint32(SIO_DIV_QUOTIENT)).toEqual(384); 59 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(1); 60 | }); 61 | 62 | it('should perform a division, store the result, do another division then restore the previously stored result ', async () => { 63 | await cpu.writeUint32(SIO_DIV_SDIVIDEND, 123456); 64 | await cpu.writeUint32(SIO_DIV_SDIVISOR, -321); 65 | const remainder = await cpu.readInt32(SIO_DIV_REMAINDER); 66 | const quotient = await cpu.readInt32(SIO_DIV_QUOTIENT); 67 | expect(remainder).toEqual(192); 68 | expect(quotient).toEqual(-384); 69 | await cpu.writeUint32(SIO_DIV_UDIVIDEND, 123); 70 | await cpu.writeUint32(SIO_DIV_UDIVISOR, 7); 71 | expect(await cpu.readUint32(SIO_DIV_REMAINDER)).toEqual(4); 72 | expect(await cpu.readUint32(SIO_DIV_QUOTIENT)).toEqual(17); 73 | await cpu.writeUint32(SIO_DIV_REMAINDER, remainder); 74 | await cpu.writeUint32(SIO_DIV_QUOTIENT, quotient); 75 | expect(await cpu.readUint32(SIO_DIV_CSR)).toEqual(3); 76 | expect(await cpu.readInt32(SIO_DIV_REMAINDER)).toEqual(192); 77 | expect(await cpu.readInt32(SIO_DIV_QUOTIENT)).toEqual(-384); 78 | }); 79 | 80 | it('should perform an unsigned division by zero 123456 / 0 = 0xffffffff REM 123456', async () => { 81 | await cpu.writeUint32(SIO_DIV_UDIVIDEND, 123456); 82 | await cpu.writeUint32(SIO_DIV_UDIVISOR, 0); 83 | expect(await cpu.readUint32(SIO_DIV_REMAINDER)).toEqual(123456); 84 | expect(await cpu.readUint32(SIO_DIV_QUOTIENT)).toEqual(0xffffffff); 85 | }); 86 | 87 | it('should perform an unsigned division by zero 0x80000000 / 0 = 0xffffffff REM 0x80000000', async () => { 88 | await cpu.writeUint32(SIO_DIV_UDIVIDEND, 0x80000000); 89 | await cpu.writeUint32(SIO_DIV_UDIVISOR, 0); 90 | expect(await cpu.readUint32(SIO_DIV_REMAINDER)).toEqual(0x80000000); 91 | expect(await cpu.readUint32(SIO_DIV_QUOTIENT)).toEqual(0xffffffff); 92 | }); 93 | 94 | it('should perform a signed division by zero 3000 / 0 = -1 REM 3000', async () => { 95 | await cpu.writeUint32(SIO_DIV_SDIVIDEND, 3000); 96 | await cpu.writeUint32(SIO_DIV_SDIVISOR, 0); 97 | expect(await cpu.readInt32(SIO_DIV_REMAINDER)).toEqual(3000); 98 | expect(await cpu.readInt32(SIO_DIV_QUOTIENT)).toEqual(-1); 99 | }); 100 | 101 | it('should perform a signed division by zero -3000 / 0 = 1 REM -3000', async () => { 102 | await cpu.writeUint32(SIO_DIV_SDIVIDEND, -3000); 103 | await cpu.writeUint32(SIO_DIV_SDIVISOR, 0); 104 | expect(await cpu.readInt32(SIO_DIV_REMAINDER)).toEqual(-3000); 105 | expect(await cpu.readInt32(SIO_DIV_QUOTIENT)).toEqual(1); 106 | }); 107 | 108 | it('should perform a signed division 0x80000000 / 2 = 0xc0000000 REM 0', async () => { 109 | await cpu.writeUint32(SIO_DIV_SDIVIDEND, 0x80000000); 110 | await cpu.writeUint32(SIO_DIV_SDIVISOR, 2); 111 | expect(await cpu.readUint32(SIO_DIV_REMAINDER)).toEqual(0); 112 | expect(await cpu.readUint32(SIO_DIV_QUOTIENT)).toEqual(0xc0000000); 113 | }); 114 | 115 | it('should perform an unsigned division 0x80000000 / 2 = 0x40000000 REM 0', async () => { 116 | await cpu.writeUint32(SIO_DIV_UDIVIDEND, 0x80000000); 117 | await cpu.writeUint32(SIO_DIV_UDIVISOR, 2); 118 | expect(await cpu.readUint32(SIO_DIV_REMAINDER)).toEqual(0); 119 | expect(await cpu.readUint32(SIO_DIV_QUOTIENT)).toEqual(0x40000000); 120 | }); 121 | }); 122 | 123 | it('should unlock, lock and check lock status of spinlock10', async () => { 124 | await cpu.writeUint32(SIO_SPINLOCK10, 0x00000001); //ensure the spinlock is released 125 | expect(await cpu.readUint32(SIO_SPINLOCK10)).toEqual(1024); // lock spinlock, return 1< { 5 | it('should not die if the descriptors are invalid', () => { 6 | expect(extractEndpointNumbers([0])).toEqual({ 7 | in: -1, 8 | out: -1, 9 | }); 10 | 11 | expect(extractEndpointNumbers([9])).toEqual({ 12 | in: -1, 13 | out: -1, 14 | }); 15 | }); 16 | 17 | it('should extract the endpoint numbers from Pi Pico SDK descriptors', () => { 18 | const sdkCDCDescriptors = [ 19 | ...[9, 2, 84, 0, 3, 1, 0, 128, 125], 20 | ...[8, 11, 0, 2, 2, 2, 0, 0], 21 | ...[9, 4, 0, 0, 1, 2, 2, 0, 4], 22 | ...[5, 36, 0, 32, 1], 23 | ...[5, 36, 1, 0, 1], 24 | ...[4, 36, 2, 2], 25 | ...[5, 36, 6, 0, 1], 26 | ...[7, 5, 129, 3, 8, 0, 16], 27 | ...[9, 4, 1, 0, 2, 10, 0, 0, 0], 28 | ...[7, 5, 2, 2, 64, 0, 0], 29 | ...[7, 5, 130, 2, 64, 0, 0], 30 | ...[9, 4, 2, 0, 0, 255, 0, 1, 5], 31 | ]; 32 | expect(extractEndpointNumbers(sdkCDCDescriptors)).toEqual({ 33 | in: 2, 34 | out: 2, 35 | }); 36 | }); 37 | 38 | it('should extract the endpoint numbers from MicroPython descriptors', () => { 39 | const micropythonDescriptors = [ 40 | ...[9, 2, 75, 0, 2, 1, 0, 128, 125], // Configuration descriptor 41 | ...[8, 11, 0, 2, 2, 2, 0, 0], 42 | ...[9, 4, 0, 0, 1, 2, 2, 0, 4], // Interface descriptor 43 | ...[5, 36, 0, 32, 1], 44 | ...[5, 36, 1, 0, 1], 45 | ...[4, 36, 2, 2], 46 | ...[5, 36, 6, 0, 1], 47 | ...[7, 5, 129, 3, 8, 0, 16], // Endpoint (interrupt) 48 | ...[9, 4, 1, 0, 2, 10, 0, 0, 0], // interface (CDC-Data class) 49 | ...[7, 5, 2, 2, 64, 0, 0], // Endpoint (bulk) 50 | ...[7, 5, 130, 2, 64, 0, 0], // Endpoint (bulk) 51 | ]; 52 | expect(extractEndpointNumbers(micropythonDescriptors)).toEqual({ 53 | in: 2, 54 | out: 2, 55 | }); 56 | }); 57 | 58 | it('should extract the endpoint numbers from CircuitPython descriptors', () => { 59 | const circuitPythonDescriptors = [ 60 | ...[9, 2, 218, 0, 6, 1, 0, 128, 50], 61 | ...[8, 11, 0, 2, 2, 2, 0, 0], 62 | ...[9, 4, 0, 0, 1, 2, 2, 0, 4], 63 | ...[5, 36, 0, 16, 1], 64 | ...[5, 36, 1, 1, 1], 65 | ...[4, 36, 2, 2], 66 | ...[5, 36, 6, 0, 1], 67 | ...[7, 5, 129, 3, 64, 0, 16], 68 | ...[9, 4, 1, 0, 2, 10, 0, 0, 5], 69 | ...[7, 5, 2, 2, 64, 0, 0], 70 | ...[7, 5, 130, 2, 64, 0, 0], 71 | ...[9, 4, 2, 0, 2, 8, 6, 80, 6], 72 | ...[7, 5, 131, 2, 64, 0, 0], 73 | ...[7, 5, 3, 2, 64, 0, 0], 74 | ...[9, 4, 3, 0, 2, 3, 0, 0, 7], 75 | ...[9, 33, 17, 1, 0, 1, 34, 195, 0], 76 | ...[7, 5, 132, 3, 64, 0, 8], 77 | ...[7, 5, 4, 3, 64, 0, 8], 78 | ...[9, 4, 4, 0, 0, 1, 1, 0, 11], 79 | ...[9, 36, 1, 0, 1, 9, 0, 1, 5], 80 | ...[9, 4, 5, 0, 2, 1, 3, 0, 10], 81 | ...[7, 36, 1, 0, 1, 37, 0], 82 | ...[6, 36, 2, 1, 1, 8], 83 | ...[6, 36, 2, 2, 2, 0], 84 | ...[9, 36, 3, 1, 3, 1, 2, 1, 9], 85 | ...[9, 36, 3, 2, 4, 1, 1, 1, 0], 86 | ...[7, 5, 5, 2, 64, 0, 0], 87 | ...[5, 37, 1, 1, 1], 88 | ...[7, 5, 133, 2, 64, 0, 0], 89 | ...[5, 37, 1, 1, 3], 90 | ]; 91 | expect(extractEndpointNumbers(circuitPythonDescriptors)).toEqual({ 92 | in: 2, 93 | out: 2, 94 | }); 95 | }); 96 | 97 | it('should extract the endpoint numbers from Arduino Core descriptors', () => { 98 | const arduinoCoreDescriptors = [ 99 | ...[9, 2, 75, 0, 2, 1, 0, 192, 250], 100 | ...[8, 11, 0, 2, 2, 2, 0, 0], 101 | ...[9, 4, 0, 0, 1, 2, 2, 1, 0], // Interface 102 | ...[5, 36, 0, 16, 1], 103 | ...[5, 36, 1, 3, 1], 104 | ...[4, 36, 2, 6], 105 | ...[5, 36, 6, 0, 1], 106 | ...[7, 5, 130, 3, 64, 0, 16], // Endpoint 107 | ...[9, 4, 1, 0, 2, 10, 0, 0, 0], // Interface 108 | ...[7, 5, 129, 2, 64, 0, 0], // Endpoint 109 | ...[7, 5, 1, 2, 64, 0, 0], // Endpoint 110 | ]; 111 | expect(extractEndpointNumbers(arduinoCoreDescriptors)).toEqual({ 112 | in: 1, 113 | out: 1, 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /src/usb/cdc.ts: -------------------------------------------------------------------------------- 1 | import { RPUSBController } from '../peripherals/usb.js'; 2 | import { FIFO } from '../utils/fifo.js'; 3 | import { DataDirection, DescriptorType, SetupRecipient, SetupType } from './interfaces.js'; 4 | import { 5 | createSetupPacket, 6 | getDescriptorPacket, 7 | setDeviceAddressPacket, 8 | setDeviceConfigurationPacket, 9 | } from './setup.js'; 10 | 11 | // CDC stuff 12 | const CDC_REQUEST_SET_CONTROL_LINE_STATE = 0x22; 13 | 14 | const CDC_DTR = 1 << 0; 15 | const CDC_RTS = 1 << 1; 16 | 17 | const CDC_DATA_CLASS = 10; 18 | const ENDPOINT_BULK = 2; 19 | 20 | const TX_FIFO_SIZE = 512; 21 | 22 | const ENDPOINT_ZERO = 0; 23 | const CONFIGURATION_DESCRIPTOR_SIZE = 9; 24 | 25 | export function extractEndpointNumbers(descriptors: ArrayLike) { 26 | let index = 0; 27 | let foundInterface = false; 28 | const result = { 29 | in: -1, 30 | out: -1, 31 | }; 32 | while (index < descriptors.length) { 33 | const len = descriptors[index]; 34 | if (len < 2 || descriptors.length < index + len) { 35 | break; 36 | } 37 | const type = descriptors[index + 1]; 38 | if (type === DescriptorType.Interface && len === 9) { 39 | const numEndpoints = descriptors[index + 4]; 40 | const interfaceClass = descriptors[index + 5]; 41 | foundInterface = numEndpoints === 2 && interfaceClass === CDC_DATA_CLASS; 42 | } 43 | if (foundInterface && type === DescriptorType.Endpoint && len === 7) { 44 | const address = descriptors[index + 2]; 45 | const attributes = descriptors[index + 3]; 46 | if ((attributes & 0x3) === ENDPOINT_BULK) { 47 | if (address & 0x80) { 48 | result.in = address & 0xf; 49 | } else { 50 | result.out = address & 0xf; 51 | } 52 | } 53 | } 54 | index += descriptors[index]; 55 | } 56 | return result; 57 | } 58 | 59 | export class USBCDC { 60 | readonly txFIFO = new FIFO(TX_FIFO_SIZE); 61 | 62 | onSerialData?: (buffer: Uint8Array) => void; 63 | onDeviceConnected?: () => void; 64 | 65 | private initialized = false; 66 | private descriptorsSize: number | null = null; 67 | private descriptors: number[] = []; 68 | private outEndpoint = -1; 69 | private inEndpoint = -1; 70 | 71 | constructor(readonly usb: RPUSBController) { 72 | this.usb.onUSBEnabled = () => { 73 | this.usb.resetDevice(); 74 | }; 75 | this.usb.onResetReceived = () => { 76 | this.usb.sendSetupPacket(setDeviceAddressPacket(1)); 77 | }; 78 | this.usb.onEndpointWrite = (endpoint, buffer) => { 79 | if (endpoint === ENDPOINT_ZERO && buffer.length === 0) { 80 | if (this.descriptorsSize == null) { 81 | this.usb.sendSetupPacket( 82 | getDescriptorPacket(DescriptorType.Configration, CONFIGURATION_DESCRIPTOR_SIZE), 83 | ); 84 | } 85 | // Acknowledgement 86 | else if (!this.initialized) { 87 | this.cdcSetControlLineState(); 88 | this.onDeviceConnected?.(); 89 | } 90 | } 91 | if (endpoint === ENDPOINT_ZERO && buffer.length > 1) { 92 | if ( 93 | buffer.length === CONFIGURATION_DESCRIPTOR_SIZE && 94 | buffer[1] === DescriptorType.Configration && 95 | this.descriptorsSize == null 96 | ) { 97 | this.descriptorsSize = (buffer[3] << 8) | buffer[2]; 98 | this.usb.sendSetupPacket( 99 | getDescriptorPacket(DescriptorType.Configration, this.descriptorsSize), 100 | ); 101 | } else if (this.descriptorsSize != null && this.descriptors.length < this.descriptorsSize) { 102 | this.descriptors.push(...buffer); 103 | } 104 | if (this.descriptorsSize === this.descriptors.length) { 105 | const endpoints = extractEndpointNumbers(this.descriptors); 106 | this.inEndpoint = endpoints.in; 107 | this.outEndpoint = endpoints.out; 108 | 109 | // Now configure the device 110 | this.usb.sendSetupPacket(setDeviceConfigurationPacket(1)); 111 | } 112 | } 113 | if (endpoint === this.inEndpoint) { 114 | this.onSerialData?.(buffer); 115 | } 116 | }; 117 | this.usb.onEndpointRead = (endpoint, size) => { 118 | if (endpoint === this.outEndpoint) { 119 | const buffer = new Uint8Array(Math.min(size, this.txFIFO.itemCount)); 120 | for (let i = 0; i < buffer.length; i++) { 121 | buffer[i] = this.txFIFO.pull(); 122 | } 123 | this.usb.endpointReadDone(this.outEndpoint, buffer); 124 | } 125 | }; 126 | } 127 | 128 | private cdcSetControlLineState(value = CDC_DTR | CDC_RTS, interfaceNumber = 0) { 129 | this.usb.sendSetupPacket( 130 | createSetupPacket({ 131 | dataDirection: DataDirection.HostToDevice, 132 | type: SetupType.Class, 133 | recipient: SetupRecipient.Device, 134 | bRequest: CDC_REQUEST_SET_CONTROL_LINE_STATE, 135 | wValue: value, 136 | wIndex: interfaceNumber, 137 | wLength: 0, 138 | }), 139 | ); 140 | this.initialized = true; 141 | } 142 | 143 | sendSerialByte(data: number) { 144 | this.txFIFO.push(data); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/usb/interfaces.ts: -------------------------------------------------------------------------------- 1 | export enum DataDirection { 2 | HostToDevice, 3 | DeviceToHost, 4 | } 5 | 6 | export enum SetupType { 7 | Standard, 8 | Class, 9 | Vendor, 10 | Reserved, 11 | } 12 | 13 | export enum SetupRecipient { 14 | Device, 15 | Interface, 16 | Endpoint, 17 | Other, 18 | } 19 | 20 | export enum SetupRequest { 21 | GetStatus, 22 | ClearFeature, 23 | Reserved1, 24 | SetFeature, 25 | Reserved2, 26 | SetAddress, 27 | GetDescriptor, 28 | SetDescriptor, 29 | GetConfiguration, 30 | SetDeviceConfiguration, 31 | GetInterface, 32 | SetInterface, 33 | SynchFrame, 34 | } 35 | 36 | export enum DescriptorType { 37 | Device = 1, 38 | Configration = 2, 39 | String = 3, 40 | Interface = 4, 41 | Endpoint = 5, 42 | } 43 | 44 | export interface ISetupPacketParams { 45 | dataDirection: DataDirection; 46 | type: SetupType; 47 | recipient: SetupRecipient; 48 | bRequest: SetupRequest | number; 49 | wValue: number /* 16 bits */; 50 | wIndex: number /* 16 bits */; 51 | wLength: number /* 16 bits */; 52 | } 53 | -------------------------------------------------------------------------------- /src/usb/setup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataDirection, 3 | DescriptorType, 4 | ISetupPacketParams, 5 | SetupRecipient, 6 | SetupRequest, 7 | SetupType, 8 | } from './interfaces.js'; 9 | 10 | export function createSetupPacket(params: ISetupPacketParams) { 11 | const setupPacket = new Uint8Array(8); 12 | setupPacket[0] = (params.dataDirection << 7) | (params.type << 5) | params.recipient; 13 | setupPacket[1] = params.bRequest; 14 | setupPacket[2] = params.wValue & 0xff; 15 | setupPacket[3] = (params.wValue >> 8) & 0xff; 16 | setupPacket[4] = params.wIndex & 0xff; 17 | setupPacket[5] = (params.wIndex >> 8) & 0xff; 18 | setupPacket[6] = params.wLength & 0xff; 19 | setupPacket[7] = (params.wLength >> 8) & 0xff; 20 | return setupPacket; 21 | } 22 | 23 | export function setDeviceAddressPacket(address: number) { 24 | return createSetupPacket({ 25 | dataDirection: DataDirection.HostToDevice, 26 | type: SetupType.Standard, 27 | recipient: SetupRecipient.Device, 28 | bRequest: SetupRequest.SetAddress, 29 | wValue: address, 30 | wIndex: 0, 31 | wLength: 0, 32 | }); 33 | } 34 | 35 | export function getDescriptorPacket(type: DescriptorType, length: number, index = 0) { 36 | return createSetupPacket({ 37 | dataDirection: DataDirection.DeviceToHost, 38 | type: SetupType.Standard, 39 | recipient: SetupRecipient.Device, 40 | bRequest: SetupRequest.GetDescriptor, 41 | wValue: type << 8, 42 | wIndex: index, 43 | wLength: length, 44 | }); 45 | } 46 | 47 | export function setDeviceConfigurationPacket(configurationNumber: number) { 48 | return createSetupPacket({ 49 | dataDirection: DataDirection.HostToDevice, 50 | type: SetupType.Standard, 51 | recipient: SetupRecipient.Device, 52 | bRequest: SetupRequest.SetDeviceConfiguration, 53 | wValue: configurationNumber, 54 | wIndex: 0, 55 | wLength: 0, 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/assembler.ts: -------------------------------------------------------------------------------- 1 | export function opcodeADCS(Rdn: number, Rm: number) { 2 | return (0b0100000101 << 6) | ((Rm & 7) << 3) | (Rdn & 7); 3 | } 4 | 5 | export function opcodeADDS1(Rd: number, Rn: number, imm3: number) { 6 | return (0b0001110 << 9) | ((imm3 & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7); 7 | } 8 | 9 | export function opcodeADDS2(Rdn: number, imm8: number) { 10 | return (0b00110 << 11) | ((Rdn & 7) << 8) | (imm8 & 0xff); 11 | } 12 | 13 | export function opcodeADDspPlusImm(Rd: number, imm8: number) { 14 | return (0b10101 << 11) | ((Rd & 7) << 8) | ((imm8 >> 2) & 0xff); 15 | } 16 | 17 | export function opcodeADDsp2(imm: number) { 18 | return (0b101100000 << 7) | ((imm >> 2) & 0x7f); 19 | } 20 | 21 | export function opcodeADDSreg(Rd: number, Rn: number, Rm: number) { 22 | return (0b0001100 << 9) | ((Rm & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7); 23 | } 24 | 25 | export function opcodeADDreg(Rdn: number, Rm: number) { 26 | return (0b01000100 << 8) | ((Rdn & 0x8) << 4) | ((Rm & 0xf) << 3) | (Rdn & 0x7); 27 | } 28 | 29 | export function opcodeADR(Rd: number, imm8: number) { 30 | return (0b10100 << 11) | ((Rd & 7) << 8) | ((imm8 >> 2) & 0xff); 31 | } 32 | 33 | export function opcodeANDS(Rn: number, Rm: number) { 34 | return (0b0100000000 << 6) | ((Rm & 7) << 3) | (Rn & 0x7); 35 | } 36 | 37 | export function opcodeASRS(Rd: number, Rm: number, imm5: number) { 38 | return (0b00010 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7); 39 | } 40 | 41 | export function opcodeASRSreg(Rdn: number, Rm: number) { 42 | return (0b0100000100 << 6) | ((Rm & 0x7) << 3) | ((Rm & 0x7) << 3) | (Rdn & 0x7); 43 | } 44 | 45 | export function opcodeBT1(cond: number, imm8: number) { 46 | return (0b1101 << 12) | ((cond & 0xf) << 8) | ((imm8 >> 1) & 0x1ff); 47 | } 48 | 49 | export function opcodeBT2(imm11: number) { 50 | return (0b11100 << 11) | ((imm11 >> 1) & 0x7ff); 51 | } 52 | 53 | export function opcodeBICS(Rdn: number, Rm: number) { 54 | return (0b0100001110 << 6) | ((Rm & 7) << 3) | (Rdn & 7); 55 | } 56 | 57 | export function opcodeBL(imm: number) { 58 | const imm11 = (imm >> 1) & 0x7ff; 59 | const imm10 = (imm >> 12) & 0x3ff; 60 | const s = imm < 0 ? 1 : 0; 61 | const j2 = 1 - (((imm >> 22) & 0x1) ^ s); 62 | const j1 = 1 - (((imm >> 23) & 0x1) ^ s); 63 | const opcode = 64 | (0b1101 << 28) | (j1 << 29) | (j2 << 27) | (imm11 << 16) | (0b11110 << 11) | (s << 10) | imm10; 65 | return opcode >>> 0; 66 | } 67 | 68 | export function opcodeBLX(Rm: number) { 69 | return (0b010001111 << 7) | (Rm << 3); 70 | } 71 | 72 | export function opcodeBX(Rm: number) { 73 | return (0b010001110 << 7) | (Rm << 3); 74 | } 75 | 76 | export function opcodeCMN(Rn: number, Rm: number) { 77 | return (0b0100001011 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7); 78 | } 79 | 80 | export function opcodeCMPimm(Rn: number, Imm8: number) { 81 | return (0b00101 << 11) | ((Rn & 0x7) << 8) | (Imm8 & 0xff); 82 | } 83 | 84 | export function opcodeCMPregT1(Rn: number, Rm: number) { 85 | return (0b0100001010 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7); 86 | } 87 | 88 | export function opcodeCMPregT2(Rn: number, Rm: number) { 89 | return (0b01000101 << 8) | (((Rn >> 3) & 0x1) << 7) | ((Rm & 0xf) << 3) | (Rn & 0x7); 90 | } 91 | 92 | export function opcodeDMBSY() { 93 | return 0x8f50f3bf; 94 | } 95 | 96 | export function opcodeDSBSY() { 97 | return 0x8f4ff3bf; 98 | } 99 | 100 | export function opcodeEORS(Rdn: number, Rm: number) { 101 | return (0b0100000001 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7); 102 | } 103 | 104 | export function opcodeISBSY() { 105 | return 0x8f6ff3bf; 106 | } 107 | 108 | export function opcodeLDMIA(Rn: number, registers: number) { 109 | return (0b11001 << 11) | ((Rn & 0x7) << 8) | (registers & 0xff); 110 | } 111 | 112 | export function opcodeLDRreg(Rt: number, Rn: number, Rm: number) { 113 | return (0b0101100 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 114 | } 115 | 116 | export function opcodeLDRimm(Rt: number, Rn: number, imm5: number) { 117 | return (0b01101 << 11) | (((imm5 >> 2) & 0x1f) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 118 | } 119 | 120 | export function opcodeLDRlit(Rt: number, imm8: number) { 121 | return (0b01001 << 11) | ((imm8 >> 2) & 0xff) | ((Rt & 0x7) << 8); 122 | } 123 | 124 | export function opcodeLDRB(Rt: number, Rn: number, imm5: number) { 125 | return (0b01111 << 11) | ((imm5 & 0x1f) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 126 | } 127 | 128 | export function opcodeLDRsp(Rt: number, imm8: number) { 129 | return (0b10011 << 11) | ((Rt & 7) << 8) | ((imm8 >> 2) & 0xff); 130 | } 131 | 132 | export function opcodeLDRBreg(Rt: number, Rn: number, Rm: number) { 133 | return (0b0101110 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 134 | } 135 | 136 | export function opcodeLDRH(Rt: number, Rn: number, imm5: number) { 137 | return (0b10001 << 11) | (((imm5 >> 1) & 0xf) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 138 | } 139 | 140 | export function opcodeLDRHreg(Rt: number, Rn: number, Rm: number) { 141 | return (0b0101101 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 142 | } 143 | 144 | export function opcodeLDRSB(Rt: number, Rn: number, Rm: number) { 145 | return (0b0101011 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 146 | } 147 | 148 | export function opcodeLDRSH(Rt: number, Rn: number, Rm: number) { 149 | return (0b0101111 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 150 | } 151 | 152 | export function opcodeLSLSreg(Rdn: number, Rm: number) { 153 | return (0b0100000010 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7); 154 | } 155 | 156 | export function opcodeLSLSimm(Rd: number, Rm: number, Imm5: number) { 157 | return (0b00000 << 11) | ((Imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7); 158 | } 159 | 160 | export function opcodeLSRS(Rd: number, Rm: number, imm5: number) { 161 | return (0b00001 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7); 162 | } 163 | 164 | export function opcodeLSRSreg(Rdn: number, Rm: number) { 165 | return (0b0100000011 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7); 166 | } 167 | 168 | export function opcodeMOV(Rd: number, Rm: number) { 169 | return (0b01000110 << 8) | ((Rd & 0x8 ? 1 : 0) << 7) | (Rm << 3) | (Rd & 0x7); 170 | } 171 | 172 | export function opcodeMOVS(Rd: number, imm8: number) { 173 | return (0b00100 << 11) | ((Rd & 0x7) << 8) | (imm8 & 0xff); 174 | } 175 | 176 | export function opcodeMOVSreg(Rd: number, Rm: number) { 177 | return (0b000000000 << 6) | ((Rm & 0x7) << 3) | (Rd & 0x7); 178 | } 179 | export function opcodeMRS(Rd: number, specReg: number) { 180 | return ( 181 | ((0b1000 << 28) | ((Rd & 0xf) << 24) | ((specReg & 0xff) << 16) | 0b1111001111101111) >>> 0 182 | ); 183 | } 184 | 185 | export function opcodeMSR(specReg: number, Rn: number) { 186 | return ((0b10001000 << 24) | ((specReg & 0xff) << 16) | (0b111100111000 << 4) | (Rn & 0xf)) >>> 0; 187 | } 188 | 189 | export function opcodeMULS(Rn: number, Rdm: number) { 190 | return (0b0100001101 << 6) | ((Rn & 7) << 3) | (Rdm & 7); 191 | } 192 | 193 | export function opcodeMVNS(Rd: number, Rm: number) { 194 | return (0b0100001111 << 6) | ((Rm & 7) << 3) | (Rd & 7); 195 | } 196 | 197 | export function opcodeNOP() { 198 | return 0b1011111100000000; 199 | } 200 | 201 | export function opcodeORRS(Rn: number, Rm: number) { 202 | return (0b0100001100 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7); 203 | } 204 | 205 | export function opcodePOP(P: boolean, registerList: number) { 206 | return (0b1011110 << 9) | ((P ? 1 : 0) << 8) | registerList; 207 | } 208 | 209 | export function opcodePUSH(M: boolean, registerList: number) { 210 | return (0b1011010 << 9) | ((M ? 1 : 0) << 8) | registerList; 211 | } 212 | 213 | export function opcodeREV(Rd: number, Rn: number) { 214 | return (0b1011101000 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7); 215 | } 216 | 217 | export function opcodeREV16(Rd: number, Rn: number) { 218 | return (0b1011101001 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7); 219 | } 220 | 221 | export function opcodeREVSH(Rd: number, Rn: number) { 222 | return (0b1011101011 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7); 223 | } 224 | 225 | export function opcodeROR(Rdn: number, Rm: number) { 226 | return (0b0100000111 << 6) | ((Rm & 0x7) << 3) | (Rdn & 0x7); 227 | } 228 | 229 | export function opcodeRSBS(Rd: number, Rn: number) { 230 | return (0b0100001001 << 6) | ((Rn & 0x7) << 3) | (Rd & 0x7); 231 | } 232 | 233 | export function opcodeSBCS(Rn: number, Rm: number) { 234 | return (0b0100000110 << 6) | ((Rm & 0x7) << 3) | (Rn & 0x7); 235 | } 236 | 237 | export function opcodeSTMIA(Rn: number, registers: number) { 238 | return (0b11000 << 11) | ((Rn & 0x7) << 8) | (registers & 0xff); 239 | } 240 | 241 | export function opcodeSTR(Rt: number, Rm: number, imm5: number) { 242 | return (0b01100 << 11) | (((imm5 >> 2) & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7); 243 | } 244 | 245 | export function opcodeSTRsp(Rt: number, imm8: number) { 246 | return (0b10010 << 11) | ((Rt & 7) << 8) | ((imm8 >> 2) & 0xff); 247 | } 248 | 249 | export function opcodeSTRreg(Rt: number, Rn: number, Rm: number) { 250 | return (0b0101000 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 251 | } 252 | 253 | export function opcodeSTRB(Rt: number, Rm: number, imm5: number) { 254 | return (0b01110 << 11) | ((imm5 & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7); 255 | } 256 | 257 | export function opcodeSTRBreg(Rt: number, Rn: number, Rm: number) { 258 | return (0b0101010 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 259 | } 260 | 261 | export function opcodeSTRH(Rt: number, Rm: number, imm5: number) { 262 | return (0b10000 << 11) | (((imm5 >> 1) & 0x1f) << 6) | ((Rm & 0x7) << 3) | (Rt & 0x7); 263 | } 264 | 265 | export function opcodeSTRHreg(Rt: number, Rn: number, Rm: number) { 266 | return (0b0101001 << 9) | ((Rm & 0x7) << 6) | ((Rn & 0x7) << 3) | (Rt & 0x7); 267 | } 268 | 269 | export function opcodeSUBS1(Rd: number, Rn: number, imm3: number) { 270 | return (0b0001111 << 9) | ((imm3 & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7); 271 | } 272 | 273 | export function opcodeSUBS2(Rdn: number, imm8: number) { 274 | return (0b00111 << 11) | ((Rdn & 7) << 8) | (imm8 & 0xff); 275 | } 276 | 277 | export function opcodeSUBSreg(Rd: number, Rn: number, Rm: number) { 278 | return (0b0001101 << 9) | ((Rm & 0x7) << 6) | ((Rn & 7) << 3) | (Rd & 7); 279 | } 280 | 281 | export function opcodeSUBsp(imm: number) { 282 | return (0b101100001 << 7) | ((imm >> 2) & 0x7f); 283 | } 284 | 285 | export function opcodeSVC(imm8: number) { 286 | return (0b11011111 << 8) | (imm8 & 0xff); 287 | } 288 | 289 | export function opcodeSXTB(Rd: number, Rm: number) { 290 | return (0b1011001001 << 6) | ((Rm & 7) << 3) | (Rd & 7); 291 | } 292 | 293 | export function opcodeSXTH(Rd: number, Rm: number) { 294 | return (0b1011001000 << 6) | ((Rm & 7) << 3) | (Rd & 7); 295 | } 296 | 297 | export function opcodeTST(Rm: number, Rn: number) { 298 | return (0b0100001000 << 6) | ((Rn & 7) << 3) | (Rm & 7); 299 | } 300 | 301 | export function opcodeUXTB(Rd: number, Rm: number) { 302 | return (0b1011001011 << 6) | ((Rm & 7) << 3) | (Rd & 7); 303 | } 304 | 305 | export function opcodeUDF(imm8: number) { 306 | return ((0b11011110 << 8) | (imm8 & 0xff)) >>> 0; 307 | } 308 | 309 | export function opcodeUDF2(imm16: number) { 310 | const imm12 = imm16 & 0xfff; 311 | const imm4 = (imm16 >> 12) & 0xf; 312 | return ((0b111101111111 << 4) | imm4 | (0b1010 << 28) | (imm12 << 16)) >>> 0; 313 | } 314 | 315 | export function opcodeUXTH(Rd: number, Rm: number) { 316 | return (0b1011001010 << 6) | ((Rm & 7) << 3) | (Rd & 7); 317 | } 318 | 319 | export function opcodeWFI() { 320 | return 0b1011111100110000; 321 | } 322 | 323 | export function opcodeYIELD() { 324 | return 0b1011111100010000; 325 | } 326 | -------------------------------------------------------------------------------- /src/utils/bit.ts: -------------------------------------------------------------------------------- 1 | export function bit(n: number) { 2 | return 1 << n; 3 | } 4 | 5 | export function s32(n: number) { 6 | return n | 0; 7 | } 8 | 9 | export function u32(n: number) { 10 | return n >>> 0; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/fifo.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { FIFO } from './fifo.js'; 3 | 4 | describe('FIFO', () => { 5 | it('should successfully push and pull 4 items', () => { 6 | const fifo = new FIFO(3); 7 | expect(fifo.empty).toBe(true); 8 | fifo.push(1); 9 | expect(fifo.empty).toBe(false); 10 | fifo.push(2); 11 | expect(fifo.itemCount).toBe(2); 12 | expect(fifo.full).toBe(false); 13 | fifo.push(3); 14 | expect(fifo.full).toBe(true); 15 | expect(fifo.pull()).toBe(1); 16 | expect(fifo.full).toBe(false); 17 | fifo.push(4); 18 | expect(fifo.full).toBe(true); 19 | expect(fifo.pull()).toBe(2); 20 | expect(fifo.pull()).toBe(3); 21 | expect(fifo.empty).toBe(false); 22 | expect(fifo.itemCount).toBe(1); 23 | expect(fifo.pull()).toBe(4); 24 | expect(fifo.full).toBe(false); 25 | expect(fifo.empty).toBe(true); 26 | }); 27 | 28 | describe('peek()', () => { 29 | it(`should return the next item in the FIFO without affecting the FIFO's content`, () => { 30 | const fifo = new FIFO(3); 31 | expect(fifo.empty).toBe(true); 32 | fifo.push(10); 33 | expect(fifo.empty).toBe(false); 34 | fifo.push(20); 35 | expect(fifo.peek()).toBe(10); 36 | expect(fifo.itemCount).toBe(2); 37 | expect(fifo.pull()).toBe(10); 38 | }); 39 | }); 40 | 41 | describe('items', () => { 42 | it(`should return an array with all the FIFO's content`, () => { 43 | const fifo = new FIFO(3); 44 | expect(fifo.empty).toBe(true); 45 | fifo.push(10); 46 | fifo.push(20); 47 | fifo.push(30); 48 | fifo.pull(); 49 | expect(fifo.items).toEqual([20, 30]); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/utils/fifo.ts: -------------------------------------------------------------------------------- 1 | export class FIFO { 2 | readonly buffer: Uint32Array; 3 | 4 | private start = 0; 5 | private used = 0; 6 | 7 | constructor(size: number) { 8 | this.buffer = new Uint32Array(size); 9 | } 10 | 11 | get size() { 12 | return this.buffer.length; 13 | } 14 | 15 | get itemCount() { 16 | return this.used; 17 | } 18 | 19 | push(value: number) { 20 | const { length } = this.buffer; 21 | const { start, used } = this; 22 | if (this.used < length) { 23 | this.buffer[(start + used) % length] = value; 24 | this.used++; 25 | } 26 | } 27 | 28 | pull() { 29 | const { start, used } = this; 30 | const { length } = this.buffer; 31 | if (used) { 32 | this.start = (start + 1) % length; 33 | this.used--; 34 | return this.buffer[start]; 35 | } 36 | return 0; 37 | } 38 | 39 | peek() { 40 | return this.used ? this.buffer[this.start] : 0; 41 | } 42 | 43 | reset() { 44 | this.used = 0; 45 | } 46 | 47 | get empty() { 48 | return this.used == 0; 49 | } 50 | 51 | get full() { 52 | return this.used === this.buffer.length; 53 | } 54 | 55 | get items() { 56 | const { start, used, buffer } = this; 57 | const { length } = buffer; 58 | const result = []; 59 | for (let i = 0; i < used; i++) { 60 | result[i] = buffer[(start + i) % length]; 61 | } 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/logging.ts: -------------------------------------------------------------------------------- 1 | import { formatTime } from './time.js'; 2 | 3 | export interface Logger { 4 | debug(componentName: string, message: string): void; 5 | warn(componentName: string, message: string): void; 6 | error(componentName: string, message: string): void; 7 | info(componentName: string, message: string): void; 8 | } 9 | 10 | export enum LogLevel { 11 | Debug, 12 | Info, 13 | Warn, 14 | Error, 15 | } 16 | 17 | export class ConsoleLogger implements Logger { 18 | constructor( 19 | public currentLogLevel: LogLevel, 20 | private throwOnError = true, 21 | ) {} 22 | 23 | private aboveLogLevel(logLevel: LogLevel): boolean { 24 | return logLevel >= this.currentLogLevel ? true : false; 25 | } 26 | 27 | private formatMessage(componentName: string, message: string) { 28 | const currentTime = formatTime(new Date()); 29 | return `${currentTime} [${componentName}] ${message}`; 30 | } 31 | 32 | debug(componetName: string, message: string): void { 33 | if (this.aboveLogLevel(LogLevel.Debug)) { 34 | console.debug(this.formatMessage(componetName, message)); 35 | } 36 | } 37 | 38 | warn(componetName: string, message: string): void { 39 | if (this.aboveLogLevel(LogLevel.Warn)) { 40 | console.warn(this.formatMessage(componetName, message)); 41 | } 42 | } 43 | 44 | error(componentName: string, message: string): void { 45 | if (this.aboveLogLevel(LogLevel.Error)) { 46 | console.error(this.formatMessage(componentName, message)); 47 | if (this.throwOnError) { 48 | throw new Error(`[${componentName}] ${message}`); 49 | } 50 | } 51 | } 52 | 53 | info(componentName: string, message: string): void { 54 | if (this.aboveLogLevel(LogLevel.Info)) { 55 | console.info(this.formatMessage(componentName, message)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/pio-assembler.spec.ts: -------------------------------------------------------------------------------- 1 | import { it, describe, expect } from 'vitest'; 2 | 3 | import { 4 | pioJMP, 5 | pioWAIT, 6 | pioIN, 7 | pioOUT, 8 | pioPUSH, 9 | pioPULL, 10 | pioMOV, 11 | pioIRQ, 12 | pioSET, 13 | PIO_COND_PIN, 14 | PIO_WAIT_SRC_GPIO, 15 | PIO_SRC_X, 16 | PIO_DEST_Y, 17 | PIO_MOV_DEST_X, 18 | PIO_OP_INVERT, 19 | PIO_SRC_STATUS, 20 | } from './pio-assembler.js'; 21 | 22 | describe('pio-assembler', () => { 23 | it('should correctly encode an `jmp PIN, 5` pio instruction', () => { 24 | expect(pioJMP(PIO_COND_PIN, 5)).toEqual(0xc5); 25 | }); 26 | 27 | it('should correctly encode an `wait 1 gpio 12` pio instruction', () => { 28 | expect(pioWAIT(true, PIO_WAIT_SRC_GPIO, 12)).toEqual(0x208c); 29 | }); 30 | 31 | it('should correctly encode an `in X, 12` pio instruction', () => { 32 | expect(pioIN(PIO_SRC_X, 12)).toEqual(0x402c); 33 | }); 34 | 35 | it('should correctly encode an `out Y, 30` pio instruction', () => { 36 | expect(pioOUT(PIO_DEST_Y, 30)).toEqual(0x605e); 37 | }); 38 | 39 | it('should correctly encode an `push iffull noblock` pio instruction', () => { 40 | expect(pioPUSH(true, true, 12)).toEqual(0x8c60); 41 | }); 42 | 43 | it('should correctly encode an `pull block` pio instruction', () => { 44 | expect(pioPULL(true, false)).toEqual(0x80c0); 45 | }); 46 | 47 | it('should correctly encode an `mov X, !STATUS` pio instruction', () => { 48 | expect(pioMOV(PIO_MOV_DEST_X, PIO_OP_INVERT, PIO_SRC_STATUS)).toEqual(0xa02d); 49 | }); 50 | 51 | it('should correctly encode an `irq set 4` pio instruction', () => { 52 | expect(pioIRQ(false, false, 4)).toEqual(0xc004); 53 | }); 54 | 55 | it('should correctly encode an `set X, 12` pio instruction', () => { 56 | expect(pioSET(PIO_MOV_DEST_X, 12)).toEqual(0xe02c); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/utils/pio-assembler.ts: -------------------------------------------------------------------------------- 1 | export const PIO_SRC_PINS = 0; 2 | export const PIO_SRC_X = 1; 3 | export const PIO_SRC_Y = 2; 4 | export const PIO_SRC_NULL = 3; 5 | export const PIO_SRC_STATUS = 5; 6 | export const PIO_SRC_ISR = 6; 7 | export const PIO_SRC_OSR = 7; 8 | 9 | export const PIO_DEST_PINS = 0; 10 | export const PIO_DEST_X = 1; 11 | export const PIO_DEST_Y = 2; 12 | export const PIO_DEST_NULL = 3; 13 | export const PIO_DEST_PINDIRS = 4; 14 | export const PIO_DEST_PC = 5; 15 | export const PIO_DEST_ISR = 6; 16 | export const PIO_DEST_EXEC = 7; 17 | 18 | export const PIO_MOV_DEST_PINS = 0; 19 | export const PIO_MOV_DEST_X = 1; 20 | export const PIO_MOV_DEST_Y = 2; 21 | export const PIO_MOV_DEST_EXEC = 4; 22 | export const PIO_MOV_DEST_PC = 5; 23 | export const PIO_MOV_DEST_ISR = 6; 24 | export const PIO_MOV_DEST_OSR = 7; 25 | 26 | export const PIO_OP_NONE = 0; 27 | export const PIO_OP_INVERT = 1; 28 | export const PIO_OP_BITREV = 2; 29 | 30 | export const PIO_WAIT_SRC_GPIO = 0; 31 | export const PIO_WAIT_SRC_PIN = 1; 32 | export const PIO_WAIT_SRC_IRQ = 2; 33 | 34 | export const PIO_COND_ALWAYS = 0; 35 | export const PIO_COND_NOTX = 1; 36 | export const PIO_COND_XDEC = 2; 37 | export const PIO_COND_NOTY = 3; 38 | export const PIO_COND_YDEC = 4; 39 | export const PIO_COND_XNEY = 5; 40 | export const PIO_COND_PIN = 6; 41 | export const PIO_COND_NOTEMPTYOSR = 7; 42 | 43 | export function pioJMP(cond: number = 0, address: number, delay: number = 0) { 44 | return ((delay & 0x1f) << 8) | ((cond & 0x7) << 5) | (address & 0x1f); 45 | } 46 | 47 | export function pioWAIT(polarity: boolean, src: number, index: number, delay: number = 0) { 48 | return ( 49 | (1 << 13) | 50 | ((delay & 0x1f) << 8) | 51 | ((polarity ? 1 : 0) << 7) | 52 | ((src & 0x3) << 5) | 53 | (index & 0x1f) 54 | ); 55 | } 56 | 57 | export function pioIN(src: number, bitCount: number, delay: number = 0) { 58 | return (2 << 13) | ((delay & 0x1f) << 8) | ((src & 0x7) << 5) | (bitCount & 0x1f); 59 | } 60 | 61 | export function pioOUT(Dest: number, bitCount: number, delay: number = 0) { 62 | return (3 << 13) | ((delay & 0x1f) << 8) | ((Dest & 0x7) << 5) | (bitCount & 0x1f); 63 | } 64 | 65 | export function pioPUSH(ifFull: boolean, noBlock: boolean, delay: number = 0) { 66 | return (4 << 13) | ((delay & 0x1f) << 8) | ((ifFull ? 1 : 0) << 6) | ((noBlock ? 1 : 0) << 5); 67 | } 68 | 69 | export function pioPULL(ifEmpty: boolean, noBlock: boolean, delay: number = 0) { 70 | return ( 71 | (4 << 13) | 72 | ((delay & 0x1f) << 8) | 73 | (1 << 7) | 74 | ((ifEmpty ? 1 : 0) << 6) | 75 | ((noBlock ? 1 : 0) << 5) 76 | ); 77 | } 78 | 79 | export function pioMOV(dest: number, op: number = 0, src: number, delay: number = 0) { 80 | return (5 << 13) | ((delay & 0x1f) << 8) | ((dest & 0x7) << 5) | ((op & 0x3) << 3) | (src & 0x7); 81 | } 82 | 83 | export function pioIRQ(clear: boolean, wait: boolean, index: number, delay: number = 0) { 84 | return ( 85 | (6 << 13) | 86 | ((delay & 0x1f) << 8) | 87 | ((clear ? 1 : 0) << 6) | 88 | ((wait ? 1 : 0) << 5) | 89 | (index & 0x1f) 90 | ); 91 | } 92 | 93 | export function pioSET(dest: number, data: number, delay: number = 0) { 94 | return (7 << 13) | ((delay & 0x1f) << 8) | ((dest & 0x7) << 5) | (data & 0x1f); 95 | } 96 | -------------------------------------------------------------------------------- /src/utils/time.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { formatTime } from './time.js'; 3 | 4 | describe('formatTime', () => { 5 | it('should correctly format a timestamp with microseconds, padding with spaces', () => { 6 | expect(formatTime(new Date(2020, 10, 10, 4, 55, 2, 12))).toBe('04:55:02.12 '); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/utils/time.ts: -------------------------------------------------------------------------------- 1 | export function getCurrentMicroseconds() { 2 | if (typeof performance != 'undefined') { 3 | return Math.floor(performance.now() * 1000); 4 | } else { 5 | return Math.floor(eval('require')('perf_hooks').performance.now() * 1000); 6 | } 7 | } 8 | 9 | function leftPad(value: string, minLength: number, padChar = ' ') { 10 | if (value.length < minLength) { 11 | value = padChar + value; 12 | } 13 | return value; 14 | } 15 | 16 | function rightPad(value: string, minLength: number, padChar = ' ') { 17 | if (value.length < minLength) { 18 | value += padChar; 19 | } 20 | return value; 21 | } 22 | 23 | export function formatTime(date: Date) { 24 | const hours = leftPad(date.getHours().toString(), 2, '0'); 25 | const minutes = leftPad(date.getMinutes().toString(), 2, '0'); 26 | const seconds = leftPad(date.getSeconds().toString(), 2, '0'); 27 | const milliseconds = rightPad(date.getMilliseconds().toString(), 3); 28 | return `${hours}:${minutes}:${seconds}.${milliseconds}`; 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/timer32.ts: -------------------------------------------------------------------------------- 1 | import { IClock } from '../clock/clock.js'; 2 | 3 | export enum TimerMode { 4 | Increment, 5 | Decrement, 6 | ZigZag, 7 | } 8 | 9 | export class Timer32 { 10 | private baseValue = 0; 11 | private baseNanos = 0; 12 | private topValue = 0xffffffff; 13 | private prescalerValue = 1; 14 | private timerMode = TimerMode.Increment; 15 | private enabled = true; 16 | readonly listeners: (() => void)[] = []; 17 | 18 | constructor( 19 | readonly clock: IClock, 20 | private baseFreq: number, 21 | ) {} 22 | 23 | reset() { 24 | this.baseNanos = this.clock.nanos; 25 | this.baseValue = 0; 26 | this.updated(); 27 | } 28 | 29 | set(value: number, zigZagDown = false) { 30 | this.baseValue = zigZagDown ? this.topValue * 2 - value : value; 31 | this.baseNanos = this.clock.nanos; 32 | this.updated(); 33 | } 34 | 35 | /** 36 | * Advances the counter by the given amount. Note that this will 37 | * decrease the counter if the timer is running in Decrement mode. 38 | * 39 | * @param delta The value to add to the counter. Can be negative. 40 | */ 41 | advance(delta: number) { 42 | this.baseValue += delta; 43 | } 44 | 45 | get rawCounter() { 46 | const { baseFreq, prescalerValue, baseNanos, baseValue, enabled, timerMode } = this; 47 | if (!baseFreq || !prescalerValue || !enabled) { 48 | return this.baseValue; 49 | } 50 | const zigzag = timerMode == TimerMode.ZigZag; 51 | const ticks = ((this.clock.nanos - baseNanos) / 1e9) * (baseFreq / prescalerValue); 52 | const topModulo = zigzag ? this.topValue * 2 : this.topValue + 1; 53 | const delta = timerMode == TimerMode.Decrement ? topModulo - (ticks % topModulo) : ticks; 54 | let currentValue = Math.round(baseValue + delta); 55 | if (this.topValue != 0xffffffff) { 56 | currentValue %= topModulo; 57 | } 58 | return currentValue; 59 | } 60 | 61 | get counter() { 62 | let currentValue = this.rawCounter; 63 | if (this.timerMode == TimerMode.ZigZag && currentValue > this.topValue) { 64 | currentValue = this.topValue * 2 - currentValue; 65 | } 66 | return currentValue >>> 0; 67 | } 68 | 69 | get top() { 70 | return this.topValue; 71 | } 72 | 73 | set top(value: number) { 74 | const { counter } = this; 75 | this.topValue = value; 76 | this.set(counter <= this.topValue ? counter : 0); 77 | } 78 | 79 | get frequency() { 80 | return this.baseFreq; 81 | } 82 | 83 | set frequency(value: number) { 84 | this.baseValue = this.counter; 85 | this.baseNanos = this.clock.nanos; 86 | this.baseFreq = value; 87 | this.updated(); 88 | } 89 | 90 | get prescaler() { 91 | return this.prescalerValue; 92 | } 93 | 94 | set prescaler(value: number) { 95 | this.baseValue = this.counter; 96 | this.baseNanos = this.clock.nanos; 97 | this.enabled = this.prescalerValue !== 0; 98 | this.prescalerValue = value; 99 | this.updated(); 100 | } 101 | 102 | toNanos(cycles: number) { 103 | const { baseFreq, prescalerValue } = this; 104 | return (cycles * 1e9) / (baseFreq / prescalerValue); 105 | } 106 | 107 | get enable() { 108 | return this.enabled; 109 | } 110 | 111 | set enable(value: boolean) { 112 | if (value !== this.enabled) { 113 | if (value) { 114 | this.baseNanos = this.clock.nanos; 115 | } else { 116 | this.baseValue = this.counter; 117 | } 118 | this.enabled = value; 119 | this.updated(); 120 | } 121 | } 122 | 123 | get mode() { 124 | return this.timerMode; 125 | } 126 | 127 | set mode(value: TimerMode) { 128 | if (this.timerMode !== value) { 129 | const { counter } = this; 130 | this.timerMode = value; 131 | this.set(counter); 132 | } 133 | } 134 | 135 | private updated() { 136 | for (const listener of this.listeners) { 137 | listener(); 138 | } 139 | } 140 | } 141 | 142 | export class Timer32PeriodicAlarm { 143 | private targetValue = 0; 144 | private enabled = false; 145 | private clockAlarm; 146 | 147 | constructor( 148 | readonly timer: Timer32, 149 | readonly callback: () => void, 150 | ) { 151 | this.clockAlarm = this.timer.clock.createAlarm(this.handleAlarm); 152 | timer.listeners.push(this.update); 153 | } 154 | 155 | get enable() { 156 | return this.enabled; 157 | } 158 | 159 | set enable(value: boolean) { 160 | if (value !== this.enabled) { 161 | this.enabled = value; 162 | if (value && this.timer.enable) { 163 | this.schedule(); 164 | } else { 165 | this.cancel(); 166 | } 167 | } 168 | } 169 | 170 | get target() { 171 | return this.targetValue; 172 | } 173 | 174 | set target(value: number) { 175 | if (value === this.targetValue) { 176 | return; 177 | } 178 | this.targetValue = value; 179 | if (this.enabled && this.timer.enable) { 180 | this.cancel(); 181 | this.schedule(); 182 | } 183 | } 184 | 185 | handleAlarm = () => { 186 | this.callback(); 187 | if (this.enabled && this.timer.enable) { 188 | this.schedule(); 189 | } 190 | }; 191 | 192 | update = () => { 193 | this.cancel(); 194 | if (this.enabled && this.timer.enable) { 195 | this.schedule(); 196 | } 197 | }; 198 | 199 | private schedule() { 200 | const { timer, targetValue } = this; 201 | const { top, mode, rawCounter } = timer; 202 | let cycleDelta = targetValue - rawCounter; 203 | if (mode === TimerMode.ZigZag && cycleDelta < 0) { 204 | if (cycleDelta < -top) { 205 | cycleDelta += 2 * top; 206 | } else { 207 | cycleDelta = top * 2 - targetValue - rawCounter; 208 | } 209 | } 210 | if (top != 0xffffffff) { 211 | if (cycleDelta <= 0) { 212 | cycleDelta += top + 1; 213 | } 214 | if (targetValue > top) { 215 | // Skip alarm 216 | return; 217 | } 218 | } 219 | if (mode === TimerMode.Decrement) { 220 | cycleDelta = top + 1 - cycleDelta; 221 | } 222 | const cyclesToAlarm = cycleDelta >>> 0; 223 | const nanosToAlarm = timer.toNanos(cyclesToAlarm); 224 | this.clockAlarm.schedule(nanosToAlarm); 225 | } 226 | 227 | private cancel() { 228 | this.clockAlarm.cancel(); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /test-utils/create-test-driver.ts: -------------------------------------------------------------------------------- 1 | import { RP2040 } from '../src/rp2040.js'; 2 | import { GDBClient } from './gdbclient.js'; 3 | import { ICortexTestDriver } from './test-driver.js'; 4 | import { GDBTestDriver } from './test-driver-gdb.js'; 5 | import { RP2040TestDriver } from './test-driver-rp2040.js'; 6 | 7 | export async function createTestDriver(): Promise { 8 | if (process.env.TEST_GDB_SERVER) { 9 | const client = new GDBClient(); 10 | await client.connect(process.env.TEST_GDB_SERVER); 11 | const cpu = new GDBTestDriver(client); 12 | await cpu.init(); 13 | return cpu; 14 | } else { 15 | return new RP2040TestDriver(new RP2040()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-utils/gdbclient.ts: -------------------------------------------------------------------------------- 1 | import { Socket } from 'net'; 2 | import { 3 | decodeHexBuf, 4 | decodeHexUint32, 5 | decodeHexUint32Array, 6 | encodeHexBuf, 7 | encodeHexByte, 8 | encodeHexUint32, 9 | encodeHexUint32BE, 10 | gdbMessage, 11 | } from '../src/gdb/gdb-utils.js'; 12 | 13 | export const registerNames = [ 14 | 'r0', 15 | 'r1', 16 | 'r2', 17 | 'r3', 18 | 'r4', 19 | 'r5', 20 | 'r6', 21 | 'r7', 22 | 'r8', 23 | 'r9', 24 | 'r10', 25 | 'r11', 26 | 'r12', 27 | 'sp', 28 | 'lr', 29 | 'pc', 30 | 'xPSR', 31 | ]; 32 | 33 | export function dumpUint32(value: number) { 34 | let valueStr = value.toString(16); 35 | while (valueStr.length < 8) { 36 | valueStr = '0' + valueStr; 37 | } 38 | return valueStr; 39 | } 40 | 41 | export class GDBClient { 42 | private socket = new Socket(); 43 | private rejectCurrentResponse?: (e: Error) => void; 44 | 45 | async connect(host: string, port: number = 3333) { 46 | return new Promise((resolve, reject) => { 47 | this.rejectCurrentResponse = reject; 48 | this.socket.once('error', (error) => { 49 | this.rejectCurrentResponse?.(new Error(`Socket error: ${error}`)); 50 | }); 51 | this.socket.once('close', () => { 52 | this.rejectCurrentResponse?.(new Error(`Socket was closed`)); 53 | }); 54 | this.socket.once('data', (data) => { 55 | if (data.toString() === '+') { 56 | resolve(); 57 | } else { 58 | reject(new Error(`Invalid data from gdbserver: ${data}`)); 59 | } 60 | }); 61 | this.socket.connect(port, host); 62 | }); 63 | } 64 | 65 | private readResponse(needAck = true) { 66 | return new Promise((resolve, reject) => { 67 | this.rejectCurrentResponse = reject; 68 | let data = ''; 69 | 70 | const listener = (buffer: Buffer) => { 71 | data += buffer.toString(); 72 | if (needAck) { 73 | if (data[0] === '+') { 74 | needAck = false; 75 | data = data.substr(1); 76 | } else { 77 | this.socket.off('data', listener); 78 | reject(new Error(`No ack from gdbserver: ${data}`)); 79 | } 80 | } 81 | if (data.length && data[0] !== '$') { 82 | this.socket.off('data', listener); 83 | reject(new Error(`Invalid response from gdbserver: ${data}`)); 84 | } 85 | const hashIndex = data.indexOf('#'); 86 | if (hashIndex >= 0 && hashIndex + 2 < data.length) { 87 | this.socket.off('data', listener); 88 | resolve(data.substring(1, hashIndex)); 89 | } 90 | }; 91 | this.socket.on('data', listener); 92 | }); 93 | } 94 | 95 | private async sendCommand(command: string) { 96 | this.socket.write(gdbMessage(command)); 97 | return await this.readResponse(); 98 | } 99 | 100 | async monitor(cmd: string) { 101 | const buf = new Uint8Array(cmd.length); 102 | for (let i = 0; i < cmd.length; i++) { 103 | buf[i] = cmd.charCodeAt(i); 104 | } 105 | let response = await this.sendCommand(`qRcmd,${encodeHexBuf(buf)}`); 106 | while (response !== 'OK' && response[0] === 'O') { 107 | this.socket.write('+'); 108 | response = await this.readResponse(false); 109 | } 110 | if (response !== 'OK') { 111 | throw new Error(`Invalid monitor response: ${response}`); 112 | } 113 | } 114 | 115 | async readRegisters() { 116 | const response = await this.sendCommand('g'); 117 | return decodeHexUint32Array(response); 118 | } 119 | 120 | async dumpRegisters() { 121 | const registers = await this.readRegisters(); 122 | for (let i = 0; i < registerNames.length; i++) { 123 | console.log(registerNames[i], '=', '0x' + dumpUint32(registers[i])); 124 | } 125 | } 126 | 127 | async readRegister(index: number) { 128 | const response = await this.sendCommand(`p${encodeHexByte(index)}`); 129 | if (response.length === 2) { 130 | return decodeHexBuf(response)[0]; 131 | } 132 | return decodeHexUint32(response); 133 | } 134 | 135 | async writeRegister(index: number, value: number, width: 8 | 32 = 32) { 136 | const response = await this.sendCommand( 137 | `P${encodeHexByte(index)}=${width === 32 ? encodeHexUint32(value) : encodeHexByte(value)}`, 138 | ); 139 | if (response !== 'OK') { 140 | throw new Error(`Invalid writeRegister response: ${response}`); 141 | } 142 | } 143 | 144 | async singleStep() { 145 | const response = await this.sendCommand('vCont;s:1;c'); 146 | if (!response.startsWith('T') && !response.startsWith('S')) { 147 | throw new Error(`Invalid singleStep response: ${response}`); 148 | } 149 | } 150 | 151 | async readMemory(address: number, length: number) { 152 | const addressStr = encodeHexUint32BE(address); 153 | const lengthStr = encodeHexUint32BE(length); 154 | const response = await this.sendCommand(`m ${addressStr},${lengthStr}`); 155 | return decodeHexBuf(response); 156 | } 157 | 158 | async writeMemory(address: number, data: Uint8Array) { 159 | const addressStr = encodeHexUint32BE(address); 160 | const lengthStr = encodeHexUint32BE(data.length); 161 | const response = await this.sendCommand(`M ${addressStr},${lengthStr}:${encodeHexBuf(data)}`); 162 | if (response !== 'OK') { 163 | throw new Error(`Invalid writeRegister response: ${response}`); 164 | } 165 | } 166 | 167 | disconnect() { 168 | this.rejectCurrentResponse = undefined; 169 | this.socket.destroy(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /test-utils/test-driver-gdb.ts: -------------------------------------------------------------------------------- 1 | import { GDBClient } from './gdbclient.js'; 2 | import { ICortexRegisterName, ICortexRegisters, ICortexTestDriver } from './test-driver.js'; 3 | 4 | const pc = 15; 5 | 6 | export class GDBTestDriver implements ICortexTestDriver { 7 | constructor(private readonly gdbClient: GDBClient) {} 8 | 9 | async init() { 10 | await this.gdbClient.monitor('reset init'); 11 | await this.setRegisters({ C: false, Z: false, N: false, V: false }); 12 | } 13 | 14 | async tearDown() { 15 | this.gdbClient.disconnect(); 16 | } 17 | 18 | async setPC(pcValue: number) { 19 | await this.gdbClient.writeRegister(pc, pcValue); 20 | } 21 | 22 | async writeUint8(address: number, value: number) { 23 | await this.gdbClient.writeMemory(address, new Uint8Array([value])); 24 | } 25 | 26 | async writeUint16(address: number, value: number) { 27 | await this.gdbClient.writeMemory(address, new Uint8Array(new Uint16Array([value]).buffer)); 28 | } 29 | 30 | async writeUint32(address: number, value: number) { 31 | await this.gdbClient.writeMemory(address, new Uint8Array(new Uint32Array([value]).buffer)); 32 | } 33 | 34 | async setRegisters(registers: Partial) { 35 | const registerMap = { 36 | r0: 0, 37 | r1: 1, 38 | r2: 2, 39 | r3: 3, 40 | r4: 4, 41 | r5: 5, 42 | r6: 6, 43 | r7: 7, 44 | r8: 8, 45 | r9: 9, 46 | r10: 10, 47 | r11: 11, 48 | r12: 12, 49 | sp: 13, 50 | lr: 14, 51 | pc: 15, 52 | xPSR: 16, 53 | MSP: 17, 54 | PSP: 18, 55 | PRIMASK: 19, 56 | CONTROL: 22, 57 | N: null, 58 | Z: null, 59 | C: null, 60 | V: null, 61 | }; 62 | const xSPR = registerMap.xPSR; 63 | let haveFlagRegisters = false; 64 | for (const key of Object.keys(registers) as ICortexRegisterName[]) { 65 | const registerIndex = registerMap[key]; 66 | if (registerIndex != null) { 67 | const value = registers[key] as number; 68 | await this.gdbClient.writeRegister(registerIndex, value); 69 | } else { 70 | haveFlagRegisters = true; 71 | } 72 | } 73 | if (haveFlagRegisters) { 74 | let xPSRValue = await this.gdbClient.readRegister(xSPR); 75 | const flagBits = { 76 | N: 0x80000000, 77 | Z: 0x40000000, 78 | C: 0x20000000, 79 | V: 0x10000000, 80 | }; 81 | for (const flag of Object.keys(flagBits) as (keyof typeof flagBits)[]) { 82 | if (flag in registers) { 83 | const flagBitMask = flagBits[flag]; 84 | const flagValue = registers[flag]; 85 | if (flagValue) { 86 | xPSRValue |= flagBitMask; 87 | } else { 88 | xPSRValue &= ~flagBitMask; 89 | } 90 | } 91 | } 92 | await this.gdbClient.writeRegister(xSPR, xPSRValue); 93 | } 94 | } 95 | 96 | async setRegister(index: number, value: number) { 97 | await this.gdbClient.writeRegister(index, value); 98 | } 99 | 100 | async singleStep() { 101 | await this.gdbClient.singleStep(); 102 | } 103 | 104 | async readRegisters(): Promise { 105 | const registers = await this.gdbClient.readRegisters(); 106 | const xPSR = registers[16]; 107 | return { 108 | r0: registers[0], 109 | r1: registers[1], 110 | r2: registers[2], 111 | r3: registers[3], 112 | r4: registers[4], 113 | r5: registers[5], 114 | r6: registers[6], 115 | r7: registers[7], 116 | r8: registers[8], 117 | r9: registers[9], 118 | r10: registers[10], 119 | r11: registers[11], 120 | r12: registers[12], 121 | sp: registers[13], 122 | lr: registers[14], 123 | pc: registers[15], 124 | xPSR, 125 | MSP: await this.gdbClient.readRegister(17), 126 | PSP: await this.gdbClient.readRegister(18), 127 | PRIMASK: await this.gdbClient.readRegister(19), 128 | CONTROL: await this.gdbClient.readRegister(22), 129 | 130 | N: !!(xPSR & 0x80000000), 131 | Z: !!(xPSR & 0x40000000), 132 | C: !!(xPSR & 0x20000000), 133 | V: !!(xPSR & 0x10000000), 134 | }; 135 | } 136 | 137 | async readUint8(address: number) { 138 | const result = await this.gdbClient.readMemory(address, 1); 139 | return result[0]; 140 | } 141 | 142 | async readUint16(address: number) { 143 | const result = await this.gdbClient.readMemory(address, 2); 144 | return new Uint16Array(result.buffer)[0]; 145 | } 146 | 147 | async readUint32(address: number) { 148 | const result = await this.gdbClient.readMemory(address, 4); 149 | return new Uint32Array(result.buffer)[0]; 150 | } 151 | 152 | async readInt32(address: number) { 153 | return (await this.readUint32(address)) | 0; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /test-utils/test-driver-rp2040.ts: -------------------------------------------------------------------------------- 1 | import { SYSM_CONTROL, SYSM_MSP, SYSM_PRIMASK, SYSM_PSP } from '../src/cortex-m0-core.js'; 2 | import { RP2040 } from '../src/rp2040.js'; 3 | import { ICortexRegisterName, ICortexRegisters, ICortexTestDriver } from './test-driver.js'; 4 | 5 | export class RP2040TestDriver implements ICortexTestDriver { 6 | constructor(readonly rp2040: RP2040) {} 7 | 8 | async init() { 9 | /* this page intentionally left blank ! */ 10 | } 11 | 12 | async tearDown() { 13 | this.rp2040.pio[0].stop(); 14 | this.rp2040.pio[1].stop(); 15 | } 16 | 17 | async setPC(pcValue: number) { 18 | this.rp2040.core.PC = pcValue; 19 | } 20 | 21 | async writeUint8(address: number, value: number) { 22 | this.rp2040.writeUint8(address, value); 23 | } 24 | 25 | async writeUint16(address: number, value: number) { 26 | this.rp2040.writeUint16(address, value); 27 | } 28 | 29 | async writeUint32(address: number, value: number) { 30 | this.rp2040.writeUint32(address, value); 31 | } 32 | 33 | async setRegisters(registers: Partial) { 34 | const { rp2040 } = this; 35 | const { core } = rp2040; 36 | for (const key of Object.keys(registers) as ICortexRegisterName[]) { 37 | const value = registers[key] as number; 38 | const boolValue = registers[key] as boolean; 39 | switch (key) { 40 | case 'r0': 41 | core.registers[0] = value; 42 | break; 43 | case 'r1': 44 | core.registers[1] = value; 45 | break; 46 | case 'r2': 47 | core.registers[2] = value; 48 | break; 49 | case 'r3': 50 | core.registers[3] = value; 51 | break; 52 | case 'r4': 53 | core.registers[4] = value; 54 | break; 55 | case 'r5': 56 | core.registers[5] = value; 57 | break; 58 | case 'r6': 59 | core.registers[6] = value; 60 | break; 61 | case 'r7': 62 | core.registers[7] = value; 63 | break; 64 | case 'r8': 65 | core.registers[8] = value; 66 | break; 67 | case 'r9': 68 | core.registers[9] = value; 69 | break; 70 | case 'r10': 71 | core.registers[10] = value; 72 | break; 73 | case 'r11': 74 | core.registers[11] = value; 75 | break; 76 | case 'r12': 77 | core.registers[12] = value; 78 | break; 79 | case 'sp': 80 | core.registers[13] = value; 81 | break; 82 | case 'lr': 83 | core.registers[14] = value; 84 | break; 85 | case 'pc': 86 | core.registers[15] = value; 87 | break; 88 | case 'xPSR': 89 | core.xPSR = value; 90 | break; 91 | case 'MSP': 92 | core.writeSpecialRegister(SYSM_MSP, value); 93 | break; 94 | case 'PSP': 95 | core.writeSpecialRegister(SYSM_PSP, value); 96 | break; 97 | case 'PRIMASK': 98 | core.writeSpecialRegister(SYSM_PRIMASK, value); 99 | break; 100 | case 'CONTROL': 101 | core.writeSpecialRegister(SYSM_CONTROL, value); 102 | break; 103 | case 'N': 104 | core.N = boolValue; 105 | break; 106 | case 'Z': 107 | core.Z = boolValue; 108 | break; 109 | case 'C': 110 | core.C = boolValue; 111 | break; 112 | case 'V': 113 | core.V = boolValue; 114 | break; 115 | } 116 | } 117 | } 118 | 119 | async singleStep() { 120 | this.rp2040.step(); 121 | } 122 | 123 | async readRegisters(): Promise { 124 | const { core } = this.rp2040; 125 | const { registers, xPSR } = core; 126 | return { 127 | r0: registers[0], 128 | r1: registers[1], 129 | r2: registers[2], 130 | r3: registers[3], 131 | r4: registers[4], 132 | r5: registers[5], 133 | r6: registers[6], 134 | r7: registers[7], 135 | r8: registers[8], 136 | r9: registers[9], 137 | r10: registers[10], 138 | r11: registers[11], 139 | r12: registers[12], 140 | sp: registers[13], 141 | lr: registers[14], 142 | pc: registers[15], 143 | xPSR, 144 | MSP: core.readSpecialRegister(SYSM_MSP), 145 | PSP: core.readSpecialRegister(SYSM_PSP), 146 | PRIMASK: core.readSpecialRegister(SYSM_PRIMASK), 147 | CONTROL: core.readSpecialRegister(SYSM_CONTROL), 148 | 149 | N: !!(xPSR & 0x80000000), 150 | Z: !!(xPSR & 0x40000000), 151 | C: !!(xPSR & 0x20000000), 152 | V: !!(xPSR & 0x10000000), 153 | }; 154 | } 155 | 156 | async readUint8(address: number) { 157 | return this.rp2040.readUint8(address); 158 | } 159 | 160 | async readUint16(address: number) { 161 | return this.rp2040.readUint16(address); 162 | } 163 | 164 | async readUint32(address: number) { 165 | return this.rp2040.readUint32(address) >>> 0; 166 | } 167 | 168 | async readInt32(address: number) { 169 | return this.rp2040.readUint32(address) | 0; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /test-utils/test-driver.ts: -------------------------------------------------------------------------------- 1 | export interface ICortexRegisters { 2 | r0: number; 3 | r1: number; 4 | r2: number; 5 | r3: number; 6 | r4: number; 7 | r5: number; 8 | r6: number; 9 | r7: number; 10 | r8: number; 11 | r9: number; 12 | r10: number; 13 | r11: number; 14 | r12: number; 15 | sp: number; 16 | lr: number; 17 | pc: number; 18 | xPSR: number; 19 | MSP: number; 20 | PSP: number; 21 | PRIMASK: number; 22 | CONTROL: number; 23 | 24 | N: boolean; 25 | Z: boolean; 26 | C: boolean; 27 | V: boolean; 28 | } 29 | 30 | export type ICortexRegisterName = keyof ICortexRegisters; 31 | 32 | export interface ICortexTestDriver { 33 | init(): Promise; 34 | setPC(pcValue: number): Promise; 35 | writeUint8(address: number, value: number): Promise; 36 | writeUint16(address: number, value: number): Promise; 37 | writeUint32(address: number, value: number): Promise; 38 | setRegisters(registers: Partial): Promise; 39 | singleStep(): Promise; 40 | readRegisters(): Promise; 41 | readUint8(address: number): Promise; 42 | readUint16(address: number): Promise; 43 | readUint32(address: number): Promise; 44 | readInt32(address: number): Promise; 45 | tearDown(): Promise; 46 | } 47 | -------------------------------------------------------------------------------- /test/micropython-spi-test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import minimist from 'minimist'; 3 | import { bootromB1 } from '../demo/bootrom.js'; 4 | import { loadMicropythonFlashImage, loadUF2 } from '../demo/load-flash.js'; 5 | import { GPIOPinState, Simulator } from '../src/index.js'; 6 | import { ConsoleLogger, LogLevel } from '../src/utils/logging.js'; 7 | 8 | const args = minimist(process.argv.slice(2)); 9 | 10 | const simulator = new Simulator(); 11 | const mcu = simulator.rp2040; 12 | mcu.loadBootrom(bootromB1); 13 | mcu.logger = new ConsoleLogger(LogLevel.Error); 14 | 15 | const imageName = 'micropython.uf2'; 16 | console.log(`Loading uf2 image ${imageName}`); 17 | loadUF2(imageName, mcu); 18 | 19 | const littlefs = 'littlefs-spi.img'; 20 | 21 | if (fs.existsSync(littlefs)) { 22 | console.log(`Loading littlefs image ${littlefs}`); 23 | loadMicropythonFlashImage(littlefs, mcu); 24 | } 25 | 26 | let spiBuf = ''; 27 | mcu.gpio[5].addListener((state: GPIOPinState, oldState: GPIOPinState) => { 28 | if (!spiBuf) { 29 | return; 30 | } 31 | 32 | if (state === GPIOPinState.High && oldState === GPIOPinState.Low) { 33 | if (spiBuf !== args._?.shift()) { 34 | console.log('SPI TEST FAILED.'); 35 | process.exit(1); 36 | } else { 37 | console.log('SPI MESSAGE RECEIVED.'); 38 | spiBuf = ''; 39 | } 40 | 41 | if (args._.length === 0) { 42 | console.log('SPI TEST PASSED.'); 43 | process.exit(0); 44 | } 45 | } 46 | }); 47 | 48 | const transmitAlarm = mcu.clock.createAlarm(() => { 49 | mcu.spi[0].completeTransmit(0); 50 | }); 51 | mcu.spi[0].onTransmit = (char) => { 52 | spiBuf += String.fromCharCode(char); 53 | transmitAlarm.schedule(2000); // 2us per byte, so 4 MHz SPI 54 | }; 55 | 56 | mcu.core.PC = 0x10000000; 57 | simulator.execute(); 58 | -------------------------------------------------------------------------------- /test/micropython/main-spi.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, SPI 2 | 3 | spi = SPI(0) 4 | cs = Pin(5, Pin.OUT) 5 | cs(1) 6 | 7 | spi.init(baudrate=10 * 1024 * 1024, polarity=0, phase=0) 8 | cs(0) 9 | spi.write(b'hello world') 10 | cs(1) 11 | 12 | cs(0) 13 | spi.write(b'h') 14 | cs(1) 15 | 16 | cs(0) 17 | spi.write(b'0123456789abcdef0123456789abcdef0123456789abcdef') 18 | cs(1) -------------------------------------------------------------------------------- /test/micropython/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | 4 | version = sys.implementation.version 5 | versionstr = "{}.{}.{}".format(version[0], version[1], version[2]) 6 | 7 | while True: 8 | print("Hello, MicroPython! version: {}".format(versionstr)) 9 | time.sleep(1) 10 | -------------------------------------------------------------------------------- /test/mklittlefs.py: -------------------------------------------------------------------------------- 1 | # Simple script to create a littlefs image for running the MicroPython test 2 | 3 | from littlefs import LittleFS 4 | from os.path import basename 5 | from sys import argv 6 | 7 | files = argv[2:] 8 | # files = ['test/micropython/main.py'] 9 | output_image = argv[1] 10 | 11 | lfs = LittleFS(block_size=4096, block_count=352, prog_size=256) 12 | main = True 13 | for filename in files: 14 | with open(filename, 'r') as src_file, lfs.open("main.py" if main else basename(filename), 'w') as lfs_file: 15 | lfs_file.write(src_file.read()) 16 | main = False 17 | with open(output_image, 'wb') as fh: 18 | fh.write(lfs.context.buffer) 19 | 20 | print('Created littlefs image: {}'.format(output_image)) 21 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | littlefs-python==0.4.0 2 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "./dist/cjs" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "target": "es2018", 5 | "module": "NodeNext", 6 | "outDir": "./dist/esm" /* Redirect output structure to the directory. */, 7 | "strict": true /* Enable all strict type-checking options. */, 8 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 9 | "declaration": true, 10 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 11 | "skipLibCheck": true /* Skip type checking of declaration files. */, 12 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 13 | "useDefineForClassFields": false 14 | }, 15 | "include": ["src/**/*.ts"], 16 | "exclude": ["src/**/*.spec.ts"] 17 | } 18 | --------------------------------------------------------------------------------