Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Note: Ted is in a very early stage of design. The syntax and semantics described here are subject to change. We welcome feedback and contributions!

Ted (Timing-Explicit Description, aka The Teddy Bear Language) is a systems programming language with explicit logical time and deterministic concurrency. Hardware modeling is a first-class library built on the same semantics.

Philosophy

Ted is built on three core principles:

  1. Time as an effect - @ and event waiting are only legal in timed contexts; ordinary code stays direct and fast
  2. Deterministic concurrency - Scheduling is defined in terms of logical time so runs are reproducible
  3. Systems performance - Core code compiles to tight machine code; timed code lowers to efficient state machines

A Quick Taste

mod blink {
    out led: bit,

    loop {
        led = !led @ +500ms; // schedule the next toggle 500ms later
    }
}

This is timed code: the task advances logical time and yields between updates.

Core vs Timed Ted

  • Core Ted is ordinary systems code (functions, structs, loops, FFI). It does not involve a scheduler and never uses @.
  • Timed Ted opts into explicit logical time. Timed contexts include on handlers and loop blocks inside modules today; future timed fn will let timed code appear anywhere.

TODO: Define timed fn tasks and where they can appear outside modules.

Deterministic Concurrency

Scheduling is defined over logical time, so concurrent programs are deterministic by default. This enables reproducible tests and is designed to support replayable debugging and systematic exploration of schedules.

The @ Operator

Each timed task has a current logical time t:

on change(sig) {
    // Schedule a future update
    sig = 1 @ +10ns;

    // Read the past (temporal values only)
    let prev = sig @ -1;
}

@ is only legal in timed contexts. Ordinary values have no history; only temporal values support x @ -delta.

TODO: Add a standalone delay statement (@ +delta;) for timed code that needs to advance time without assigning.

Temporal Storage

Not every value is time-travelable. Ports and module-level state are temporal, and future syntax will make temporal storage explicit (for example, a signal or temporal declaration). Temporal values keep bounded history so the compiler can allocate compact ring buffers and stay fast.

Hardware Is a Library

Hardware modeling is built on the timed core with libraries and conventions:

  • out led: bit is sugar for a temporal output type
  • on rising(clk) is shorthand for subscribing to a rising-edge event stream
  • Waveform dumping is a runtime option, not a semantic requirement

Next Steps

Progress

This page tracks implementation status and planned work. Update it alongside changes to the language semantics or tooling.

Status Snapshot

  • Parser: stubbed; lexer and diagnostics are scaffolded
  • Core syntax: documented; implementation pending
  • Timed runtime: not implemented yet
  • Cranelift backend: prototype emits a main that only supports literal print(...) calls
  • CLI: compile/check are wired; compile uses the prototype backend

Near-Term TODOs

  • TODO: Implement the parser for module items, statements, and time operators.
  • TODO: Define explicit temporal storage syntax and history bounds.
  • TODO: Add a standalone delay statement (@ +delta;) for timed code without assignment.
  • TODO: Specify timed fn tasks and where they can appear.
  • TODO: Define standard event sources (timeouts, intervals, channels) and ordering rules.
  • TODO: Specify the core standard library surface (including I/O and print).
  • TODO: Lower MIR into Cranelift and emit real program semantics.

Longer-Term Goals

  • Lower timed code to explicit state machines and scheduled tasks.
  • Build a deterministic scheduler with a fast event queue.
  • Provide a standard library for timeouts, intervals, and waveform output.

Updating This Page

  • Add links to issues/PRs once issue tracking is in place.
  • Keep status short and factual; move detailed design notes into the language docs.

Installation

Prerequisites

Ted requires:

  • Rust (stable, latest version recommended)
  • Cargo (comes with Rust)

Install Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update stable

Building from Source

Clone the repository:

git clone https://github.com/ted-lang/ted.git
cd ted

Build the compiler:

cargo build --release

The ted binary will be at target/release/ted.

Installation

Add to PATH

# Add to your shell profile (.bashrc, .zshrc, etc.)
export PATH="$PATH:/path/to/ted/target/release"

Or install via Cargo

cargo install --path crates/ted-cli

Verify Installation

ted --version
ted --help

Development Setup

For contributing to Ted:

# Install development tools
rustup component add rustfmt clippy

# Build and test
cargo build --workspace
cargo test --workspace

# Format and lint
cargo fmt --all
cargo clippy --workspace

Editor Support

Ted files use the .ted extension. Syntax highlighting is planned for:

  • VS Code
  • Vim/Neovim
  • Emacs

Next Steps

Now that Ted is installed, continue to Hello World to write your first program.

Hello World

Let’s write your first Ted program. These examples use the timed hardware modeling library; core code looks like normal systems code and does not use @.

The Simplest Program

Create a file called hello.ted:

mod hello {
    print("Hello from Ted!");
}

This prints a message to the terminal.

TODO: print is a prototype intrinsic and currently only accepts literal strings or integers.

Check the Program

ted check hello.ted

If there are no errors, you’ll see:

No errors found.

Compile (Prototype)

You can produce a native executable with the prototype Cranelift backend:

ted compile hello.ted --emit exe -o ./hello
./hello

You should see:

Hello from Ted!

TODO: Lower real program semantics into codegen; the current backend only supports literal print(...) calls.

A Blinking LED

A more interesting example - an LED that toggles every 500ms:

mod blink {
    out led: bit,

    loop {
        led = !led @ +500ms;
    }
}

This demonstrates:

  • out led: bit - an output signal
  • loop { ... } - continuous execution
  • !led - toggle the current value
  • @ +500ms - schedule the change 500ms in the future

A Counter

A classic 8-bit counter:

mod counter {
    in  clk: bit,
    in  reset: bit,
    out count: u8,

    on rising(clk) {
        if reset {
            count = 0;
        } else {
            count = count + 1;
        }
    }
}

This demonstrates:

  • Multiple ports (in and out)
  • on rising(clk) - react to clock edges
  • Conditional logic with if/else

Using Past Values

Detect a rising edge manually:

mod edge_detect {
    in  signal: bit,
    out rising: bit,

    on change(signal) {
        rising = signal && !(signal @ -1);
    }
}

The @ -1 reads the signal’s value from one cycle ago. This is only legal for temporal values like ports and module state.

Next Steps

Syntax Overview

Ted uses a Rust-like syntax that should feel familiar to systems programmers.

Basic Structure

A Ted program consists of modules (the current top-level unit). Modules can host both core code and timed event handlers:

mod my_module {
    // port declarations
    in  input_signal: bit,
    out output_signal: bit,

    // logic
    on change(input_signal) {
        output_signal = input_signal;
    }
}

Comments

// Single-line comment

/*
   Multi-line
   comment
*/

Identifiers

Identifiers follow Rust conventions:

  • Start with a letter or underscore
  • Contain letters, digits, or underscores
  • Case-sensitive
signal_name
_private
counter123

Keywords

mod     in      out     inout
on      rising  falling change
if      else    loop    break
let     fn      return
bit     u8      u16     u32     u64
i8      i16     i32     i64

Operators

Arithmetic

a + b    // addition
a - b    // subtraction
a * b    // multiplication
a / b    // division
a % b    // modulo

Bitwise

a & b    // AND
a | b    // OR
a ^ b    // XOR
~a       // NOT
a << n   // left shift
a >> n   // right shift

Comparison

a == b   // equal
a != b   // not equal
a < b    // less than
a <= b   // less or equal
a > b    // greater than
a >= b   // greater or equal

Logical

a && b   // logical AND
a || b   // logical OR
!a       // logical NOT

Calls

print("Hello from Ted!");

TODO: Function calls and string literals are prototype-level and will be specified alongside the core standard library.

Time

In timed contexts, @ is available for history reads and scheduled writes.

x @ -1        // history read (temporal values only, timed contexts)
x = 1 @ +10ns // schedule a write 10ns later

TODO: Add a standalone delay statement (@ +delta;) for timed code that needs to advance time without assigning.

Blocks

Blocks use curly braces:

{
    let x = 1;
    let y = 2;
    x + y
}

Statements vs Expressions

Most constructs are expressions that return values:

let result = if condition { a } else { b };

Time and @

Ted treats time as an explicit effect in timed contexts. Each timed task carries a current logical time t.

Timed Contexts

@ and event waiting are only legal in timed contexts, such as:

  • on handlers
  • loop blocks inside modules
  • (planned) timed fn tasks

Core code cannot use @ and compiles directly with no scheduler overhead.

TODO: Define timed fn syntax and formalize module-level timed tasks in the grammar and parser.

Logical Time Model

Timed code runs as tasks with a current logical time t. Scheduling is expressed with assignment offsets.

  • x = v writes at the task’s current time t
  • x = v @ +delta schedules the write at t+delta and yields the task
  • x @ -delta reads a temporal value as it was at t-delta

Here, delta is a non-negative time offset (cycles or time units) and can come from a literal or an expression.

Example:

on change(sig) {
    sig = 1 @ +10ns;
}

TODO: Add a standalone delay statement (@ +delta;) for timed code that needs to advance time without assigning.

Temporal Storage

Not every value is time-travelable. Only temporal values keep history:

  • Ports and module-level state are temporal today
  • Future syntax will make temporal storage explicit (for example, signal or temporal declarations)

Temporal values keep bounded history so the compiler can allocate compact ring buffers. The history window can be inferred from @ -delta uses or declared explicitly.

TODO: Specify explicit temporal storage declarations (for example, signal or temporal) and history bounds.

Reading the Past

Access previous values of a temporal signal:

Note: The examples in this section assume they appear inside a timed context (for example, an on handler or a module-level loop).

// Relative to current time
let prev = sig @ -1;        // one cycle ago
let older = sig @ -5;       // five cycles ago

// With time units
let past = sig @ -10ns;     // 10 nanoseconds ago
let history = sig @ -1us;   // 1 microsecond ago

Use Cases

Edge Detection:

let rising_edge = sig && !(sig @ -1);
let falling_edge = !sig && (sig @ -1);

Change Detection:

let changed = sig != (sig @ -1);

Delay Line:

on change(input) {
    delayed = input @ -100ns;
}

Scheduling the Future

Schedule values for future logical time:

// Relative scheduling
sig = 1 @ +1;           // schedule for the next cycle
sig = value @ +10;      // 10 cycles from now

// With time units
sig = 1 @ +10ns;        // in 10 nanoseconds
sig = 0 @ +1ms;         // in 1 millisecond

Use Cases

Pulse Generation:

// Generate a 10ns pulse
pulse = 1;
pulse = 0 @ +10ns;

Delayed Response:

on rising(trigger) {
    output = 1 @ +100ns;
}

Periodic Toggle:

loop {
    led = !led @ +500ms;
}

Time Units

Ted supports these time units:

UnitMeaning
(none)cycles
nsnanoseconds
usmicroseconds
msmilliseconds
sseconds

These units are part of logical time; they do not imply wall-clock delays.

Combining Past and Future

You can use past values to determine future assignments:

// Capture now, then apply later
let prev = sig @ -1;
sig = !prev @ +1;

// Delayed feedback
let sample = input @ -1;
output = sample @ +10ns;

Rules and Constraints

  1. Timed only - @ is only legal in timed contexts
  2. Temporal only - History reads (x @ -delta) require temporal values
  3. Scheduling - x = v @ +delta schedules a write and yields the task
  4. No future reads - x @ +delta is invalid as a read; only assignments can target the future
  5. Evaluation timing - x = v @ +delta evaluates v after the time advance; capture values explicitly if needed
  6. Grouping - Use parentheses for compound expressions (for example, (a + b) @ +1)
  7. Deterministic - Ordering is defined; same input produces identical output
  8. Progress - Timed loops must include time advancement or event waits to avoid zero-time nontermination
  9. Cycle-accurate - Integer offsets refer to logical cycles
  10. Time-accurate - Unit-based offsets refer to logical time units

Events

Timed Ted is event-driven. Event handlers execute in response to event sources (signals are one common source).

The on Keyword

The on keyword defines event handlers:

on <event> {
    // code to execute
}

on introduces a timed context, so @ and event waits are legal inside the handler. Handlers run at the current logical time; schedule assignments with @ +delta to advance time when needed.

TODO: Add a standalone delay statement (@ +delta;) for timed code that needs to advance time without assigning.

Event Types

The following adapters are part of the hardware modeling library:

rising - Rising Edge

Triggers when a signal transitions from 0 to 1:

on rising(clk) {
    count = count + 1;
}

falling - Falling Edge

Triggers when a signal transitions from 1 to 0:

on falling(clk) {
    latch = data;
}

change - Any Change

Triggers on any value change:

on change(data) {
    valid = 1;
    output = process(data);
}

Timed events are not limited to hardware signals. The standard library will add event sources like timeouts, intervals, and channels so the same model applies to software and simulations.

TODO: Define the standard event source APIs (timeouts, intervals, channels) and their ordering rules.

Multiple Events

Multiple Handlers

You can have multiple handlers for the same signal:

on rising(clk) {
    // happens first
}

on rising(clk) {
    // happens second
}

Multiple Signals

React to changes in any of several signals:

on change(a, b, c) {
    result = a + b + c;
}

Event Priority

When multiple events trigger simultaneously:

  1. Events are processed in declaration order
  2. All handlers for one signal complete before the next
  3. Nested events are queued, not immediately executed

Conditional Events

Use guards to filter events:

on rising(clk) if enable {
    // only executes if enable is high
    count = count + 1;
}

Comparison with Verilog

TedVerilog
on rising(clk)always @(posedge clk)
on falling(clk)always @(negedge clk)
on change(sig)always @(sig)
on change(a, b)always @(a or b)

These are convenience adapters; the core semantics are event sources plus timed handlers.

Best Practices

  1. Keep handlers small - Each handler should do one thing
  2. Avoid circular triggers - Don’t create infinite event loops
  3. Use rising/falling for clocks - More efficient than change
  4. Document timing assumptions - Especially for multi-signal handlers

Modules

Modules are the current top-level unit in Ted. Hardware modeling uses ports and event handlers, but core code can live inside modules without any timing.

Module Declaration

mod module_name {
    // ports
    // internal signals
    // logic
}

Ports

Ports define the module’s interface for hardware modeling:

mod example {
    in  clock: bit,           // input port
    out data: u8,             // output port
    inout bus: u32,           // bidirectional port
}

Port Directions

DirectionDescription
inInput - read only inside module
outOutput - write only inside module
inoutBidirectional - read and write

Port Types

Ports must have explicit types:

in single_bit: bit,
in byte_value: u8,
in wide_bus: u64,

Ports are convenience syntax for temporal values; they can be modeled as library types like Signal<Bit, Out>.

Internal Signals

Signals declared without direction are internal:

mod counter {
    in  clk: bit,
    out count: u8,

    // Internal signal
    let overflow: bit = 0;

    on rising(clk) {
        if count == 255 {
            overflow = 1;
        }
        count = count + 1;
    }
}

Timed Contexts

on handlers and module-level loop blocks are timed contexts. @ is only legal inside them; helper code outside remains ordinary core Ted.

TODO: Add explicit timed task declarations and timed fn for timed code outside modules.

Module Instantiation

Instantiate modules within other modules:

mod top {
    in  clk: bit,
    out led: bit,

    // Instantiate a counter
    counter my_counter {
        clk: clk,
        count: _, // unconnected
    };

    // Use counter output
    led = my_counter.overflow;
}

TODO: Define the exact semantics of module-level assignments (for example, continuous assignment vs event-driven evaluation).

Connection Syntax

module_type instance_name {
    port_name: signal,
    another_port: another_signal,
};

Use _ for unconnected ports.

Parameterized Modules

Modules can have parameters:

mod counter<WIDTH: u32 = 8> {
    in  clk: bit,
    out count: uint<WIDTH>,

    on rising(clk) {
        count = count + 1;
    }
}

// Instantiate with custom width
counter<16> wide_counter { ... };

Module Hierarchy

Modules can contain other modules:

mod system {
    in clk: bit,

    // Connect them
    let bus: u32;
    cpu processor { clk: clk, data_out: bus };
    memory ram { clk: clk, data_in: bus };
}

Visibility

By default, all ports are visible. Use _ prefix for internal implementation details:

mod example {
    out result: u8,      // Public
    let _temp: u8,       // Private (convention)
}

Types

Ted has a simple, systems-oriented type system with explicit widths. Temporal storage is explicit by design and opt-in.

Temporal Storage

Ted distinguishes ordinary values from temporal values. Only temporal values keep history and allow x @ -delta.

  • Ports and module-level state are temporal today
  • Future syntax will make temporal storage explicit (for example, signal or temporal declarations)

Temporal values keep bounded history so the compiler can allocate compact ring buffers and avoid unbounded storage.

TODO: Define explicit temporal storage syntax (for example, signal or temporal) and history bounds.

Primitive Types

Bit

Single binary value (0 or 1):

let flag: bit = 0;
let enable: bit = 1;

Unsigned Integers

Fixed-width unsigned integers:

TypeWidthRange
u88 bits0 to 255
u1616 bits0 to 65,535
u3232 bits0 to 4,294,967,295
u6464 bits0 to 18,446,744,073,709,551,615
let byte: u8 = 42;
let word: u32 = 0xDEADBEEF;

Signed Integers

Fixed-width signed integers (two’s complement):

TypeWidthRange
i88 bits-128 to 127
i1616 bits-32,768 to 32,767
i3232 bits-2^31 to 2^31-1
i6464 bits-2^63 to 2^63-1
let offset: i16 = -100;

Custom Width

For arbitrary bit widths:

let nibble: uint<4> = 0xF;
let wide: uint<128> = 0;

Arrays

Fixed-size arrays:

let memory: [u8; 256];           // 256 bytes
let registers: [u32; 16];        // 16 registers

// Access
let value = memory[0];
memory[index] = data;

Structs

Group related signals:

struct Packet {
    valid: bit,
    data: u64,
    tag: u8,
}

let pkt: Packet;
pkt.valid = 1;
pkt.data = payload;

Type Inference

Types can often be inferred:

let x = 42;           // inferred as u32
let y = x + 1;        // also u32
let flag = true;      // inferred as bit

Type Conversions

Explicit Casts

let narrow: u8 = wide as u8;      // truncate
let wider: u32 = narrow as u32;   // zero-extend

Bit Extraction

let byte = word[7:0];             // bits 7 down to 0
let nibble = byte[3:0];           // lower nibble

Concatenation

let combined = {high, low};       // concatenate
let word = {byte, byte, byte, byte};

Constants

Compile-time constants:

const WIDTH: u32 = 8;
const DEPTH: u32 = 256;
const INIT: u8 = 0xFF;

Literals

Integer Literals

42          // decimal
0xFF        // hexadecimal
0b1010      // binary
0o77        // octal
1_000_000   // underscores for readability

Boolean Literals

true        // same as 1 for bit
false       // same as 0 for bit

Time Literals

10ns        // nanoseconds
1us         // microseconds
100ms       // milliseconds
1s          // seconds

String Literals

"Hello from Ted!"

TODO: Strings are prototype-only today and will be specified with the core standard library.

Grammar

This is the formal grammar for Ted in EBNF notation.

TODO: This grammar is evolving. It reflects the documented syntax but does not yet capture every planned feature (for example, fn and advanced type forms).

Lexical Grammar

(* Whitespace and comments *)
whitespace = " " | "\t" | "\n" | "\r" ;
line_comment = "//" , { any_char - "\n" } , "\n" ;
block_comment = "/*" , { any_char } , "*/" ;

(* Identifiers *)
ident = ( letter | "_" ) , { letter | digit | "_" } ;
letter = "a" | ... | "z" | "A" | ... | "Z" ;
digit = "0" | ... | "9" ;

(* Literals *)
int_literal = decimal | hex | binary | octal ;
decimal = digit , { digit | "_" } ;
hex = "0x" , hex_digit , { hex_digit | "_" } ;
binary = "0b" , ( "0" | "1" ) , { "0" | "1" | "_" } ;
octal = "0o" , octal_digit , { octal_digit | "_" } ;

time_literal = int_literal , time_unit ;
time_unit = "ns" | "us" | "ms" | "s" ;
bool_literal = "true" | "false" ;
string_literal = "\"" , { ? any character except \" and newline ? | escape_sequence } , "\"" ;
escape_sequence = "\\\\" , ( "\"" | "\\" | "n" | "r" | "t" ) ;

(* Keywords *)
keyword = "mod" | "in" | "out" | "inout" | "let" | "const"
        | "on" | "rising" | "falling" | "change"
        | "if" | "else" | "loop" | "break" | "return"
        | "fn" | "struct" | "true" | "false" ;

Syntax Grammar

(* Program structure *)
program = { module } ;

module = "mod" , ident , [ generic_params ] , "{" , module_body , "}" ;
module_body = { port_decl | const_decl | signal_decl | struct_decl | module_stmt | event_handler | timed_loop | module_inst } ;

(* Ports and signals *)
port_decl = port_dir , ident , ":" , type , "," ;
port_dir = "in" | "out" | "inout" ;
signal_decl = "let" , ident , [ ":" , type ] , [ "=" , expr ] , ";" ;
const_decl = "const" , ident , ":" , type , "=" , expr , ";" ;
struct_decl = "struct" , ident , "{" , { struct_field } , "}" ;
struct_field = ident , ":" , type , "," ;

(* Types *)
type = primitive_type | array_type | custom_type ;
primitive_type = "bit" | "u8" | "u16" | "u32" | "u64"
               | "i8" | "i16" | "i32" | "i64"
               | "uint" , "<" , expr , ">" ;
array_type = "[" , type , ";" , expr , "]" ;
custom_type = ident , [ generic_args ] ;

(* Events *)
event_handler = "on" , event_spec , [ "if" , expr ] , block ;
event_spec = event_type , "(" , ident_list , ")" ;
event_type = "rising" | "falling" | "change" ;
ident_list = ident , { "," , ident } ;

(* Timed tasks *)
timed_loop = "loop" , block ;

(* Expressions *)
expr = time_expr | binary_expr | unary_expr | postfix_expr ;
unary_expr = unary_op , expr ;
unary_op = "!" | "~" | "-" ;
binary_expr = expr , binary_op , expr ;
binary_op = "+" | "-" | "*" | "/" | "%"
          | "&" | "|" | "^" | "<<" | ">>"
          | "==" | "!=" | "<" | "<=" | ">" | ">="
          | "&&" | "||" ;
time_expr = postfix_expr , "@" , time_offset ;
time_offset = ( "+" | "-" ) , expr ;
postfix_expr = primary_expr , { field_access | index_access | slice_access | call_suffix } ;
field_access = "." , ident ;
index_access = "[" , expr , "]" ;
slice_access = "[" , expr , ":" , expr , "]" ;
call_suffix = "(" , [ expr_list ] , ")" ;
expr_list = expr , { "," , expr } ;
primary_expr = ident | literal | "(" , expr , ")" | block | concat_expr ;
concat_expr = "{" , expr , { "," , expr } , "}" ;
literal = int_literal | bool_literal | time_literal | string_literal ;

(* Statements *)
stmt = let_stmt | assign_stmt | if_stmt | loop_stmt | break_stmt | expr_stmt ;
(* Module-level statements (combinational logic) *)
module_stmt = assign_stmt | expr_stmt ;
let_stmt = "let" , ident , [ ":" , type ] , [ "=" , expr ] , ";" ;
assign_stmt = lvalue , "=" , expr , ";" ;
lvalue = ident , { field_access | index_access | slice_access } ;
if_stmt = "if" , expr , block , [ "else" , ( block | if_stmt ) ] ;
loop_stmt = "loop" , block ;
break_stmt = "break" , ";" ;
expr_stmt = expr , ";" ;

block = "{" , { stmt } , [ expr ] , "}" ;

(* Module instantiation *)
module_inst = ident , [ generic_args ] , ident , "{" , connections , "}" , ";" ;
connections = { ident , ":" , ( expr | "_" ) , "," } ;

(* Generics *)
generic_params = "<" , generic_param , { "," , generic_param } , ">" ;
generic_param = ident , ":" , type , [ "=" , expr ] ;
generic_args = "<" , expr , { "," , expr } , ">" ;

Operator Precedence

From highest to lowest:

PrecedenceOperatorsAssociativity
1@ (time)Left
2! ~ - (unary)Right
3* / %Left
4+ -Left
5<< >>Left
6< <= > >=Left
7== !=Left
8&Left
9^Left
10``
11&&Left
12`
13= (assignment)Right

Example Programs

This section contains complete Ted example programs. These examples focus on the timed hardware modeling library and explicit time semantics.

Basic Examples

Hello World

The simplest Ted program:

mod hello {
    print("Hello from Ted!");
}

TODO: print is a prototype intrinsic and currently only accepts literal strings or integers.

Toggle an LED every 500ms:

mod blink {
    out led: bit,

    loop {
        led = !led @ +500ms;
    }
}

Counter

8-bit counter with reset:

mod counter {
    in  clk: bit,
    in  reset: bit,
    out count: u8,

    on rising(clk) {
        if reset {
            count = 0;
        } else {
            count = count + 1;
        }
    }
}

Intermediate Examples

Edge Detector

Detect rising and falling edges:

mod edge_detector {
    in  signal: bit,
    out rising: bit,
    out falling: bit,

    on change(signal) {
        let prev = signal @ -1;
        rising = signal && !prev;
        falling = !signal && prev;
    }
}

Pulse Generator

Generate a pulse of specific width:

mod pulse_gen {
    in  trigger: bit,
    in  width: u32,      // pulse width in cycles
    out pulse: bit,

    on rising(trigger) {
        pulse = 1;
        pulse = 0 @ +width;
    }
}

Debouncer

Debounce a noisy input signal:

mod debouncer {
    in  noisy: bit,
    out clean: bit,

    let stable_count: u8 = 0;
    const THRESHOLD: u8 = 10;

    on change(noisy) {
        stable_count = 0;
    }

    loop {
        if stable_count < THRESHOLD {
            stable_count = (stable_count + 1) @ +1ms;
        } else {
            clean = noisy;
        }
    }
}

Advanced Examples

FIFO Queue

Simple first-in-first-out buffer:

mod fifo<DEPTH: u32 = 16> {
    in  clk: bit,
    in  write_en: bit,
    in  read_en: bit,
    in  data_in: u8,
    out data_out: u8,
    out empty: bit,
    out full: bit,

    let buffer: [u8; DEPTH];
    let write_ptr: u32 = 0;
    let read_ptr: u32 = 0;
    let count: u32 = 0;

    empty = count == 0;
    full = count == DEPTH;

    on rising(clk) {
        if write_en && !full {
            buffer[write_ptr] = data_in;
            write_ptr = (write_ptr + 1) % DEPTH;
            count = count + 1;
        }

        if read_en && !empty {
            data_out = buffer[read_ptr];
            read_ptr = (read_ptr + 1) % DEPTH;
            count = count - 1;
        }
    }
}

Shift Register

Configurable shift register:

mod shift_reg<WIDTH: u32 = 8> {
    in  clk: bit,
    in  data_in: bit,
    in  load: bit,
    in  parallel_in: uint<WIDTH>,
    out data_out: bit,
    out parallel_out: uint<WIDTH>,

    let reg: uint<WIDTH> = 0;

    data_out = reg[WIDTH - 1];
    parallel_out = reg;

    on rising(clk) {
        if load {
            reg = parallel_in;
        } else {
            reg = {reg[WIDTH-2:0], data_in};
        }
    }
}

Running Examples

Check an example:

ted check examples/counter.ted

Compile the hello example (prototype backend):

ted compile examples/hello.ted --emit exe -o ./hello
./hello

TODO: The prototype codegen only supports literal print(...) calls right now.

Simulate (when implemented):

ted sim counter.tedbc --cycles 100