452 Simple Signal Generator

452 : Simple Signal Generator

Design render
  • Author: Matt Venn
  • Description: Square wave generator with a phase-shifting second channel for metastability experiments
  • GitHub repository
  • Open in 3D viewer
  • Clock: 50000000 Hz

How it works

Two square wave generators drive uo[1:0].

Channel 0 (uo[0]): independent square wave. High-time (on_count) and low-time (off_count) are set independently via SPI, giving full control over frequency and duty cycle.

Channel 1 (uo[1]): phase-shifted replica of ch0. It uses the same on_count and off_count as ch0, but its rising edge is offset by a programmable delay. This is designed for exploring metastability on an SR latch: by placing ch1's edge close to ch0's edge, the two inputs can be brought arbitrarily close together.

Phase control

The total delay applied to ch1's rising edge is:

total_delay = spi_offset + enc_int + sigma_delta_carry

spi_offset (registers 4–5): signed 16-bit static offset in clock cycles, set via SPI. Positive = ch1 lags ch0; negative = ch1 leads ch0. Range: ±32767 cycles.

Encoder (ui[4]=A, ui[5]=B): a quadrature encoder adjusts the phase in real time. Each click changes the phase by enc_step / 256 cycles (Q8 fixed-point). The integer part of the encoder accumulator is added directly to the delay; the fractional part is dithered via first-order sigma-delta modulation, so sub-cycle offsets are averaged accurately over multiple periods. Encoder range: ±127 cycles (use spi_offset for coarse positioning).

enc_step (register 10): step size per encoder click in 1/256-cycle units. Examples:

  • enc_step = 1 → ~0.004 cycles/click (~78 ps at 50 MHz), finest resolution
  • enc_step = 64 → 0.25 cycles/click
  • enc_step = 128 → 0.5 cycles/click
  • enc_step = 255 → ~1 cycle/click

Encoder button (ui[2], PmodEnc's SWT pin, active-high): while held, multiplies enc_step by 8x for fast scanning across the phase range. Releasing it reverts the very next click to the normal step size.

ch1's delay (spi_offset + enc_int + sigma_delta_carry) covers the full [0, period) range with no clamping — it wraps modulo the period, so ch1's pulse can land anywhere relative to ch0's, including spanning the period boundary. (For very small periods combined with large spi_offset/encoder values — larger in magnitude than the period — the wrap is computed mod one period only, so the resulting delay may not match the mathematical mod for |total_delay| >= period; this is a pre-existing edge case unrelated to normal use.)

Frequency formula

At 50 MHz, for a 50% duty-cycle square wave at frequency F:

on_count = off_count = 25_000_000 // F

Both counts are 16-bit → minimum frequency ≈ 382 Hz (on_count = off_count = 65535).

Setting on_count = 0 silences both channels.

The SPI peripheral is reused from calonso88/tt07_alu_74181.

How to test

The SPI master (e.g. RP2350 running MicroPython) programs the design using machine.SoftSPI. Each SPI frame is 2 bytes:

  • Byte 0: 0x80 | reg_addr (write), or reg_addr (read)
  • Byte 1: data

SPI mode: CPOL and CPHA are set via ui[0] and ui[1] respectively (both 0 for mode 0).

Register map

Reg Field Notes
0 ch0 on_count[15:8] MSB
1 ch0 on_count[7:0] LSB
2 ch0 off_count[15:8] MSB
3 ch0 off_count[7:0] LSB
4 spi_offset[15:8] Signed 16-bit MSB; +ve=lag, −ve=lead
5 spi_offset[7:0] Signed 16-bit LSB
6 enc_step[7:0] Q8 step per encoder click (0–255)
7 reserved

MicroPython example — 10 kHz, 500-cycle lag, encoder fine-tune

from machine import Pin, SoftSPI

spi = SoftSPI(baudrate=100_000, polarity=0, phase=0, bits=8,
              firstbit=SoftSPI.MSB,
              sck=Pin(30), mosi=Pin(31), miso=Pin(28))
cs = Pin(29, Pin.OUT, value=1)

def write_reg(addr, val):
    cs(0); spi.write(bytes([0x80 | addr, val])); cs(1)

# ch0: 10 kHz, 50% duty (on=2500, off=2500 @ 50 MHz)
write_reg(0, 0x09); write_reg(1, 0xC4)  # on_count  = 2500
write_reg(2, 0x09); write_reg(3, 0xC4)  # off_count = 2500

# ch1: 500-cycle static lag (10 µs)
offset = 500  # cycles
write_reg(4, (offset >> 8) & 0xFF)
write_reg(5,  offset       & 0xFF)

# Encoder: 0.25 cycles/click for fine phase adjustment
write_reg(10, 64)

For RP2350 on the TT demo board, scripts/demo.py provides ready-made helpers (set_ch0, set_ch0_counts, set_phase_offset, set_enc_step, silence). Use scripts/run_freq.py to program from the command line:

python scripts/run_freq.py 1000 --offset 500 --enc-step 64

Encoder hardware

Connect a Digilent PModEnc to the bottom row of the input Pmod connector on the Tiny Tapeout demo board. This maps the encoder A/B outputs to ui[4] and ui[5], and the encoder's pushbutton (SWT) to ui[2] (fast-scan, see "Phase control" above).

Testing

Simulation (cocotb)

source /home/matt/oss-cad-suite/environment
cd test && make
Test What it checks
test_spi_registers Round-trip read/write of all config registers
test_frequency_generation ch0 outputs correct frequency (edge count)
test_ch1_inphase offset=0 → ch1 fires simultaneously with ch0
test_spi_phase_offset Positive SPI offset → ch1 lags ch0 by correct cycle count
test_channel_silence on_count=0 holds both outputs LOW
test_encoder_integer_phase Encoder clicks accumulate to integer cycle offset
test_encoder_sigma_delta Fractional enc_step dithers delay via sigma-delta
test_encoder_button_fast_scan Holding the encoder button multiplies enc_step by 8x
test_encoder_fast_scan_clamp_no_wrap Holding the button at ENC_MAX/ENC_MIN clamps correctly instead of wrapping (16->17-bit overflow fix)

Hardware-in-the-loop (HIL)

test/test_hil.py verifies frequency accuracy on real hardware using a Keysight oscilloscope and the RP2350. Probes on uo[0] (CH1) and uo[1] (CH2).

External hardware

  • RP2350 (or any SPI master) connected to uio[6:4] (MOSI, CLK, CS_N) and uio[3] (MISO)
  • Digilent PModEnc on the bottom row of the input Pmod connector (→ ui[4]=A, ui[5]=B, ui[2]=button/SWT for 8x fast-scan)
  • Oscilloscope on uo[1:0]
  • SR latch with S=uo[0] and R=uo[1] (or vice versa) for metastability experiments

IO

#InputOutputBidirectional
0SPI CPOLChannel 0 square wave output
1SPI CPHAChannel 1 square wave output
2Encoder button (hold for 8x fast-scan)
3SPI MISO
4Encoder ASPI CS_N (active low)
5Encoder BSPI CLK
6SPI MOSI
7

Chip location

Controller 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 (Tiny Tapeout Factory Test) tt_um_utoss_riscv (UTOSS RISC-V core) tt_um_memory_game_top (Number Memory Game) tt_um_danielpenas42 (Ball Display) tt_um_machinelearning (7-Segment Neural Predictor) tt_um_microlane_demo (microlane demo project) tt_um_pixel_processor (Tiny Pixel Processor) tt_um_jpigdon_gps_accelerator_top (GPS_Accelerator) tt_um_rgb_mixer (rgb_mixer) tt_um_bgao43 (Tiny TPU Systolic Array) tt_um_main (Pong in Verilog) tt_um_joannec34_teenytpu (teenytpu) tt_um_apa102_ws2812_squidgeefish (APA102 to WS2812 Translator) tt_um_uacj_bouncing_DVD_screensaver (Custom DVD Screensaver for VGA) tt_um_logoUACJ_MOGA (VGA_screensaver_UACJ) tt_um_grace_spi_led_driver (SPI-Controlled 8-Channel LED Driver) tt_um_rebeccargb_universal_decoder (Universal Binary to Segment Decoder) tt_um_rebeccargb_hardware_utf8 (Hardware UTF Encoder/Decoder) tt_um_happyhop_deadcast2 (happyhop) tt_um_dino7 (Dino-7: 7-Segment Runner Game) tt_um_arty3_mac_engine (Simple MAC Engine w/ Postproc) tt_um_uacj (Custom DVD Screensaver for VGA) tt_um_algofoogle_dottee (DOTTEE VGA demo (TTGF26a)) tt_um_mattvenn_signal_generator (Simple Signal Generator) tt_um_urish_simon (Simon Says memory game) tt_um_tpu (Tensor Processing Unit For GF) tt_um_gojimmypi_ttgf_UART_FSM_TRNG_Lab (Hardware Entropy Explorer: UART/SPI TRNG and PUF) tt_um_wokwi_465483277165299713 (First Tinytapeout) tt_um_prem_pipeline_test (Programmable_Pipeline-RISC-V) tt_um_wokwi_467219410242853889 (Tiny Tapeout testtest 111233) tt_um_wokwi_465549494272929793 (Pacos first design) tt_um_wokwi_465731371445677057 (Arturo's first Wokwi design) tt_um_wokwi_465732744934845441 (Tiny Tapeout Template_1234) tt_um_wokwi_465736492859711489 (Tiny Tapeout Workshop JuanF) tt_um_wokwi_465731430225727489 (Rafa’s first Wokwi design) tt_um_wokwi_465731458365332481 (7 segment Display Fli-Flop Try-out) tt_um_wokwi_465732744245929985 (DiseñoCursoTiny) tt_um_wokwi_465731490568160257 (Matt’s first Wokwi design) tt_um_wokwi_465736691688630273 (test1) tt_um_wokwi_465731458628527105 (Mi copia del Tiny Tapeout) tt_um_wokwi_465731520738845697 (El primer diseño) tt_um_wokwi_465731521356457985 (Tiny Tapeout Template Copy) tt_um_gen1_digital_companion_tile (Gen1 Digital Companion Tile) tt_um_wokwi_465732827753495553 (Tiny Tapeout Template Ayman) tt_um_wokwi_465731394728267777 (Julian_Proyecto) tt_um_wokwi_465731458535202817 (Tiny Tapeout Template Copy) tt_um_wokwi_465732847401723905 (Basic Circuit) tt_um_wokwi_465731452481768449 (El primer diseño de Matt para Wokwi) tt_um_wokwi_465731502018614273 (Tiny Tapeout Template flip flop) tt_um_wokwi_465732616714924033 (Tiny Tapeout RJAP) tt_um_wokwi_465731575275296769 (ocxpkeWokwiDesign) tt_um_wokwi_465732880722332673 (Pedro Template) tt_um_wokwi_465731858252480513 (Paula's first Wokwi design) tt_um_wokwi_465731455677830145 (Tiny Tapeout JMCG) tt_um_wokwi_465737601403996161 (Tiny Number Simon) tt_um_ttmul (Balanced Ternary Multiplier) tt_um_wokwi_465731466664816641 (Tiny Tapeout Workshop Malaga 2jun2026) tt_um_8bit_risc_cpu (8-bit RISC CPU) tt_um_wokwi_451184391728659457 (Simple Sprinkler) tt_um_fhw_appel_spiPWMio (spiPWMio) tt_um_divadnauj_GB_serv_soc_wb (serv_soc_wb) tt_um_8bitcustomcomputer (SAP 8 Bit Computer) tt_um_bioimpedance (Very Low Resource Digital Implementation of Bioimpedance Analysis) tt_um_mgj_bist8 (BIST-8: Built-In Self-Test for 8-bit CLA Adder) tt_um_roberto_tiny_radar_tile (BioPulse Tile) tt_um_systolic_mac_2x2 (2x2 Systolic Array Matrix Multiplier) tt_um_peg_top (2x2 CNN Accelerator PE Grid with UART) tt_um_AlvaroRub_ringcounter (Counter16Outputs) tt_um_wokwi_465731440267947009 (Antonio's first Wokwi design) tt_um_wokwi_465732706576877569 (Guille's first Wokwi design.) tt_um_wokwi_465731481873367041 (MIPS-Lite 8-bit Processor) tt_um_wokwi_465736612213902337 (Juan`s first Worki design) tt_um_wokwi_465731439156454401 (Rhyloo’s first Wokwi design) tt_um_wokwi_465732536551273473 (Tiny Tapeout Marcos Fernandez) tt_um_wokwi_465737290543084545 (Tiny Tapeout Template) tt_um_wokwi_465630130495825921 (ram 1 bit Copy) tt_um_wokwi_465731403724006401 (sdft wokwi 1) tt_um_top (RHD2164-MCU-SPI Bridge) tt_um_line_follower_arvaloez (Line Follower Robot controller) tt_um_xoroshiro64plus_v2 (xoroshiro64) tt_um_ohuettenhofer_tiny_qsim (Tiny Quantum Circuit Simulator) tt_um_santhosh_ring_osc_gf (Ring Oscillator PVT Sensor & TRNG (GF180)) tt_um_santhosh_stoch_stdp_pair_gf (Stochastic neuron + STDP controller (merged, GF180)) tt_um_santhosh_rsd_char_gf (RRAM Characterization Platform (DC sweep + endurance + retention + histogram, GF180)) tt_um_santhosh_xbar_ctrl_gf (Memristive Crossbar Peripheral Controller (GF180)) tt_um_joseph_bf (BF) tt_um_hydrocomms (FSK Modem) tt_um_systolic_array (2x2 MAC Systolic array with DFT) tt_um_kluterirv_rv32e_core (Minimal RV32E SoC with UART Loader) tt_um_algofoogle_ttgf26a_vco (VCO driven by DAC) tt_um_fer_logo_music_vga (UNIZG-FER VGA project) tt_um_maqsudbek_dyadic_pwm (Dyadic PWM) tt_um_waferspace_vga_screensaver (Wafer.space Logo VGA Screensaver) tt_um_htfab_vga_tester (Video mode tester)