586 SPELL

586 : SPELL

Design render
  • Author: Uri Shaked
  • Description: SPELL is a minimal, cryptic, stack-based programming language crafted for The Skull CTF
  • GitHub repository
  • Clock: 10000000 Hz

How it works

SPELL is a minimal, stack-based programming language created for The Skull CTF.

The language is defined by the following cryptic piece of Arduino code:

void spell() {

                  uint8_t*a,pc=16,sp=0,
               s[32]={0},op;while(!0){op=
            EEPROM.read(pc);switch(+op){case
          ',':delay(s[sp-1]);sp--;break;case'>':
         s[sp-1]>>=1|1;break;case'<':s[sp-1]<<=1;
        break;case'=':pc=s[sp-1]-1;sp--;break;case
       '@':if(s[sp-2]){s[sp-2]--;pc=s[sp-1]-1;sp+=
      1;}sp-=2;break;case'&':s[sp-2]&=s[sp-1];sp-=1;
      break;case'|':s[sp-2]|=s[sp-1];sp-=1;break;case
    '^':s[sp-2]^=s[sp-1];sp--;break;case'+':s[sp-2]+=
   s[sp-1];sp=sp-1;break;case'-':s[sp-2]-=s[sp-1];sp--;
   break;case'2':s[sp]=s[sp-1];sp=sp+1;break;case'?':s[
 sp-1]=EEPROM.         read(s[sp-1]|0        );break;case
  "!!!"[0]:             666,EEPROM              .write(s
   [sp-1]                ,s[sp-2]                );sp=+
    sp-02;               ;break;                 1;case
    "Arr"[               1]:  s[+               sp-1]=
    *(char*)            (s[+   sp-1           ]);break
      ;case'w':*   (char*)(     s[+sp-1])  =s[sp-+2];
        sp-=2;break;case+       'x':s[sp] =s[sp-1
           ];s[sp-1]=s[sp   +    -2];s[sp-2]=s[
            0|sp];break;    ;;    case"zzz"[0
             ]:sleep();"   Arrr  ";break;case
             255  :return;;  default:s  [sp]
              =+   op;sp+=    1,1   ;}pc=
               +    pc  +      1;      %>

}

This design is an hardware implementation of SPELL with the following features:

  • 256 bytes of program memory (volatile, simulates EEPROM)
  • 32 bytes of stack memory
  • 32 bytes of data memory
  • 8 bidirectional pins and up to 8 output-only pins

Initially, all the program memory is filled with 0xFF, and the stack and data memory are filled with 0x00. The program counter is set to 0x00, and the stack pointer is set to 0x00.

To load a program or inspect the internal state, the design provides access to the following registers via a simple serial interface:

Address Register name Description
0x00 PC Program counter
0x01 SP Stack pointer
0x02 EXEC Execute-in-place (write-only)
0x03 STACK Stack access (read the top value, or push a value)

The serial interface is implemented using a shift register, which is controlled by the following signals:

Pin Type Description
reg_sel input Select the register to read/write
load input Load the selected register with the value from the shift register
dump input Dump the selected register value to the shift register
shift_in input Serial data input
shift_out output Serial data output (when porta[3] is disabled)

When load is high, the value from the shift register is loaded into the selected register. When dump is high, the value of the selected register is dumped into the shift register, and can be read after two clock cycles by reading shift_out (MSB first).

For example, if you want to read the value of the program counter (PC), you would:

  1. Set reg_sel to 0x00 and set dump to 1
  2. Wait for two clock cycles for the first bit (MSB) to appear on shift_out.
  3. Read the remaining bits from shift_out on each clock cycle.

To write a value to the program counter, you would:

  1. Write the value to the shift register, one bit at a time, starting with the MSB.
  2. Set reg_sel to 0x00 and set load to 1.
  3. Wait for a single clock cycle for the value to be loaded.

Writing an opcode to the EXEC register will execute the opcode in place, without modifying the program counter (unless the opcode is a jump instruction).

The STACK register is used to push a value onto the stack or read the top value from the stack (for debugging purposes).

Data memory and registers

The data memory space is divided into two regions:

Address range Description
0x00 - 0x1F General-purpose data storage (data memory)
0x20 - 0x5F I/O and control registers

Other addresses are reserved for future use, and should not be accessed.

The following registers are available in the data memory space:

Address Name Description
0x36 PINB Read the value of the portb pins, or toggle the output when written to
0x37 DDRB Set the direction of the portb pins (0 = input, 1 = output)
0x38 PORTB Write to the portb pins
0x39 PINA Toggle the output on porta pins (write only; read returns 0x00)
0x3A DDRA Enables of the porta pins (0 = disabled, 1 = output)
0x3B PORTA Write to the porta (output only) pins

For example, to toggle the value of the portb[2] (uio[2]) pin, you would write 0x04 to the PINB register.

The porta[3:0] pins are also used for debug output, and their function is determined by the DDRA register:

Output pin DDRA[n] == 0 DDRA[n] == 1
0 sleep porta[0]
1 stop porta[1]
2 wait_delay porta[2]
3 shift_out porta[3]
4 0 porta[4]
5 0 porta[5]
6 0 porta[6]
7 0 porta[7]

How to test

To test SPELL, you need to load a program into the program memory and execute it. You can load the program by repeatedly executing the following steps for each byte of the program:

  1. Write the byte to the top of the stack (using the STACK register)
  2. Write the address of the byte in the program memory to top of the stack
  3. Write the opcode ! to the EXEC register

After loading the program, you can execute it by writing the address of the first byte in the program memory to the PC register, and then pulsing the run signal.

Test programs

The following program will spell "SPELL" on the Tiny Tapeout demo board's 7-segment display: (see what we did there?)

[127, 58, 119, 0, 129, 57, 57, 244, 62, 116, 109, 50, 0, 38, 94, 59, 119, 250, 44, 0, 59, 119, 25, 44, 11, 64, 3, 61]

The program bytes should be loaded into the program memory starting at address 0.

And of course, the obligatory blink, rapidly blinking an LED connected to the uio[0] pin:

[1, 55, 119, 1, 54, 119, 250, 44, 3, 61]

External hardware

None

IO

#InputOutputBidirectional
0runsleep/porta[0]portb[0]
1stepstop/porta[1]portb[1]
2loadwait_delay/porta[2]portb[2]
3dumpshift_out/porta[3]portb[3]
4shift_inporta[4]portb[4]
5reg_sel[0]porta[5]portb[5]
6reg_sel[1]porta[6]portb[6]
7porta[7]portb[7]

Chip location

Controller Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux tt_um_chip_rom (Chip ROM) tt_um_factory_test (TinyTapeout Factory Test) tt_um_MichaelBell_tinyQV (TinyQV Risc-V SoC) tt_um_urish_silife_max (Game of Life 8x32 (siLife)) tt_um_vc32_cpu (VC 16-bit CPU) tt_um_tinytapeout_logo_screensaver (VGA Screensaver with Tiny Tapeout Logo) tt_um_htfab_rotfpga2 (ROTFPGA v2a) tt_um_htfab_latch_test (Latch test) tt_um_no_time_for_squares_tommythorn (No Time For Squares, IHP edition) tt_um_tommythorn_maxbw (Asynchronous Multiplier) tt_um_urish_simon (Simon Says memory game) tt_um_htfab_rotfpga2_ff (ROTFPGA v2b) tt_um_meiniKi_ttihp_fazyrv_exotiny (FazyRV-ExoTiny) tt_um_rebeccargb_hardware_utf8 (Hardware UTF Encoder/Decoder) tt_um_rebeccargb_universal_decoder (Universal Binary to Segment Decoder) tt_um_algofoogle_raybox_zero (raybox-zero TTIHP0p2 edition) tt_um_z2a_rgb_mixer (RGB Mixer demo) tt_um_vga_clock (VGA clock) tt_um_frequency_counter (Frequency counter) tt_um_brandonramos_VGA_Pong_with_NES_Controllers (VGA Pong with NES Controllers) tt_um_demosiine_sda (DemoSiine) tt_um_toivoh_demo_deluxe (Sequential Shadows Deluxe [TT08 demo competition]) tt_um_edwintorok (Rounding error) tt_um_2048_vga_game (2048 sliding tile puzzle game (VGA)) tt_um_hpretl_spi (SPI Test) tt_um_top_mole99 (One Sprite Pony) tt_um_urish_spell (SPELL) tt_um_rebeccargb_vga_pride (VGA Pride) tt_um_autosel (I2C EEPROM Project Selection) tt_um_a1k0n_nyancat (VGA Nyan Cat) tt_um_a1k0n_vgadonut (VGA donut) tt_um_rebeccargb_colorbars (Color Bars) tt_um_crispy_vga (Crispy VGA) tt_um_kbeckmann_flame (Flame demo) tt_um_jamesrosssharp_1bitam (1bit_am_sdr) tt_um_simon_cipher (simon_cipher) tt_um_htfab_bouncy_capsule (Bouncy Capsule) tt_um_phansel_laplace_lut (Experiment Number Six: Laplace LUT) tt_um_kianv_bare_metal (KianV RISC-V RV32E Baremetal SoC) tt_um_calonso88_rsa (8 bit RSA encryption) tt_um_silice (Warp) tt_um_rejunity_vga_test01 (VGA Drop (audio/visual demo)) tt_um_a1k0n_demo (Demo by a1k0n) tt_um_MichaelBell_canon (TT08 Pachelbel's Canon demo) tt_um_htfab_caterpillar (Simon's Caterpillar) tt_um_ravenslofty_chess (Chess) tt_um_fountaincoder_top_V2 (maddihp) tt_um_tomkeddie_a (VGA Experiments in Tennis) tt_um_MichaelBell_mandelbrot (VGA Mandelbrot) tt_um_MichaelBell_rle_vga (RLE Video Player) tt_um_jayjaywong12 (mulmul) tt_um_wokwi_392873974467527681 (PILIPINASLASALLE) tt_um_froith_goldcrest (Goldcrest RISC-V) tt_um_dvxf_dj8v (DJ8 8-bit CPU) tt_um_hpretl_minilogix (Minilogix) tt_um_tomkeddie_b (Transmit UART) tt_um_joerdsonsilva_modem (Multimode Modem) tt_um_oled_frequency_counter (Frequency Counter SSD1306 OLED) tt_um_stochastic_addmultiply_CL123abc (Stochastic Multiplier, Adder and Self-Multiplier) tt_um_QIF_8bit (8 Bit Digital QIF) tt_um_toivoh_retro_console (Retro Console) tt_um_cejmu (CEJMU Beers and Adders) tt_um_rejunity_sn76489 (Classic 8-bit era Programmable Sound Generator SN76489) tt_um_dlmiles_tt05_i2c_bert (I2C BERT) tt_um_dlmiles_muldiv8 (MULDIV unit (8-bit signed/unsigned)) tt_um_dlmiles_loopback (IHP loopback tile with input skew measurement) tt_um_dlmiles_bad_synchronizer (Example of Bad Synchronizer) tt_um_wokwi_407306064811090945 (DDR throughput and flop aperature test) tt_um_urish_giant_ringosc (Giant Ring Oscillator (3853 inverters)) tt_um_digital_clock_example (Digital Desk Clock v2.0) tt_um_rejunity_z80 (Zilog Z80) tt_um_rejunity_ay8913 (Classic 8-bit era Programmable Sound Generator AY-3-8913) tt_um_rtfb_collatz (Collatz conjecture brute-forcer) tt_um_ccattuto_conway (Conway's Game of Life on UART and VGA) tt_um_snow (Snow) tt_um_calonso88_74181 (8-bit ALU based on 2x 74181) tt_um_rejunity_vga_logo (VGA Tiny Logo (1 tile)) tt_um_NicklausThompson_SkyKing (SkyKing Demo) tt_um_htfab_cells (Cell mux) tt_um_htfab_pg_1x1 (Power gating test (1x1)) tt_um_htfab_pg_1x2 (Power gating test (1x2)) tt_um_dlmiles_ringosc_5inv (Ring Oscillator (5 inverter)) tt_um_devinatkin_pulse_width_counter (Pulse Width Counter) tt_um_algofoogle_vga_fun_wrapper (TTIHP VGA FUN!) tt_um_cfib_demo (cfib Demoscene Entry) tt_um_vga_glyph_mode (Glyph Mode) tt_um_favoritohjs_scroller (VGA Scroller) tt_um_pulse_generator (TTL Pulse Generator) tt_um_rajum_iterativeMAC (Iterative MAC) tt_um_algofoogle_tinyvga_fun_wrapper (TTIHP TinyVGA FUN!) tt_um_urish_sram_test (SRAM (1024x8) test) tt_um_one_bit_puf_wrapper (One Bit PUF) tt_um_multi_bit_puf_wrapper (One Bit PUF) tt_um_gray_sobel (Gray scale and Sobel filter) tt_um_rebeccargb_intercal_alu (INTERCAL ALU)