celox-runtime 0.4.1

Backend-independent Celox simulation runtime contracts
Documentation
use celox_design::{BitAccess, StateAddr as AbsoluteAddr, VarAtomBase};
use celox_testbench::{
    AssertMessage as GenericAssertMessage, ClockCount as GenericClockCount, CompiledExpr,
    ExecutableArgument, ExecutableAssertMessage, ExecutableClockCount, ExecutableLoopBound,
    ExecutableStatement, ExecutableTestbench, ExprBytecode, LoopBound as GenericLoopBound,
    SemanticArgument, SemanticComponentBinding, SemanticStatement, StateLocation, TestbenchProgram,
    TestbenchSelection, TestbenchStatement as GenericTestbenchStatement, TestbenchTarget,
};

use crate::{SignalRef, backend::SimBackend};

fn bind_expr<B: SimBackend>(
    backend: &B,
    expr: ExprBytecode<StateLocation<AbsoluteAddr>>,
) -> Option<CompiledExpr> {
    let layout = backend.layout();
    let bytecode = expr
        .bind_with(|address| layout.offsets.get(address).copied())
        .ok()?;
    Some(CompiledExpr::new(bytecode))
}

fn bind_component<B: SimBackend>(
    backend: &B,
    component: SemanticComponentBinding<AbsoluteAddr>,
    rtl_writes: &fxhash::FxHashSet<VarAtomBase<AbsoluteAddr>>,
) -> Option<celox_testbench::ExecutableComponentBinding<B::Event, SignalRef>> {
    Some(celox_testbench::ComponentBinding {
        instance: component.instance,
        connections: component
            .connections
            .into_iter()
            .map(|connection| {
                let output = match connection.output {
                    Some(output) => Some(bind_target(backend, output)?),
                    None => None,
                };
                let output_rtl_driven = output.as_ref().is_some_and(|output| {
                    let target_access = match &output.selection {
                        Some(selection) => selection
                            .offset
                            .constant_u64()
                            .and_then(|offset| usize::try_from(offset).ok())
                            .and_then(|lsb| {
                                output
                                    .width
                                    .checked_sub(1)
                                    .and_then(|tail| lsb.checked_add(tail))
                                    .map(|msb| BitAccess::new(lsb, msb))
                            }),
                        None => output
                            .signal
                            .width
                            .checked_sub(1)
                            .map(|msb| BitAccess::new(0, msb)),
                    };
                    rtl_writes.iter().any(|write| {
                        backend.resolve_signal(&write.id) == output.signal
                            && target_access.is_none_or(|target| target.overlaps(&write.access))
                    })
                });
                Some(celox_testbench::ComponentConnectionBinding {
                    port: connection.port,
                    input: match connection.input {
                        Some(input) => Some(bind_expr(backend, input)?),
                        None => None,
                    },
                    input_target: match connection.input_target {
                        Some(input) => Some(bind_target(backend, input)?),
                        None => None,
                    },
                    output,
                    output_rtl_driven,
                    event: connection
                        .event
                        .and_then(|event| backend.resolve_event_opt(&event)),
                })
            })
            .collect::<Option<Vec<_>>>()?,
    })
}

fn bind_assert_arg<B: SimBackend>(
    backend: &B,
    arg: SemanticArgument<AbsoluteAddr>,
) -> Option<ExecutableArgument> {
    Some(ExecutableArgument {
        expr: bind_expr(backend, arg.expr)?,
        width: arg.width,
        signed: arg.signed,
        is_string: arg.is_string,
    })
}

fn bind_assert_message<B: SimBackend>(
    backend: &B,
    message: GenericAssertMessage<SemanticArgument<AbsoluteAddr>>,
) -> Option<ExecutableAssertMessage> {
    match message {
        GenericAssertMessage::Formatted { template, args } => {
            let args = args
                .into_iter()
                .map(|arg| bind_assert_arg(backend, arg))
                .collect::<Option<Vec<_>>>()?;
            Some(GenericAssertMessage::Formatted { template, args })
        }
        GenericAssertMessage::DynamicArgs(args) => {
            let args = args
                .into_iter()
                .map(|arg| bind_assert_arg(backend, arg))
                .collect::<Option<Vec<_>>>()?;
            Some(GenericAssertMessage::DynamicArgs(args))
        }
    }
}

fn bind_clock_count<B: SimBackend>(
    backend: &B,
    count: GenericClockCount<ExprBytecode<StateLocation<AbsoluteAddr>>>,
) -> Option<ExecutableClockCount> {
    match count {
        GenericClockCount::Static(count) => Some(GenericClockCount::Static(count)),
        GenericClockCount::Dynamic(expr) => {
            Some(GenericClockCount::Dynamic(bind_expr(backend, expr)?))
        }
    }
}

fn bind_loop_bound<B: SimBackend>(
    backend: &B,
    bound: GenericLoopBound<ExprBytecode<StateLocation<AbsoluteAddr>>>,
) -> Option<ExecutableLoopBound> {
    match bound {
        GenericLoopBound::Static(bound) => Some(GenericLoopBound::Static(bound)),
        GenericLoopBound::Dynamic {
            expr,
            width,
            signed,
        } => Some(GenericLoopBound::Dynamic {
            expr: bind_expr(backend, expr)?,
            width,
            signed,
        }),
    }
}

fn bind_target<B: SimBackend>(
    backend: &B,
    target: TestbenchTarget<
        celox_testbench::SemanticSignal<AbsoluteAddr>,
        ExprBytecode<StateLocation<AbsoluteAddr>>,
    >,
) -> Option<TestbenchTarget<SignalRef, CompiledExpr>> {
    Some(TestbenchTarget {
        signal: backend.resolve_signal(&target.signal.address),
        selection: match target.selection {
            Some(selection) => Some(TestbenchSelection {
                offset: bind_expr(backend, selection.offset)?,
                width: selection.width,
            }),
            None => None,
        },
        width: target.width,
    })
}

fn bind_optional_target<B: SimBackend>(
    backend: &B,
    target: Option<
        TestbenchTarget<
            celox_testbench::SemanticSignal<AbsoluteAddr>,
            ExprBytecode<StateLocation<AbsoluteAddr>>,
        >,
    >,
) -> Option<Option<TestbenchTarget<SignalRef, CompiledExpr>>> {
    match target {
        Some(target) => Some(Some(bind_target(backend, target)?)),
        None => Some(None),
    }
}

fn bind_statement<B: SimBackend>(
    backend: &B,
    statement: SemanticStatement<AbsoluteAddr>,
) -> Option<ExecutableStatement<B::Event, SignalRef>> {
    match statement {
        GenericTestbenchStatement::ClockNext { clock_event, count } => {
            Some(GenericTestbenchStatement::ClockNext {
                clock_event: backend.resolve_event_opt(&clock_event)?,
                count: bind_clock_count(backend, count)?,
            })
        }
        GenericTestbenchStatement::ResetAssert {
            reset_signal,
            reset_event,
            clock_event,
            duration,
            assert_value,
            deassert_value,
        } => Some(GenericTestbenchStatement::ResetAssert {
            reset_signal: backend.resolve_signal(&reset_signal.address),
            reset_event: reset_event.and_then(|event| backend.resolve_event_opt(&event)),
            clock_event: backend.resolve_event_opt(&clock_event)?,
            duration: bind_clock_count(backend, duration)?,
            assert_value,
            deassert_value,
        }),
        GenericTestbenchStatement::Assert {
            expr,
            site_id,
            continue_on_fail,
            message,
            location,
        } => Some(GenericTestbenchStatement::Assert {
            expr: bind_expr(backend, expr)?,
            site_id,
            continue_on_fail,
            message: match message {
                Some(message) => Some(bind_assert_message(backend, message)?),
                None => None,
            },
            location,
        }),
        GenericTestbenchStatement::Display { message, newline } => {
            Some(GenericTestbenchStatement::Display {
                message: match message {
                    Some(message) => Some(bind_assert_message(backend, message)?),
                    None => None,
                },
                newline,
            })
        }
        GenericTestbenchStatement::If {
            expr,
            then_block,
            else_block,
        } => Some(GenericTestbenchStatement::If {
            expr: bind_expr(backend, expr)?,
            then_block: then_block
                .into_iter()
                .map(|statement| bind_statement(backend, statement))
                .collect::<Option<Vec<_>>>()?,
            else_block: else_block
                .into_iter()
                .map(|statement| bind_statement(backend, statement))
                .collect::<Option<Vec<_>>>()?,
        }),
        GenericTestbenchStatement::For {
            loop_var,
            start,
            end,
            inclusive,
            step,
            step_op,
            reverse,
            body,
        } => Some(GenericTestbenchStatement::For {
            loop_var: loop_var.map(|(signal, width, signed)| {
                (backend.resolve_signal(&signal.address), width, signed)
            }),
            start: bind_loop_bound(backend, start)?,
            end: bind_loop_bound(backend, end)?,
            inclusive,
            step,
            step_op,
            reverse,
            body: body
                .into_iter()
                .map(|statement| bind_statement(backend, statement))
                .collect::<Option<Vec<_>>>()?,
        }),
        GenericTestbenchStatement::Assign { dst, expr } => {
            Some(GenericTestbenchStatement::Assign {
                dst: bind_target(backend, dst)?,
                expr: bind_expr(backend, expr)?,
            })
        }
        GenericTestbenchStatement::RandomSeed { handle, value } => {
            Some(GenericTestbenchStatement::RandomSeed {
                handle,
                value: bind_expr(backend, value)?,
            })
        }
        GenericTestbenchStatement::RandomGet {
            handle,
            width,
            signed,
            ret,
        } => Some(GenericTestbenchStatement::RandomGet {
            handle,
            width,
            signed,
            ret: bind_optional_target(backend, ret)?,
        }),
        GenericTestbenchStatement::RandomGetRange {
            handle,
            min,
            max,
            width,
            signed,
            ret,
        } => Some(GenericTestbenchStatement::RandomGetRange {
            handle,
            min: bind_expr(backend, min)?,
            max: bind_expr(backend, max)?,
            width,
            signed,
            ret: bind_optional_target(backend, ret)?,
        }),
        GenericTestbenchStatement::RandomGetSeed { handle, ret } => {
            Some(GenericTestbenchStatement::RandomGetSeed {
                handle,
                ret: bind_optional_target(backend, ret)?,
            })
        }
        GenericTestbenchStatement::ComponentMethod {
            instance,
            method,
            args,
            ret,
            ret_width,
            ret_signed,
            ret_strict,
        } => Some(GenericTestbenchStatement::ComponentMethod {
            instance,
            method,
            args: args
                .into_iter()
                .map(|arg| bind_assert_arg(backend, arg))
                .collect::<Option<Vec<_>>>()?,
            ret: bind_optional_target(backend, ret)?,
            ret_width,
            ret_signed,
            ret_strict,
        }),
        GenericTestbenchStatement::Break => Some(GenericTestbenchStatement::Break),
        GenericTestbenchStatement::Finish => Some(GenericTestbenchStatement::Finish),
    }
}

pub fn bind_testbench_program<B: SimBackend>(
    backend: &B,
    program: TestbenchProgram<AbsoluteAddr>,
    rtl_writes: &fxhash::FxHashSet<VarAtomBase<AbsoluteAddr>>,
) -> Option<ExecutableTestbench<B::Event, SignalRef>> {
    let random_seed = program.configured_random_seed();
    let components = program.components().to_vec();
    let component_libraries = program.component_libraries().to_vec();
    let component_file_base = program.component_file_base().map(ToOwned::to_owned);
    let component_bindings = program
        .component_bindings()
        .to_vec()
        .into_iter()
        .map(|component| bind_component(backend, component, rtl_writes))
        .collect::<Option<Vec<_>>>()?;
    let statements = program
        .into_statements()
        .into_iter()
        .map(|statement| bind_statement(backend, statement))
        .collect::<Option<Vec<_>>>()?;
    Some(
        ExecutableTestbench::new_with_random_seed(statements, random_seed).with_component_runtime(
            components,
            component_libraries,
            component_file_base,
            component_bindings,
        ),
    )
}