npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@padcom/pico-risc-v-hart

v0.1.0

Published

A RISC-V (RV64GC) computer for bare-metal programs, in Node.js and browsers: compile C, run it, talk to its UART. The core of Pico RISC-V.

Readme

@padcom/pico-risc-v-hart

A RISC-V computer in a function call. Give it a program, compiled for RISC-V, and get back its exit code and what it printed. Its UART goes where you say: your terminal, a callback that sees every character as it's written, or both. It runs in Node.js and in browsers.

It's the core of Pico RISC-V, the emulator that boots Debian in a browser tab: the same RV64GC CPU, with the same JIT that compiles hot RISC-V code to WebAssembly, in a small bare-metal computer.

import { readFileSync } from 'node:fs'
import { run } from '@padcom/pico-risc-v-hart'

const { exitCode, output } = await run(readFileSync('hello.elf'))

Try it

The package comes with an example: a hello world that asks for your name over the UART, already compiled.

npm install @padcom/pico-risc-v-hart
node node_modules/@padcom/pico-risc-v-hart/examples/hello/run.mjs       # type your name
node node_modules/@padcom/pico-risc-v-hart/examples/hello/capture.mjs   # a script answers
Hello from RISC-V! What's your name? Ada
Nice to meet you, Ada! Your name has 3 letters: that's my exit code.
[hello.elf exited with code 3, after 1000687 instructions]

Compiling C for it

You need a RISC-V GCC for bare metal (macOS: brew install riscv64-elf-gcc; Debian/Ubuntu: apt install gcc-riscv64-unknown-elf). The package's c/ directory has the rest:

  • crt0.S: the startup code. It sets up the stack, clears .bss, calls main(), and exits with what main() returns.
  • pico.ld: the linker script. Code goes at 0x80000000, the stack 1 MiB above it.
  • pico.h: the UART (pico_putc, pico_puts, pico_putu, pico_getc, pico_gets, pico_ready) and pico_exit(code).
#include "pico.h"

int main(void) {
    pico_puts("Hello, world!\n");
    return 0;
}
C=node_modules/@padcom/pico-risc-v-hart/c
riscv64-elf-gcc -march=rv64imac -mabi=lp64 -mcmodel=medany -O2 \
  -ffreestanding -nostdlib -nostartfiles -I $C -T $C/pico.ld \
  $C/crt0.S hello.c -o hello.elf

Floating point works too (-march=rv64gc -mabi=lp64d). The start-up code turns the FPU on. There's no C library, so no printf. Bring your own, or use one for embedded systems (picolibc, newlib).

The UART

run() and Computer take a uart: where what the program writes goes, and where what it reads comes from.

import { both, captureUart, consoleUart, run } from '@padcom/pico-risc-v-hart'

// The terminal (the default): output only...
await run(program)
// ...or your keyboard too (raw mode: every key as you type it).
await run(program, { uart: consoleUart({ input: true }) })

// Captured: every character as it's written, all of it at the end,
// and input whenever you like.
const uart = captureUart({
  onText: text => process.stdout.write(text),
  input: 'Ada\r', // (there when it starts reading)
})
const result = await run(program, { uart })
uart.text // everything it wrote (result.output has it too)
uart.send('more input\r')

// Both at once: shown on the terminal and captured.
await run(program, { uart: both(consoleUart(), captureUart({ onText })) })

Or write your own: anything with a write(byte) method is a UART. attach(send) is optional and gets the function that types into the program; close() is called when the program stops.

The API

run(program: Uint8Array, options?: ComputerOptions): Promise<RunResult>

The program can be an ELF executable (64-bit RISC-V; its segments go where it says, in RAM, and it starts at its entry point), or a flat binary, which goes at the start of RAM and starts there.

| Option | Default | | |---|---|---| | uart | consoleUart() | where the UART goes | | memory | 16 MiB | RAM, in bytes | | maxInstructions | none | stop after this many | | signal | | an AbortSignal that stops it | | jit | true | compile hot code to WebAssembly |

The result:

| Field | | |---|---| | reason | 'exit' (it exited), 'limit' (maxInstructions) or 'aborted' | | exitCode | 0 to 255, with 'exit' | | output | everything it wrote to the UART, as text | | instructions | how many it ran |

For more control, use Computer: load(program), run(), send(input) while it runs, and stop(). Its cpu, uart and clint are there too. The building blocks (CPU, SystemBus, RAM, Terminal, Clint, Syscon, parseElf, and a small assembler, asm) are exported for machines of your own. That's how Pico RISC-V's Linux machine is built.

The machine

  • CPU: one RV64GC hart (RV64IMAFDC, Zicsr, Zifencei), in machine, supervisor and user modes, with Sv39 virtual memory. Hot code is compiled to WebAssembly.

  • Memory map:

    | Address | | |---|---| | 0x1000_0000 | UART (16550-style); its interrupt is the hart's machine external interrupt (mip.MEIP) | | 0x1100_0000 | CLINT: mtime (1 MHz, real time), mtimecmp, msip | | 0x1110_0000 | syscon: write 0x5555 to exit with code 0, or code << 16 \| 0x3333 to exit with code | | 0x8000_0000 | RAM |

  • Waiting: a program waiting in wfi sleeps (no CPU) until its timer or input wakes it. One that polls the UART in a loop keeps running at full speed.

In a browser, compile the core first and hand it over: setCoreModule(await WebAssembly.compileStreaming(fetch(<url of the package's build/hart.wasm>))). (Node.js reads it from the package by itself.)

Who wrote it

Every line of it was written by Claude (Anthropic's AI), in Claude Code, as part of Pico RISC-V.

License: ISC.