Skip to content

Instantly share code, notes, and snippets.

@alwynallan
Last active April 10, 2026 12:51
Show Gist options
  • Select an option

  • Save alwynallan/de68ef48fc50cb406c015c0d975fa435 to your computer and use it in GitHub Desktop.

Select an option

Save alwynallan/de68ef48fc50cb406c015c0d975fa435 to your computer and use it in GitHub Desktop.
PIO_LET - Raspberry Pi Pico SDK project for PRNG on a state machine
; Rename to just pio_let.pio, the arrow is to get it first in a Gist
; (C) 2026 alwynallan@gmail.com MIT License
; pio_let is an efficient PRNG that runs on a RP2040 family state machine
; Prove me wrong - LFSR on a PIO state machine is impossible!
.program pio_let
; pio_sm_exec()s load initial state in osr; zero isr and y
.wrap_target
;set pins, 1 ; LED for testing
mov x, ::osr
next_2_bits:
out y, 2
dec_x_until_y_zero:
jmp x-- nowhere
nowhere:
jmp y-- dec_x_until_y_zero
in x, 2
jmp !osre next_2_bits
mov osr, ~isr ; osr full
mov isr, osr ; for push
;set pins, 0
push block ; mean cycle time 0.88 us
; at full rate, output will repeat every 63 minutes
.wrap ; CBC mode!
% c-sdk {
void pio_let_program_init(PIO pio, uint sm, uint offset, uint led_pin, uint initial) {
pio_gpio_init(pio, led_pin);
pio_sm_set_consecutive_pindirs(pio, sm, led_pin, 1, true);
pio_sm_config c = pio_let_program_get_default_config(offset);
sm_config_set_in_shift(&c, false, false, 32);
sm_config_set_out_shift(&c, true, false, 32);
sm_config_set_set_pins(&c, led_pin, 1);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); // makes initialization tricky
pio_sm_init(pio, sm, offset, &c);
// set initial value in x without TX_FIFO
for(int i=7; i>=0; i--) {
pio_sm_exec(pio, sm, pio_encode_set(pio_x, (initial>>(i*4)) & 15));
pio_sm_exec(pio, sm, pio_encode_in(pio_x, 4));
}
pio_sm_exec(pio, sm, pio_encode_mov(pio_osr, pio_isr));
pio_sm_exec(pio, sm, pio_encode_set(pio_isr, 0)); // in case of restart
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0)); // in case of restart
}
%}
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0-a4)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD adafruit_feather_rp2350 CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(pio_let_test C CXX ASM)
set(PICO_SYS_CLOCK 150000000)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(pio_let_test pio_let_test.c )
pico_set_program_name(pio_let_test "pio_let_test")
pico_set_program_version(pio_let_test "0.1")
# Generate PIO header
pico_generate_pio_header(pio_let_test ${CMAKE_CURRENT_LIST_DIR}/pio_let.pio)
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(pio_let_test 0)
pico_enable_stdio_usb(pio_let_test 1)
# Add the standard library to the build
target_link_libraries(pio_let_test
pico_stdlib
pico_rand)
# Add the standard include files to the build
target_include_directories(pio_let_test PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Add any user requested libraries
target_link_libraries(pio_let_test
hardware_pio
)
pico_add_extra_outputs(pio_let_test)
// (C) 2026 alwynallan@gmail.com MIT License
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/rand.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "pio_let.pio.h"
#define ROR32(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
#define rev32(value) __builtin_arm_rbit(value)
#define PIO_LET(osr) { \
uint32_t x = rev32(osr); \
uint32_t isr = 0; \
for(int i=0; i<16; i++) { \
x -= (osr & 3) + 1; \
osr = osr >> 2; \
isr = (isr << 2) | (x & 3); \
} \
osr = ~isr; \
}
// https://www.reddit.com/r/RNG/comments/1sfsez6/comment/of1c837/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
uint32_t inv(uint32_t isr) {
uint32_t x0; // will be the initial x = rev32(osr);
uint32_t osr = 0;
isr = ~isr;
for (int i = 0; i < 16; i++) {
osr <<= 2;
if (i != 15) {
osr |= ((isr >> 2) - isr - 1) & 3;
} else {
osr |= ( x0 - isr - 1) & 3;
}
if (i == 0) {
x0 = (osr >> 1) | ((osr & 1) << 1); // bit reverse
}
isr >>= 2;
}
return osr;
}
int main() {
stdio_init_all();
bool inv_correct = true;
for(int i=0; i<1000; i++) {
uint32_t a = get_rand_32();
uint32_t b = a; PIO_LET(b);
uint32_t c = inv(b);
if(a != c) inv_correct = false;
}
while(!stdio_usb_connected()) sleep_ms(100); // wait for usb host
if(!inv_correct) printf("Error case found in inv()\n");
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &pio_let_program);
printf("Loaded program at %d\n", offset);
printf("System Clock: %u Hz\n", clock_get_hz(clk_sys));
//uint in = 0;
uint in = get_rand_32();
pio_let_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN, in);
pio_sm_set_enabled(pio, sm, true);
uint exp = in, out, runs = 0;
while (--runs) {
uint t=time_us_32();
for(int i=0; i<2097152; i++) {
out = pio_sm_get_blocking(pio, sm);
//printf("%08x\n", out); stdio_flush();
PIO_LET(exp);
}
t = time_us_32() - t;
printf("8MB in %u ms\n", t/1000); stdio_flush();
if(out != exp) {
printf("Error\n");
stdio_flush();
}
}
while(1); // keeps USB alive for BOOTSEL
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment