390 IEEE Digital EEG Threshold Event Detector

390 : IEEE Digital EEG Threshold Event Detector

Design render
  • Author: Jacob Kebaso
  • Description: A digital threshold-crossing event detector with hysteresis and a refractory period, intended as the event-detection stage downstream of an external ADC digitizing an EEG signal. No analog pins are used; all thresholding and hysteresis logic is implemented digitally.
  • GitHub repository
  • Open in 3D viewer
  • Open in Tiny Tapeout Explorer
  • Clock: 10000 Hz

What is this project?

This project is a digital threshold-crossing event detector with hysteresis and a refractory period — effectively a "digital Schmitt trigger" designed to sit downstream of an external ADC that has already digitized a physiological signal such as an EEG channel.

The motivation comes from wearable seizure-detection hardware. A common building block in EEG-based seizure onset detectors is a threshold-crossing detector: raise a flag when a signal feature (raw amplitude, line length, band energy, etc.) exceeds a threshold for long enough to be a real event rather than noise, and only clear that flag once the signal has genuinely settled back down — not the instant it dips below the trigger point. That "settle back down" behaviour is hysteresis, and doing it with two separate thresholds (a high arming threshold and a lower re-arming threshold) is exactly how an analog Schmitt trigger comparator behaves, except implemented here entirely in synchronous digital logic.

No analog pins are used anywhere in this design. All thresholding, hysteresis, and timing logic is implemented as ordinary synthesizable Verilog operating on an 8-bit digital sample bus. This keeps the design low-risk for a first tapeout (fully simulatable and testable with cocotb before submission) while still directly modelling a real analog behaviour in the digital domain.

The project is intended as a standalone building block that could later feed into a larger seizure-detection or neural-event-detection pipeline — the output is a simple digital flag that a downstream microcontroller or state machine can act on (log the event, trigger an alert, wake up a higher-power processing stage, etc.).

How it works

Interface summary

Pin Direction Meaning
ui_in[7:0] in Digitized sample value (normal mode) or configuration value (config mode)
uio_in[0] in cfg_mode — 1 selects a configuration write cycle, 0 selects normal sample processing
uio_in[2:1] in cfg_addr — selects which configuration register is written during a config cycle
uio_in[7:3] in unused
uio_out[7:0] out unused, driven low
uio_oe[7:0] out all zero — every uio pin is an input
uo_out[0] out event_detected — high while the detector is in the DETECTED state
uo_out[1] out armed — high while the detector is in the MONITOR (idle/watching) state
uo_out[2] out above_high — raw comparator debug bit: is the current sample ≥ th_high?
uo_out[3] out in_refractory — high while the detector is in the REFRACTORY state
uo_out[7:4] out unused, driven low

Configuration registers

The detector has four 8-bit configuration registers, written one at a time over ui_in while cfg_mode = 1:

cfg_addr Register Meaning
2'b00 th_high Arming threshold. A sample at or above this value counts toward triggering a detection.
2'b01 th_low Re-arming (hysteresis) threshold. Must be set lower than th_high. The detection flag is only cleared once the sample drops below this value — not merely below th_high.
2'b10 debounce_limit Number of consecutive samples that must be at or above th_high before a detection is actually declared. This suppresses single-sample noise spikes.
2'b11 refractory_len Number of clock cycles the detector holds in a refractory (locked-out) state after a detection clears, before it can arm and trigger again.

On reset, sensible defaults are loaded automatically (th_high = 200, th_low = 150, debounce_limit = 4, refractory_len = 32) so the design is meaningfully functional even before any configuration writes occur — useful for quick bench testing.

Detection state machine

The core of the design is a three-state finite state machine, evaluated once per clock cycle whenever cfg_mode = 0:

  1. MONITOR (idle / armed): the detector watches incoming samples. Each cycle a sample is at or above th_high, an internal counter increments; any sample below th_high resets that counter to zero. Once the counter reaches debounce_limit, the state machine transitions to DETECTED.
  2. DETECTED: the event_detected output is asserted. The detector stays in this state — continuing to report a detected event — for as long as the sample remains at or above th_low. This is the hysteresis band: even if the signal dips briefly below th_high (but stays above th_low), the detection is not cleared prematurely.
  3. REFRACTORY: once the sample genuinely falls below th_low, the detector clears event_detected and enters a lockout period lasting refractory_len clock cycles, during which no new detection can be declared even if the signal spikes again immediately. This mirrors the refractory period of a biological neuron and prevents rapid re-triggering on a single noisy event. After the countdown reaches zero, the detector returns to MONITOR and can arm again.

Why this design avoids analog pins

A "true" analog comparator front end was considered first, but Tiny Tapeout's standard digital IO pads already buffer every ui_in/uio_in signal through a fixed-threshold (and possibly hysteretic) digital buffer before user logic ever sees it — so a hand-designed transistor-level comparator inside a normal digital tile cannot see a true continuous analog input without a ua[] analog pin. Rather than fight that constraint, this project embraces it: it assumes an external ADC (or a companion microcontroller, which is realistic for an actual EEG wearable pipeline anyway) performs the analog-to-digital conversion, and this chip implements the threshold/hysteresis/refractory logic that would otherwise require an analog comparator — entirely in the digital domain, fully synthesizable, and fully verifiable in simulation before submission.

How to test

Simulation (cocotb)

The included cocotb testbench (test.py) exercises the full state machine and should be run before submission using the standard Tiny Tapeout test flow (from the project's test/ directory):

make -B

The testbench does the following, in order:

  1. Applies reset and confirms the detector powers up in the MONITOR (armed) state.
  2. Writes all four configuration registers (th_high=100, th_low=60, debounce_limit=3, refractory_len=5) using the cfg_mode/cfg_addr protocol.
  3. Drives a run of low-amplitude "baseline" samples and confirms event_detected stays low and armed stays high.
  4. Drives a run of samples above th_high for more than debounce_limit cycles and confirms event_detected asserts.
  5. Drives a sample that falls between th_low and th_high and confirms the detection is not cleared (verifying hysteresis).
  6. Drives a sample below th_low and confirms the detector transitions to REFRACTORY, clearing event_detected.
  7. Waits out the refractory period and confirms the detector re-arms (armed goes high again).
  8. Repeats a high-amplitude run and confirms the detector can re-trigger after re-arming.

Bench testing on real silicon

Once the chip is back and mounted on the Tiny Tapeout demo board:

  1. Hold rst_n low briefly, then release it — the detector will power up in MONITOR with the default thresholds already active, so you can test immediately without sending any configuration writes.
  2. Drive ui_in[7:0] from a function generator through an external ADC (or from a microcontroller producing 8-bit digital samples) representing your signal of interest.
  3. Watch uo_out[1] (armed) and uo_out[0] (event_detected) on a logic analyzer or LEDs. Slowly ramp the input above the default th_high (200) for a few cycles and confirm event_detected goes high; bring it back down below th_low (150) and confirm the detector clears and then re-arms after the refractory window.
  4. To try custom thresholds, drive uio_in[0] = 1 with uio_in[2:1] set to the desired register address and ui_in set to the desired 8-bit value, for one clock cycle per register, then return uio_in[0] to 0 before resuming normal sample streaming.

External hardware

No external hardware is strictly required to exercise the digital logic itself (samples can be driven directly as 8-bit digital values from a microcontroller or test bench). For an end-to-end demonstration against a real analog signal, an external ADC (e.g. an SPI ADC such as the MCP3008, or a microcontroller's built-in ADC) is needed to digitize the analog signal into the 8-bit sample stream expected on ui_in.

IO

#InputOutputBidirectional
0sample[0]event_detectedcfg_mode
1sample[1]armedcfg_addr[0]
2sample[2]above_high (debug)cfg_addr[1]
3sample[3]in_refractory (debug)
4sample[4]
5sample[5]
6sample[6]
7sample[7]

Chip location

Controller Mux Mux Mux Mux Mux Mux Mux Mux Mux Mux Analog Mux Mux Mux Mux Mux Mux Mux Mux tt_um_chip_rom (Chip ROM) tt_um_factory_test (Tiny Tapeout Factory Test) tt_um_ieee_LDO (LDO) tt_um_chip_ieee_analog (IEEE Bandgap Reference) tt_um_snn_voice_calculator_mauro_ciccone (snn-voice-calculator) tt_um_hx2003_delay (4 Channel - 32 Tap Programmable Delay with Delay Locked Loop Calibration) tt_um_adxl362_test (tt_um_adxl362_test) tt_um_larsnit_cfar (1D CA/GO/SO CFAR radar detector) tt_um_abeccari_swsynth (Sine Wave Synthesizer) tt_um_dpi_adexp (AdExp DPI Neuron ) tt_um_140oo041_fpu130 (FPU-130) tt_um_blonghi_uart (uart) tt_um_directsgg_mini_proceo_8bit (Mini 8-bit Processor) tt_um_umaece1982_lfsr (Low-Power LFSR-Based Test Pattern Generator) tt_um_deploy_timer (launch deployment timer) tt_um_urish_simon (Simon Says memory game) tt_um_nimelli_kinematic_wave_engine (Kinematic Wave Engine) tt_um_multi_seg_monitor (Multi Segment Monitor) tt_um_UART_TX (project) tt_um_crc8_lfsr (CRC-8 Serial LFSR) tt_um_tinynpu4 (TinyNPU4) tt_um_alu_bns (6-bit multi function ALU ( eldawly_V2) ) tt_um_echoworld424_tpv (Timing-Prediction Test Vehicle) tt_um_gyro_lockin (Laser Gyro Lock-in Readout Core) tt_um_josue_olivos_sar_adc (4-Bit Charge-Redistribution SAR ADC Controller) tt_um_flower (VGA Flower) tt_um_vperumal_l1_fabric (Scalable Banked L1 Memory Fabric for Edge AI) tt_um_preinception_top (Preinception: Simple Compute Accelerator) tt_um_italu (iTALU: Interactive Testable Arithmetic Logic Unit) tt_um_neuron (4-Input Signed Neuron / Perceptron) tt_um_4tap_mac (4-Tap Signed MAC Unit) tt_um_mac_engine (DSP MAC Engine) tt_um_crypto_led_demo (QAMER CryptoUART: Encrypted UART with LED Status) tt_um_layernorm (LayerNorm) tt_um_ez130_8t_mystery (EZ130 8T Mystery Circuit) tt_um_sent2spi (SENT Receiver with SPI Interface) tt_um_llr_hepiarisc (Hepiarisc with SPI flash) tt_um_rebeccargb_vga_pride (VGA Pride) tt_um_hasi_ising (Oscillator Ising Machine) tt_um_c061618g2 (Circuitli C061618G2) tt_um_tiny_dram_pim (Tiny Dual-Channel DRAM-PIM Controller + PU) tt_um_Tbilisi_CORDIC_Engine (Tbilisi CORDIC Engine) tt_um_rahulmascarenhas_folded_nn (Frozen ternary backbone + loadable head) tt_um_miniMAC (miniMAC_IHP26b) tt_um_rumcajs (IEEE DOORSH) tt_um_sg13g2_mystery (SG13G2 Mystery Circuit) tt_um_ULSR88 (ULSR demo) tt_um_ez130_7t_mystery (EZ130 7T Mystery Circuit) tt_um_tinyopt4 (ieee_tt_tinyopt4) tt_um_vga_example (IEEE VGA Animated Beach) tt_um_hyphen133_drone_detection (IEEE Acoustic Drone Detector) tt_um_nuatlabs_fifo_pwm (Async FIFO with CDC + PWM Peripheral) tt_um_nuatlabs_uart (8N1 UART Transceiver) tt_um_eeg_threshold_detector (IEEE Digital EEG Threshold Event Detector) tt_um_smart_traffic (Smart Traffic Light Controller) tt_um_94442024_mini_cpu (Mini 8-bit Accumulator CPU) tt_um_wokwi_475369131246576641 (IEEE_UPB_TT_1) tt_um_aion (AION) tt_um_rebeccargb_hardware_utf8 (Hardware UTF Encoder/Decoder) tt_um_rebeccargb_universal_decoder (Universal Binary to Segment Decoder) tt_um_rebeccargb_intercal_alu (INTERCAL ALU) tt_um_flappy_bird (IEEE Flappy Bird VGA Game) tt_um_oryan01_alu (ALU CASS PUCV) tt_um_S4xU4 (S4xU4) tt_um_vga_ca (Space CA) tt_um_llr_simplenpu (simple SPI flash streaming NPU) tt_um_pucv_pspwm (3LFCC PS-PWM Modulator) tt_um_yuri_fpga (Tiny FPGA) tt_um_mikailgedik_inverted_inverters (Inverted inverters) tt_um_esauqch_hamming74 (Hamming(7,4) encoder/decoder (IEEE)) tt_um_hackin7_analog_experiments (TinyAnalogExperiments) tt_um_snake (snake game) tt_um_mini_kraken (Kraken IO Subprocessor) tt_um_fabien_pio (AstraPIO) tt_um_chiplab (ChipLab) tt_um_wokwi_475490677474407425 (Tiny_Divider) tt_um_c061618g2tr (Circuitli C061618G2TR) tt_um_catalinlazar_nanopio (nanoPIO) tt_um_catalinlazar_uart_spi_i2c_bridge (UART-SPI-I2C Bridge) tt_um_enzonappi_sent_i2c (SENT to I2C bridge) tt_um_kush1434_proof (Proof) tt_um_schwallsunk_signal_discriminator (Highspeed voltage discriminator) tt_um_tiarinix_ttihp_verilog_template (8-bit educational SAP-style CPU) tt_um_vga_glyph_mode (BOOTCAMP) tt_um_GiulioGirelli_packet_processor (Configurable Low-Latency Match-Action Packet Processor) tt_um_vga_tictactoe (Tic Tac Toe) tt_um_vga_dvd_player (DVD player) tt_um_clea_katseye_rain (KATSEYE) tt_um_romd_uart_hello (UART Hello World) tt_um_vga_snake (CDM PYTHON GAME) tt_um_vga_slot_machine (tt_um_vga_slot_machine) tt_um_jet_seq8b (SEQ8 Programmable Sequencer) tt_um_kibo_leak_inspect (KIBO Leak-Inspection Target Controller (VGA)) tt_um_endless_runner (Endless Runner) tt_um_omega_infinity_kaoru (OMEGA INFINITY KAORU 3D Metal Grid Processor) tt_um_nikleberg_mixer (Mixer) tt_um_lahnb_sgdma (TinyDMA: A Descriptor-Based Dual-PSRAM Memory Mover) tt_um_gstj_lockin (Digital IQ Lock-in (IEEE)) tt_um_benpayne_ps2_decoder (PS/2 Keyboard Decoder for 68k) tt_um_cass_s_ui_neuron_lif (Neurona LIF con Aprendizaje STDP Dinamico (IEEE)) tt_um_vga_glyph_mode_CDM_Matrix (CDM Matrix) tt_um_qd39l_xor_stream (Fixed-ROM XOR Stream Engine) tt_um_conv3x3 (3x3 Clock Rate Streaming Input Convolution Engine) tt_um_mc14500b_soc_extended (MC14500B Extended 1-bit Microcontroller SoC) tt_um_vga_hypno_spiral (tt_um_vga_hypno_spiral) tt_um_mattizen_morse_tree (Morse Tree LED Decoder) tt_um_CDM (Colegio de Muntinlupa DVD-like Display) tt_um_romd_uart_loader (UART SPI RAM Loader) tt_um_TscherterJunior_stapel_geraet (stapel gerät) tt_um_das2225_dna_accel (DNA_Accel) tt_um_tinysoc (TinySoC) tt_um_barrel_shifter (Barrel Shifter) tt_um_approx_mac_coprocessor (Approximate DSP: Time-Multiplexed MAC Coprocessor) tt_um_joesagents_market_split_oracle (Market-split oracle) tt_um_mgpauly1458_ringmeter (Ring oscillator frequency meter) tt_um_pettit_prism_lite (PRISM with Risc-V (TinyQV) SoC) tt_um_workshop_cpu (IEEE Workshop Simple CPU) tt_um_algofoogle_analog_junk (Simple comparator + 2 DACs analog layout in a 1x1 tile) tt_um_lkhanh_cordic (TinyQV SoC (Dual Memory Backend)) tt_um_4x4npu (4x4NPU: Dual-Lane INT4 Neural Accelerator) tt_um_abiaselli_izh_bridge_3x2 (Izhikevich event bridge (4 contexts)) tt_um_fabulous_ihp_26b (Tiny FABulous FPGA) tt_um_zanderivo_voronoi (Four-Metric VGA Nearest-Prototype Visualizer)